protostar 0.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +60 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +647 -0
  5. data/THIRD_PARTY_NOTICES.md +61 -0
  6. data/exe/protoc-gen-protostar +7 -0
  7. data/exe/protostar +7 -0
  8. data/lib/protostar/builder/generator.rb +71 -0
  9. data/lib/protostar/builder/sink.rb +35 -0
  10. data/lib/protostar/builder.rb +965 -0
  11. data/lib/protostar/cli.rb +267 -0
  12. data/lib/protostar/codec/decode_generator.rb +199 -0
  13. data/lib/protostar/codec/decoder.rb +296 -0
  14. data/lib/protostar/codec/encode_generator.rb +224 -0
  15. data/lib/protostar/codec/encoder.rb +121 -0
  16. data/lib/protostar/codec/sizer.rb +91 -0
  17. data/lib/protostar/codec.rb +15 -0
  18. data/lib/protostar/compiler/ast.rb +193 -0
  19. data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
  20. data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
  21. data/lib/protostar/compiler/emitters/grpc.rb +175 -0
  22. data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
  23. data/lib/protostar/compiler/emitters/rbs.rb +394 -0
  24. data/lib/protostar/compiler/emitters/ruby.rb +503 -0
  25. data/lib/protostar/compiler/emitters.rb +268 -0
  26. data/lib/protostar/compiler/include/LICENSE +32 -0
  27. data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
  28. data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
  29. data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
  30. data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
  31. data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
  32. data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
  33. data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
  34. data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
  35. data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
  36. data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
  37. data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
  38. data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
  39. data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
  40. data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
  41. data/lib/protostar/compiler/lexer.rb +659 -0
  42. data/lib/protostar/compiler/linker.rb +894 -0
  43. data/lib/protostar/compiler/option_interpreter.rb +363 -0
  44. data/lib/protostar/compiler/parser.rb +1696 -0
  45. data/lib/protostar/compiler/retention.rb +104 -0
  46. data/lib/protostar/compiler/run.rb +184 -0
  47. data/lib/protostar/compiler/source_info.rb +161 -0
  48. data/lib/protostar/compiler/symbol_table.rb +223 -0
  49. data/lib/protostar/compiler/text_format.rb +340 -0
  50. data/lib/protostar/compiler/validator.rb +944 -0
  51. data/lib/protostar/compiler.rb +304 -0
  52. data/lib/protostar/diagnostics/codes.rb +1307 -0
  53. data/lib/protostar/diagnostics/formats.rb +64 -0
  54. data/lib/protostar/diagnostics/renderer.rb +240 -0
  55. data/lib/protostar/diagnostics/source.rb +153 -0
  56. data/lib/protostar/diagnostics.rb +117 -0
  57. data/lib/protostar/dsl.rb +367 -0
  58. data/lib/protostar/enum.rb +70 -0
  59. data/lib/protostar/errors.rb +23 -0
  60. data/lib/protostar/google/protobuf/any_pb.rb +90 -0
  61. data/lib/protostar/google/protobuf/api_pb.rb +199 -0
  62. data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
  63. data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
  64. data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
  65. data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
  66. data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
  67. data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
  68. data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
  69. data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
  70. data/lib/protostar/google/protobuf/type_pb.rb +202 -0
  71. data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
  72. data/lib/protostar/google_protobuf_shim.rb +24 -0
  73. data/lib/protostar/json/encoder.rb +340 -0
  74. data/lib/protostar/json/errors.rb +34 -0
  75. data/lib/protostar/json/parser.rb +437 -0
  76. data/lib/protostar/json/path.rb +39 -0
  77. data/lib/protostar/json.rb +95 -0
  78. data/lib/protostar/lint/command.rb +197 -0
  79. data/lib/protostar/lint/config.rb +98 -0
  80. data/lib/protostar/lint/context.rb +126 -0
  81. data/lib/protostar/lint/directives.rb +56 -0
  82. data/lib/protostar/lint/fixer.rb +165 -0
  83. data/lib/protostar/lint/naming.rb +54 -0
  84. data/lib/protostar/lint/rule.rb +120 -0
  85. data/lib/protostar/lint/rules/comment_enum.rb +31 -0
  86. data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
  87. data/lib/protostar/lint/rules/comment_field.rb +33 -0
  88. data/lib/protostar/lint/rules/comment_message.rb +31 -0
  89. data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
  90. data/lib/protostar/lint/rules/comment_service.rb +31 -0
  91. data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
  92. data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
  93. data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
  94. data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
  95. data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
  96. data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
  97. data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
  98. data/lib/protostar/lint/rules/field_not_required.rb +39 -0
  99. data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
  100. data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
  101. data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
  102. data/lib/protostar/lint/rules/import_no_public.rb +29 -0
  103. data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
  104. data/lib/protostar/lint/rules/import_used.rb +33 -0
  105. data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
  106. data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
  107. data/lib/protostar/lint/rules/no_groups.rb +34 -0
  108. data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
  109. data/lib/protostar/lint/rules/package_defined.rb +28 -0
  110. data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
  111. data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
  112. data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
  113. data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
  114. data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
  115. data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
  116. data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
  117. data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
  118. data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
  119. data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
  120. data/lib/protostar/lint/rules/service_suffix.rb +28 -0
  121. data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
  122. data/lib/protostar/lint/runner.rb +135 -0
  123. data/lib/protostar/lint.rb +122 -0
  124. data/lib/protostar/message/accessors.rb +86 -0
  125. data/lib/protostar/message/coercion.rb +171 -0
  126. data/lib/protostar/message/field_info.rb +145 -0
  127. data/lib/protostar/message/reflection.rb +149 -0
  128. data/lib/protostar/message.rb +572 -0
  129. data/lib/protostar/plugin.rb +94 -0
  130. data/lib/protostar/schema/default_value.rb +113 -0
  131. data/lib/protostar/schema/descriptor.rb +41 -0
  132. data/lib/protostar/schema/draft/copy.rb +88 -0
  133. data/lib/protostar/schema/draft/resolution.rb +177 -0
  134. data/lib/protostar/schema/draft.rb +779 -0
  135. data/lib/protostar/schema/enum.rb +106 -0
  136. data/lib/protostar/schema/extension_range.rb +37 -0
  137. data/lib/protostar/schema/features.rb +153 -0
  138. data/lib/protostar/schema/field.rb +201 -0
  139. data/lib/protostar/schema/file.rb +217 -0
  140. data/lib/protostar/schema/freezer.rb +61 -0
  141. data/lib/protostar/schema/message.rb +104 -0
  142. data/lib/protostar/schema/oneof.rb +38 -0
  143. data/lib/protostar/schema/options.rb +367 -0
  144. data/lib/protostar/schema/pool.rb +179 -0
  145. data/lib/protostar/schema/proto.rb +576 -0
  146. data/lib/protostar/schema/resolver.rb +118 -0
  147. data/lib/protostar/schema/service.rb +89 -0
  148. data/lib/protostar/schema/source_info.rb +53 -0
  149. data/lib/protostar/schema.rb +65 -0
  150. data/lib/protostar/short_names/any.rb +7 -0
  151. data/lib/protostar/short_names/duration.rb +7 -0
  152. data/lib/protostar/short_names/empty.rb +7 -0
  153. data/lib/protostar/short_names/field_mask.rb +7 -0
  154. data/lib/protostar/short_names/struct.rb +9 -0
  155. data/lib/protostar/short_names/timestamp.rb +7 -0
  156. data/lib/protostar/short_names/wrappers.rb +15 -0
  157. data/lib/protostar/text/extensions.rb +114 -0
  158. data/lib/protostar/text/parser.rb +439 -0
  159. data/lib/protostar/text/printer.rb +304 -0
  160. data/lib/protostar/text/tokenizer.rb +116 -0
  161. data/lib/protostar/text.rb +67 -0
  162. data/lib/protostar/types.rb +152 -0
  163. data/lib/protostar/unknown_field.rb +58 -0
  164. data/lib/protostar/version.rb +5 -0
  165. data/lib/protostar/view/generator.rb +298 -0
  166. data/lib/protostar/view/map.rb +194 -0
  167. data/lib/protostar/view/repeated.rb +143 -0
  168. data/lib/protostar/view/scan.rb +183 -0
  169. data/lib/protostar/view.rb +283 -0
  170. data/lib/protostar/well_known/any.rb +52 -0
  171. data/lib/protostar/well_known/duration.rb +53 -0
  172. data/lib/protostar/well_known/list_value.rb +63 -0
  173. data/lib/protostar/well_known/struct.rb +55 -0
  174. data/lib/protostar/well_known/timestamp.rb +64 -0
  175. data/lib/protostar/well_known/value.rb +60 -0
  176. data/lib/protostar/well_known.rb +38 -0
  177. data/lib/protostar/wire/io_source.rb +40 -0
  178. data/lib/protostar/wire/reader.rb +254 -0
  179. data/lib/protostar/wire/writer.rb +201 -0
  180. data/lib/protostar/wire.rb +96 -0
  181. data/lib/protostar.rb +95 -0
  182. data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
  183. data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
  184. data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
  185. data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
  186. data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
  187. data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
  188. data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
  189. data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
  190. data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
  191. data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
  192. data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
  193. data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
  194. data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
  195. data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
  196. data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
  197. data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
  198. data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
  199. data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
  200. data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
  201. data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
  202. data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
  203. data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
  204. data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
  205. data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
  206. data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
  207. data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
  208. data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
  209. data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
  210. data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
  211. data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
  212. data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
  213. data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
  214. data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
  215. data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
  216. data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
  217. data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
  218. data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
  219. data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
  220. data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
  221. data/sig/protostar/lint.rbs +216 -0
  222. data/sig/protostar.rbs +326 -0
  223. metadata +366 -0
@@ -0,0 +1,894 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ class Compiler
5
+ # Links one parsed file against the pool, the way protoc's
6
+ # DescriptorBuilder does: drafts a {Schema::File} as it registers the
7
+ # file's symbols, resolves every name onto the draft, interprets options
8
+ # into it, and freezes it. It reports protoc's errors in protoc's order,
9
+ # each against the AST node and location kind protoc reports it at;
10
+ # {Validator} adds the checks protoc runs once the file is built.
11
+ #
12
+ # @api private
13
+ class Linker
14
+ # One error or warning
15
+ class Problem
16
+ # @return [Symbol] :error or :warning
17
+ attr_reader :severity
18
+ # @return [AST::Node, nil] whose recorded position protoc reports, nil for none
19
+ attr_reader :node
20
+ # @return [Symbol, nil] the kind of position (:name, :number, ...), or :import
21
+ attr_reader :location
22
+ # @return [String]
23
+ attr_reader :message
24
+ # @return [String, nil] for an :import problem, the imported file name
25
+ attr_reader :import
26
+ # @return [Array<Array(AST::Node, Symbol, String)>] other places the problem
27
+ # involves, as node, position kind and label, for the rich renderer
28
+ attr_reader :related
29
+
30
+ def initialize(severity:, message:, node: nil, location: nil, import: nil, related: [])
31
+ @severity = severity
32
+ @node = node
33
+ @location = location
34
+ @message = message
35
+ @import = import
36
+ @related = related.freeze
37
+ freeze
38
+ end
39
+
40
+ def error?
41
+ @severity == :error
42
+ end
43
+ end
44
+
45
+ MAX_FIELD_NUMBER = 536_870_911
46
+ INT32_MAX = 2**31 - 1
47
+ LEGACY_EDITIONS = %i[EDITION_PROTO2 EDITION_PROTO3].freeze
48
+ MINIMUM_EDITION = :EDITION_PROTO2
49
+ MAXIMUM_EDITION = :EDITION_2026
50
+
51
+ OPTIONS_TYPES = {
52
+ file: "google.protobuf.FileOptions", message: "google.protobuf.MessageOptions",
53
+ field: "google.protobuf.FieldOptions", oneof: "google.protobuf.OneofOptions",
54
+ extension_range: "google.protobuf.ExtensionRangeOptions", enum: "google.protobuf.EnumOptions",
55
+ enum_value: "google.protobuf.EnumValueOptions", service: "google.protobuf.ServiceOptions",
56
+ method: "google.protobuf.MethodOptions"
57
+ }.freeze
58
+
59
+ # The options of one element awaiting interpretation
60
+ class Pending
61
+ # @return [AST::Node]
62
+ attr_reader :node
63
+ # @return [Symbol] which options message applies: a key of {OPTIONS_TYPES}
64
+ attr_reader :kind
65
+ # @return [String] the scope custom option names are looked up from
66
+ attr_reader :name_scope
67
+ # @return [Array<Integer>] source path of the element's `options` field
68
+ attr_reader :path
69
+
70
+ def initialize(node:, kind:, name_scope:, path:)
71
+ @node = node
72
+ @kind = kind
73
+ @name_scope = name_scope
74
+ @path = path
75
+ freeze
76
+ end
77
+
78
+ # @return [AST::Options] the uninterpreted options
79
+ def options
80
+ @node.options
81
+ end
82
+ end
83
+
84
+ # @return [Array<Problem>]
85
+ attr_reader :problems
86
+ # @return [AST::File]
87
+ attr_reader :ast
88
+ # @return [SymbolTable]
89
+ attr_reader :table
90
+ # @return [Schema::Pool]
91
+ attr_reader :pool
92
+ # @return [Symbol] the file's edition (EDITION_PROTO2 or EDITION_PROTO3 for legacy syntax)
93
+ attr_reader :edition
94
+ # @return [Schema::File, nil] the built file, when linking succeeded
95
+ attr_reader :file
96
+ # @return [Array<SourceInfo::Location>] locations after option paths were rewritten
97
+ attr_reader :locations
98
+
99
+ # @param ast [AST::File]
100
+ # @param pool [Schema::Pool] holds every import that could be compiled
101
+ # @param missing [Array<String>] imports that could not be found or compiled
102
+ # @param direct [Boolean] whether the file was named on the command line
103
+ # (unused imports are only reported for those)
104
+ # @param options_fallback [#call] returns the bundled descriptor.proto's
105
+ # options message for a name, when the pool has no descriptor.proto
106
+ # @param protoc_cli [Boolean] apply what protoc's command line enables on
107
+ # top of a plain DescriptorPool: imports loaded from disk, unused import
108
+ # warnings, naming style, proto limits, symbol visibility and
109
+ # feature support checks
110
+ def initialize(ast, pool:, missing: [], direct: true, options_fallback: nil, source_info: false,
111
+ protoc_cli: true)
112
+ @ast = ast
113
+ @protoc_cli = protoc_cli
114
+ @source_info = source_info
115
+ @pool = pool
116
+ @missing = missing
117
+ @direct = direct
118
+ @options_fallback = options_fallback
119
+ @problems = []
120
+ @pending = []
121
+ @hints = {}
122
+ @locations = ast.locations
123
+ @descriptors = {}.compare_by_identity
124
+ end
125
+
126
+ # @return [OptionInterpreter, nil] set once options have been interpreted
127
+ attr_reader :interpreter
128
+
129
+ def protoc_cli?
130
+ @protoc_cli
131
+ end
132
+
133
+ # @return [Boolean] whether the file was named on the command line
134
+ def direct?
135
+ @direct
136
+ end
137
+
138
+ # @return [Array<Schema::File, nil>] the direct imports, nil where missing
139
+ attr_reader :dependency_files
140
+
141
+ # @return [Boolean] whether any error was reported
142
+ def errors?
143
+ @problems.any?(&:error?)
144
+ end
145
+
146
+ # Runs every phase up to building the {Schema::File}
147
+ # @return [Schema::File, nil]
148
+ def link
149
+ setup_edition
150
+ setup_symbols
151
+ build_file
152
+ cross_link
153
+ suggest_field_numbers
154
+ unless errors?
155
+ interpret_options
156
+ build_schema unless errors?
157
+ end
158
+ @file
159
+ end
160
+
161
+ # @return [Schema::Draft::File] the file as drafted so far
162
+ attr_reader :draft
163
+
164
+ # The descriptor drafted for an AST node: the file, a message, field,
165
+ # oneof, extension range, enum, enum value, service or method
166
+ # @return [Schema::Descriptor, nil]
167
+ def descriptor_of(node)
168
+ node.is_a?(AST::File) ? @draft.descriptor : @descriptors[node]
169
+ end
170
+
171
+ # Every element in protoc's VisitDescriptors order, as [node, kind]
172
+ def each_element(&block)
173
+ return enum_for(:each_element) unless block
174
+ yield @ast, :file
175
+ @ast.message_type.each { |m| visit_message(m, &block) }
176
+ @ast.enum_type.each { |e| visit_enum(e, &block) }
177
+ @ast.extension.each { |f| yield f, :field }
178
+ @ast.service.each do |s|
179
+ yield s, :service
180
+ s.rpcs.each { |m| yield m, :method }
181
+ end
182
+ end
183
+
184
+ # Records an error against a node's recorded position
185
+ def error(node, location, message, related: [])
186
+ @problems << Problem.new(severity: :error, node: node, location: location, message: message, related: related)
187
+ end
188
+
189
+ def warning(node, location, message, related: [])
190
+ @problems << Problem.new(severity: :warning, node: node, location: location, message: message,
191
+ related: related)
192
+ end
193
+
194
+ def import_error(name, message)
195
+ @problems << Problem.new(severity: :error, location: :import, message: message, import: name)
196
+ end
197
+
198
+ def import_warning(name, message)
199
+ @problems << Problem.new(severity: :warning, location: :import, message: message, import: name)
200
+ end
201
+
202
+ # @return [Integer]
203
+ def edition_number
204
+ Schema::Features::EDITIONS.fetch(@edition)
205
+ end
206
+
207
+ def legacy?
208
+ LEGACY_EDITIONS.include?(@edition)
209
+ end
210
+
211
+ private
212
+
213
+ def visit_message(message, &block)
214
+ yield message, :message
215
+ message.enum_type.each { |e| visit_enum(e, &block) }
216
+ message.oneof_decl.each { |o| yield o, :oneof }
217
+ message.field.each { |f| yield f, :field }
218
+ message.nested_type.each { |n| visit_message(n, &block) }
219
+ message.extension.each { |f| yield f, :field }
220
+ message.extension_range.each { |r| yield r, :extension_range }
221
+ end
222
+
223
+ def visit_enum(enum)
224
+ yield enum, :enum
225
+ enum.value.each { |v| yield v, :enum_value }
226
+ end
227
+
228
+ def setup_edition
229
+ @edition = if @ast.edition
230
+ @ast.edition
231
+ elsif @ast.syntax == "proto3"
232
+ :EDITION_PROTO3
233
+ else
234
+ :EDITION_PROTO2
235
+ end
236
+ number = edition_number
237
+ if number < Schema::Features::EDITIONS.fetch(MINIMUM_EDITION)
238
+ error(@ast, :editions, "Edition #{edition_label(@edition)} is earlier than the minimum supported edition " \
239
+ "#{edition_label(MINIMUM_EDITION)}")
240
+ elsif number > Schema::Features::EDITIONS.fetch(MAXIMUM_EDITION) && @edition != :EDITION_UNSTABLE
241
+ error(@ast, :editions, "Edition #{edition_label(@edition)} is later than the maximum supported edition " \
242
+ "#{edition_label(MAXIMUM_EDITION)}")
243
+ end
244
+ end
245
+
246
+ # How absl prints an Edition: the enum name without its prefix
247
+ def edition_label(edition)
248
+ edition.to_s.delete_prefix("EDITION_")
249
+ end
250
+
251
+ # AddImportError: protoc's command line loads imports on demand, a bare pool does not
252
+ def missing_import_message(name)
253
+ if @protoc_cli
254
+ "Import \"#{name}\" was not found or had errors."
255
+ else
256
+ "Import \"#{name}\" has not been loaded."
257
+ end
258
+ end
259
+
260
+ def setup_symbols
261
+ dependencies = []
262
+ unused = []
263
+ seen = {}
264
+ names = @ast.dependency + @ast.option_dependency
265
+ option_files = []
266
+ names.each_with_index do |name, i|
267
+ option = i >= @ast.dependency.size
268
+ import_error(name, "Import \"#{name}\" was listed twice.") if seen[name]
269
+ seen[name] = true
270
+ dep = @pool.file(name)
271
+ dep = nil if @missing.include?(name)
272
+ if dep.nil?
273
+ import_error(name, missing_import_message(name)) unless option
274
+ elsif @direct && dep.public_dependencies.empty? && !option
275
+ unused << dep
276
+ end
277
+ option ? option_files << dep : dependencies << dep
278
+ end
279
+ @ast.public_dependency.each { |i| unused.delete(dependencies[i]) }
280
+ @dependency_files = dependencies
281
+ @table = SymbolTable.new(@ast.name, @ast.package.to_s, @pool, dependencies, option_files, unused)
282
+ @unknown_option_imports = @ast.option_dependency.each_with_index.select { |_, i| option_files[i].nil? }.map(&:first)
283
+ end
284
+
285
+ def build_file
286
+ package = @ast.package.to_s
287
+ unless package.empty?
288
+ if (existing = @table.add_package(package))
289
+ error(@ast, :name, "\"#{existing.full_name}\" is already defined (as something other than a package) " \
290
+ "in file \"#{existing.file_name}\".")
291
+ end
292
+ end
293
+ @ast.full_name = @ast.name
294
+ @ast.path = []
295
+ @draft = Schema::File.draft(@ast.name, package: package, syntax: draft_syntax,
296
+ edition: legacy? ? nil : @edition, dependencies: @ast.dependency,
297
+ public_dependencies: @ast.public_dependency, weak_dependencies: @ast.weak_dependency,
298
+ option_dependencies: @ast.option_dependency)
299
+ @ast.message_type.each_with_index { |m, i| build_message(m, nil, i, [4, i], @draft) }
300
+ @ast.enum_type.each_with_index { |e, i| build_enum(e, nil, i, [5, i], @draft) }
301
+ @ast.service.each_with_index { |s, i| build_service(s, i) }
302
+ @ast.extension.each_with_index { |f, i| build_field(f, nil, i, [7, i], @draft, extension: true) }
303
+ pend(@ast, :file, "#{package}.dummy", [8])
304
+ @draft.__seal
305
+ end
306
+
307
+ # protoc's CopyTo writes no `syntax` for proto2
308
+ def draft_syntax
309
+ case @edition
310
+ when :EDITION_PROTO2 then nil
311
+ when :EDITION_PROTO3 then :proto3
312
+ else :editions
313
+ end
314
+ end
315
+
316
+ def scope_of(parent)
317
+ parent ? parent.full_name : @ast.package.to_s
318
+ end
319
+
320
+ def qualify(scope, name)
321
+ scope.empty? ? name : "#{scope}.#{name}"
322
+ end
323
+
324
+ def pend(node, kind, name_scope, path)
325
+ return unless node.options
326
+ @pending << Pending.new(node: node, kind: kind, name_scope: name_scope, path: path)
327
+ end
328
+
329
+ def add_symbol(kind, node, full_name)
330
+ existing = @table.add(kind, full_name, descriptor_of(node))
331
+ return true unless existing
332
+ if existing.file_name == @ast.name
333
+ first = existing.object && @descriptors.key(existing.object)
334
+ related = first ? [[first, :name, "first defined here"]] : []
335
+ dot = full_name.rindex(".")
336
+ if dot
337
+ error(node, :name, "\"#{full_name[(dot + 1)..]}\" is already defined in \"#{full_name[0, dot]}\".",
338
+ related: related)
339
+ else
340
+ error(node, :name, "\"#{full_name}\" is already defined.", related: related)
341
+ end
342
+ else
343
+ error(node, :name, "\"#{full_name}\" is already defined in file \"#{existing.file_name}\".")
344
+ end
345
+ false
346
+ end
347
+
348
+ def hint(message, reason, location, from = 0, to = 1)
349
+ fit = ->(v) { v.clamp(0, MAX_FIELD_NUMBER) }
350
+ h = (@hints[message] ||= {count: 0, reason: nil, location: nil})
351
+ h[:count] = fit.call(h[:count] + fit.call(fit.call(to) - fit.call(from)))
352
+ return if h[:reason]
353
+ h[:reason] = reason
354
+ h[:location] = location
355
+ end
356
+
357
+ def build_message(message, parent, index, path, scope)
358
+ message.parent = parent
359
+ message.index = index
360
+ message.path = path
361
+ message.full_name = qualify(scope_of(parent), message.name)
362
+ full_name = message.full_name
363
+ draft = scope.message(message.name, visibility: message.visibility)
364
+ @descriptors[message] = draft.descriptor
365
+
366
+ oneofs = message.oneof_decl.each_with_index.map do |oneof, i|
367
+ oneof.parent = message
368
+ oneof.index = i
369
+ oneof.path = path + [8, i]
370
+ oneof.full_name = "#{full_name}.#{oneof.name}"
371
+ @descriptors[oneof] = draft.oneof(oneof.name)
372
+ pend(oneof, :oneof, oneof.full_name, oneof.path + [2])
373
+ add_symbol(:oneof, oneof, oneof.full_name)
374
+ @descriptors[oneof]
375
+ end
376
+ message.field.each_with_index do |f, i|
377
+ build_field(f, message, i, path + [2, i], draft, extension: false, oneofs: oneofs)
378
+ end
379
+ message.enum_type.each_with_index { |e, i| build_enum(e, message, i, path + [4, i], draft) }
380
+ message.extension_range.each_with_index do |range, i|
381
+ range.parent = message
382
+ range.index = i
383
+ range.path = path + [5, i]
384
+ range.full_name = full_name
385
+ @descriptors[range] = draft.extension_range(range.start..(range.end - 1))
386
+ if range.start <= 0
387
+ hint(message, range, :number, range.start, range.end)
388
+ error(range, :number, "Extension numbers must be positive integers.")
389
+ end
390
+ if range.start >= range.end
391
+ error(range, :number, "Extension range end number must be greater than start number.")
392
+ end
393
+ pend(range, :extension_range, full_name, range.path + [3])
394
+ end
395
+ message.extension.each_with_index { |f, i| build_field(f, message, i, path + [6, i], draft, extension: true) }
396
+ message.reserved_range.each do |range|
397
+ draft.reserved(range.start..(range.end - 1))
398
+ if range.start <= 0
399
+ hint(message, range, :number, range.start, range.end)
400
+ error(range, :number, "Reserved numbers must be positive integers.")
401
+ end
402
+ if range.start >= range.end
403
+ error(range, :number, "Reserved range end number must be greater than start number.")
404
+ end
405
+ end
406
+ draft.reserved(*message.reserved_name)
407
+ pend(message, :message, full_name, path + [7])
408
+ message.nested_type.each_with_index { |m, i| build_message(m, message, i, path + [3, i], draft) }
409
+ add_symbol(:message, message, full_name)
410
+
411
+ ranges = message.reserved_range
412
+ ranges.each_with_index do |r1, i|
413
+ ranges[(i + 1)..].each do |r2|
414
+ next unless r1.end > r2.start && r2.end > r1.start
415
+ error(r1, :number, "Reserved range #{r2.start} to #{r2.end - 1} overlaps with already-defined range " \
416
+ "#{r1.start} to #{r1.end - 1}.")
417
+ end
418
+ end
419
+ reserved_names = {}
420
+ message.reserved_name.each do |name|
421
+ if reserved_names[name]
422
+ error(message, :name, "Field name \"#{name}\" is reserved multiple times.")
423
+ end
424
+ reserved_names[name] = true
425
+ end
426
+ message.field.each do |field|
427
+ message.extension_range.each do |range|
428
+ next unless range.start <= field.number && field.number < range.end
429
+ hint(message, range, :number)
430
+ error(range, :number, "Extension range #{range.start} to #{range.end - 1} includes field " \
431
+ "\"#{field.name}\" (#{field.number}).", related: [[field, :number, "`#{field.name}` uses it"]])
432
+ end
433
+ ranges.each do |range|
434
+ next unless range.start <= field.number && field.number < range.end
435
+ hint(message, range, :number)
436
+ error(range, :number, "Field \"#{field.name}\" uses reserved number #{field.number}.",
437
+ related: [[field, :number, "`#{field.name}` uses it"]])
438
+ end
439
+ error(field, :name, "Field name \"#{field.name}\" is reserved.") if reserved_names[field.name]
440
+ end
441
+ message.extension_range.each_with_index do |r1, i|
442
+ ranges.each do |r2|
443
+ next unless r1.end > r2.start && r2.end > r1.start
444
+ error(r1, :number, "Extension range #{r1.start} to #{r1.end - 1} overlaps with reserved range " \
445
+ "#{r2.start} to #{r2.end - 1}.")
446
+ end
447
+ message.extension_range[(i + 1)..].each do |r2|
448
+ next unless r1.end > r2.start && r2.end > r1.start
449
+ error(r1, :number, "Extension range #{r2.start} to #{r2.end - 1} overlaps with already-defined range " \
450
+ "#{r1.start} to #{r1.end - 1}.")
451
+ end
452
+ end
453
+ end
454
+
455
+ def build_field(field, parent, index, path, scope, extension:, oneofs: nil)
456
+ field.parent = parent
457
+ field.index = index
458
+ field.path = path
459
+ field.extension = extension
460
+ field.full_name = qualify(scope_of(parent), field.name)
461
+ type = field.type && Schema::Field::TYPES.fetch(field.type)
462
+ kwargs = {label: Schema::Field::LABELS.fetch(field.label || :LABEL_OPTIONAL),
463
+ default: field.default_value && default_text(field), json_name: field.json_name,
464
+ optional: field.proto3_optional ? true : false}
465
+ @descriptors[field] = if extension
466
+ scope.extension(field.name, field.number, type, field.type_name, extendee: field.extendee, **kwargs)
467
+ else
468
+ oneof = field.oneof_index && oneofs[field.oneof_index]
469
+ scope.field(field.name, field.number, type, field.type_name, oneof: oneof, **kwargs)
470
+ end
471
+ if field.label == :LABEL_REQUIRED && extension
472
+ error(field, :type, "The extension #{field.full_name} cannot be required.")
473
+ end
474
+ if field.default_value && field.label == :LABEL_REPEATED
475
+ error(field, :default_value, "Repeated fields can't have default values.")
476
+ end
477
+ if field.type && field.default_value
478
+ case field.type
479
+ when :TYPE_BOOL
480
+ unless %w[true false].include?(field.default_value)
481
+ error(field, :default_value, "Boolean default must be true or false.")
482
+ end
483
+ when :TYPE_BYTES
484
+ error(field, :default_value, "Invalid escaping in default value.") unless c_unescape(field.default_value)
485
+ when :TYPE_MESSAGE, :TYPE_GROUP
486
+ error(field, :default_value, "Messages can't have default values.")
487
+ end
488
+ end
489
+ if field.number <= 0
490
+ hint(parent, field, :number)
491
+ error(field, :number, "Field numbers must be positive integers.")
492
+ elsif !extension && field.number > MAX_FIELD_NUMBER
493
+ hint(parent, field, :number)
494
+ error(field, :number, "Field numbers cannot be greater than #{MAX_FIELD_NUMBER}.")
495
+ end
496
+ pend(field, :field, field.full_name, path + [8])
497
+ add_symbol(:field, field, field.full_name)
498
+ end
499
+
500
+ def build_enum(enum, parent, index, path, scope_draft)
501
+ enum.parent = parent
502
+ enum.index = index
503
+ enum.path = path
504
+ scope = scope_of(parent)
505
+ enum.full_name = qualify(scope, enum.name)
506
+ draft = scope_draft.enum(enum.name, visibility: enum.visibility)
507
+ @descriptors[enum] = draft.descriptor
508
+ error(enum, :name, "Enums must contain at least one value.") if enum.value.empty?
509
+ inner = {}
510
+ enum.value.each_with_index do |value, i|
511
+ value.parent = enum
512
+ value.index = i
513
+ value.path = path + [2, i]
514
+ value.full_name = qualify(scope, value.name)
515
+ @descriptors[value] = draft.value(value.name, value.number)
516
+ pend(value, :enum_value, value.full_name, value.path + [3])
517
+ added_outer = add_symbol(:enum_value, value, value.full_name)
518
+ added_inner = !inner.key?(value.name)
519
+ inner[value.name] = true
520
+ next unless added_inner && !added_outer
521
+ outer = parent ? parent.full_name : @ast.package.to_s
522
+ outer = outer.empty? ? "the global scope" : "\"#{outer}\""
523
+ error(value, :name, "Note that enum values use C++ scoping rules, meaning that enum values are siblings " \
524
+ "of their type, not children of it. Therefore, \"#{value.name}\" must be unique within #{outer}, not " \
525
+ "just within \"#{enum.name}\".")
526
+ end
527
+ pend(enum, :enum, enum.full_name, path + [3])
528
+ add_symbol(:enum, enum, enum.full_name)
529
+
530
+ ranges = enum.reserved_range
531
+ ranges.each { |range| draft.reserved(range.start..range.end) }
532
+ draft.reserved(*enum.reserved_name)
533
+ ranges.each do |range|
534
+ if range.start > range.end
535
+ error(range, :number, "Reserved range end number must be greater than start number.")
536
+ end
537
+ end
538
+ ranges.each_with_index do |r1, i|
539
+ ranges[(i + 1)..].each do |r2|
540
+ next unless r1.end >= r2.start && r2.end >= r1.start
541
+ error(r1, :number, "Reserved range #{r2.start} to #{r2.end} overlaps with already-defined range " \
542
+ "#{r1.start} to #{r1.end}.")
543
+ end
544
+ end
545
+ reserved_names = {}
546
+ enum.reserved_name.each do |name|
547
+ error(enum, :name, "Enum value \"#{name}\" is reserved multiple times.") if reserved_names[name]
548
+ reserved_names[name] = true
549
+ end
550
+ enum.value.each do |value|
551
+ ranges.each do |range|
552
+ next unless value.number.between?(range.start, range.end)
553
+ error(range, :number, "Enum value \"#{value.name}\" uses reserved number #{value.number}.",
554
+ related: [[value, :number, "`#{value.name}` uses it"]])
555
+ end
556
+ error(value, :name, "Enum value \"#{value.name}\" is reserved.") if reserved_names[value.name]
557
+ end
558
+ end
559
+
560
+ def build_service(service, index)
561
+ service.index = index
562
+ service.path = [6, index]
563
+ service.full_name = qualify(@ast.package.to_s, service.name)
564
+ draft = @draft.service(service.name)
565
+ @descriptors[service] = draft.descriptor
566
+ service.rpcs.each_with_index do |method, i|
567
+ method.parent = service
568
+ method.index = i
569
+ method.path = service.path + [2, i]
570
+ method.full_name = "#{service.full_name}.#{method.name}"
571
+ @descriptors[method] = draft.rpc(method.name, method.input_type, method.output_type,
572
+ client_streaming: method.client_streaming, server_streaming: method.server_streaming)
573
+ pend(method, :method, method.full_name, method.path + [4])
574
+ add_symbol(:method, method, method.full_name)
575
+ end
576
+ pend(service, :service, service.full_name, service.path + [3])
577
+ add_symbol(:service, service, service.full_name)
578
+ end
579
+
580
+ def cross_link
581
+ @fields_by_number = {}
582
+ @ast.message_type.each { |m| cross_link_message(m) }
583
+ @ast.extension.each { |f| cross_link_field(f) }
584
+ @ast.service.each { |s| s.rpcs.each { |m| cross_link_method(m) } }
585
+ end
586
+
587
+ def cross_link_message(message)
588
+ message.nested_type.each { |m| cross_link_message(m) }
589
+ message.field.each { |f| cross_link_field(f) }
590
+ message.extension.each { |f| cross_link_field(f) }
591
+
592
+ counts = Hash.new(0)
593
+ message.field.each_with_index do |field, i|
594
+ next unless field.oneof_index
595
+ oneof = message.oneof_decl[field.oneof_index]
596
+ next unless oneof
597
+ if counts[oneof] > 0 && message.field[i - 1].oneof_index != field.oneof_index
598
+ prev = message.field[i - 1]
599
+ error(prev, :type, "Fields in the same oneof must be defined consecutively. \"#{prev.name}\" cannot be " \
600
+ "defined before the completion of the \"#{oneof.name}\" oneof definition.")
601
+ end
602
+ counts[oneof] += 1
603
+ end
604
+ message.oneof_decl.each do |oneof|
605
+ error(oneof, :name, "Oneof must have at least one field.") if counts[oneof] == 0
606
+ end
607
+ end
608
+
609
+ def not_defined(node, location, name)
610
+ @table.not_defined_messages(name).each { |m| error(node, location, m) }
611
+ end
612
+
613
+ def cross_link_field(field)
614
+ holder = field.parent
615
+ if field.extension
616
+ extendee = @table.lookup(field.extendee, field.full_name)
617
+ if extendee.nil?
618
+ not_defined(field, :extendee, field.extendee)
619
+ return
620
+ elsif extendee.kind != :message
621
+ error(field, :extendee, "\"#{field.extendee}\" is not a message type.")
622
+ return
623
+ end
624
+ return unless visible?(extendee, field, :extendee, "target of extend")
625
+ @draft.__resolve_extendee(descriptor_of(field), extendee.object)
626
+ holder = extendee
627
+ unless extension_number?(extendee, field.number)
628
+ error(field, :number, "\"#{extendee.full_name}\" does not declare #{field.number} as an extension number.")
629
+ end
630
+ end
631
+
632
+ if field.oneof_index && field.label != :LABEL_OPTIONAL
633
+ error(field, :name, "Fields of oneofs must themselves have label LABEL_OPTIONAL.")
634
+ end
635
+
636
+ if field.type_name
637
+ type = @table.lookup(field.type_name, field.full_name, types_only: true)
638
+ if type.nil?
639
+ not_defined(field, :type, field.type_name)
640
+ return
641
+ end
642
+ return unless visible?(type, field, :type)
643
+ if field.type.nil? && !type.type?
644
+ error(field, :type, "\"#{field.type_name}\" is not a type.")
645
+ return
646
+ end
647
+ kind = if field.type
648
+ %i[TYPE_MESSAGE TYPE_GROUP].include?(field.type) ? :message : :enum
649
+ else
650
+ type.kind
651
+ end
652
+ if kind == :message
653
+ if type.kind != :message
654
+ error(field, :type, "\"#{field.type_name}\" is not a message type.")
655
+ return
656
+ end
657
+ @draft.__resolve_type(descriptor_of(field), type.object)
658
+ error(field, :default_value, "Messages can't have default values.") if field.default_value
659
+ else
660
+ if type.kind != :enum
661
+ error(field, :type, "\"#{field.type_name}\" is not an enum type.")
662
+ return
663
+ end
664
+ @draft.__resolve_type(descriptor_of(field), type.object)
665
+ if field.default_value
666
+ if !Lexer.identifier?(field.default_value)
667
+ error(field, :default_value, "Default value for an enum field must be an identifier.")
668
+ elsif !enum_value_names(type).include?(field.default_value)
669
+ error(field, :default_value, "Enum type \"#{type.full_name}\" has no value named " \
670
+ "\"#{field.default_value}\".")
671
+ end
672
+ end
673
+ end
674
+ end
675
+
676
+ key = [holder_key(holder), field.number]
677
+ if (conflict = @fields_by_number[key])
678
+ holder_name = holder ? holder.full_name : "unknown"
679
+ if field.extension
680
+ error(field, :number, "Extension number #{field.number} has already been used in \"#{holder_name}\" by " \
681
+ "extension \"#{conflict.full_name}\".", related: [[conflict, :number, "first used by `#{conflict.name}`"]])
682
+ else
683
+ error(field, :number, "Field number #{field.number} has already been used in \"#{holder_name}\" by field " \
684
+ "\"#{conflict.name}\". #{next_available_number(field.parent)}.",
685
+ related: [[conflict, :number, "first used by `#{conflict.name}`"]])
686
+ end
687
+ else
688
+ @fields_by_number[key] = field
689
+ if field.extension && (other = @pool.find_extension(holder.full_name, field.number))
690
+ warning(field, :number, "Extension number #{field.number} has already been used in " \
691
+ "\"#{holder.full_name}\" by extension \"#{other.full_name}\" defined in #{other.file.name}.")
692
+ end
693
+ end
694
+ end
695
+
696
+ def holder_key(holder)
697
+ holder&.full_name
698
+ end
699
+
700
+ def next_available_number(message)
701
+ used = message.field.map { |f| [f.number, f.number] }
702
+ message.extension_range.each { |r| used << [r.start, r.end - 1] }
703
+ message.reserved_range.each { |r| used << [r.start, r.end - 1] }
704
+ proposed = 1
705
+ used.uniq.sort.each do |first, last|
706
+ break unless proposed.between?(first, last)
707
+ proposed = last + 1
708
+ end
709
+ (proposed <= MAX_FIELD_NUMBER) ? "Next available field number is #{proposed}" : "There are no available field numbers"
710
+ end
711
+
712
+ def extension_number?(symbol, number)
713
+ symbol.object.extension_number?(number)
714
+ end
715
+
716
+ def enum_value_names(symbol)
717
+ symbol.object.values.map(&:name)
718
+ end
719
+
720
+ # Symbol::IsVisibleFrom: edition 2024 `local` types are invisible outside their file
721
+ def visible?(symbol, node, location, usage = "")
722
+ return true unless @protoc_cli
723
+ return true unless symbol.type? && !symbol.local?
724
+ descriptor = symbol.object
725
+ visibility = descriptor.visibility
726
+ if visibility.nil?
727
+ visibility = case descriptor.features.default_symbol_visibility
728
+ when :EXPORT_ALL then :export
729
+ when :EXPORT_TOP_LEVEL then descriptor.containing_message ? :local : :export
730
+ when :LOCAL_ALL, :STRICT then :local
731
+ else :export
732
+ end
733
+ explicit = false
734
+ else
735
+ explicit = true
736
+ end
737
+ return true if visibility == :export
738
+ reason = if explicit && descriptor.visibility == :local
739
+ "It is explicitly marked 'local'"
740
+ else
741
+ "It defaulted to local from file-level 'option features.default_symbol_visibility = " \
742
+ "'#{descriptor.features.default_symbol_visibility}';"
743
+ end
744
+ error(node, location, "Symbol \"#{descriptor.full_name}\", defined in \"#{descriptor.file.name}\" #{usage} " \
745
+ "is not visible from \"#{@ast.name}\". #{reason} and cannot be accessed outside its own file")
746
+ false
747
+ end
748
+
749
+ def cross_link_method(method)
750
+ input = @table.lookup(method.input_type, method.full_name)
751
+ if input.nil?
752
+ not_defined(method, :input_type, method.input_type)
753
+ elsif input.kind != :message
754
+ error(method, :input_type, "\"#{method.input_type}\" is not a message type.")
755
+ elsif visible?(input, method, :input_type)
756
+ @draft.__resolve_rpc(descriptor_of(method), input: input.object)
757
+ end
758
+ output = @table.lookup(method.output_type, method.full_name)
759
+ if output.nil?
760
+ not_defined(method, :output_type, method.output_type)
761
+ elsif output.kind != :message
762
+ error(method, :output_type, "\"#{method.output_type}\" is not a message type.")
763
+ elsif visible?(output, method, :output_type)
764
+ @draft.__resolve_rpc(descriptor_of(method), output: output.object)
765
+ end
766
+ end
767
+
768
+ def suggest_field_numbers
769
+ @ast.message_type.each do |message|
770
+ h = @hints[message]
771
+ next unless h
772
+ count = [3, h[:count]].min
773
+ next if count <= 0
774
+ used = []
775
+ add_ordinal = lambda do |n|
776
+ next if n <= 0 || n > MAX_FIELD_NUMBER
777
+ if !used.empty? && n == used.last[1]
778
+ used.last[1] = n + 1
779
+ else
780
+ used << [n, n + 1]
781
+ end
782
+ end
783
+ add_range = lambda do |from, to|
784
+ from = from.clamp(0, MAX_FIELD_NUMBER + 1)
785
+ to = to.clamp(0, MAX_FIELD_NUMBER + 1)
786
+ used << [from, to] if from < to
787
+ end
788
+ message.field.each { |f| add_ordinal.call(f.number) }
789
+ message.extension.each { |f| add_ordinal.call(f.number) }
790
+ message.reserved_range.each { |r| add_range.call(r.start, r.end) }
791
+ message.extension_range.each { |r| add_range.call(r.start, r.end) }
792
+ used << [MAX_FIELD_NUMBER, MAX_FIELD_NUMBER + 1]
793
+ used << [19_000, 19_999]
794
+ used.sort!
795
+ current = 1
796
+ ids = []
797
+ used.each do |from, to|
798
+ while current < from && count > 0
799
+ ids << current
800
+ current += 1
801
+ count -= 1
802
+ end
803
+ break if count == 0
804
+ current = [current, to].max
805
+ end
806
+ error(h[:reason], h[:location], "Suggested field numbers for #{message.full_name}: #{ids.join(", ")}")
807
+ end
808
+ end
809
+
810
+ def interpret_options
811
+ unless @pending.empty?
812
+ @unknown_option_imports.each { |name| import_error(name, missing_import_message(name)) }
813
+ end
814
+ return if errors?
815
+ # option types declared in this file are read from the draft itself,
816
+ # linked and with its structural features
817
+ @draft.resolve(@pool)
818
+ @interpreter = OptionInterpreter.new(self, @pool, @options_fallback)
819
+ options = @interpreter.run(@pending) { |features| check_features(features) }
820
+ options.each { |node, value| @draft.set_options(descriptor_of(node), value) }
821
+ @locations = SourceInfo.rewrite_option_paths(@ast.locations, @interpreter.interpreted_paths)
822
+ rescue Schema::Error => e
823
+ error(nil, nil, e.message)
824
+ end
825
+
826
+ FEATURE_UNKNOWNS = {
827
+ field_presence: :FIELD_PRESENCE_UNKNOWN, enum_type: :ENUM_TYPE_UNKNOWN,
828
+ repeated_field_encoding: :REPEATED_FIELD_ENCODING_UNKNOWN, utf8_validation: :UTF8_VALIDATION_UNKNOWN,
829
+ message_encoding: :MESSAGE_ENCODING_UNKNOWN, json_format: :JSON_FORMAT_UNKNOWN,
830
+ enforce_naming_style: :ENFORCE_NAMING_STYLE_UNKNOWN,
831
+ default_symbol_visibility: :DEFAULT_SYMBOL_VISIBILITY_UNKNOWN, enforce_proto_limits: :PROTO_LIMITS_UNKNOWN
832
+ }.freeze
833
+
834
+ # ResolveFeaturesImpl's checks, in VisitDescriptors order, once the
835
+ # non-extension options (`features` among them) are known
836
+ # @param features [Hash{AST::Node => Schema::MessageValue}] the `features` each element sets
837
+ def check_features(features)
838
+ each_element do |node, kind|
839
+ raw = features[node]
840
+ next if raw.nil?
841
+ location = (kind == :file) ? :editions : :name
842
+ if legacy?
843
+ error(node, location, "Features are only valid under editions.")
844
+ next
845
+ end
846
+ FEATURE_UNKNOWNS.each do |name, unknown|
847
+ next unless raw[name] == unknown
848
+ error(node, location, "Feature field `#{name}` must resolve to a known value, found #{unknown}")
849
+ break
850
+ end
851
+ end
852
+ end
853
+
854
+ def build_schema
855
+ if @source_info
856
+ @draft.source_info = @locations.map(&:to_schema)
857
+ end
858
+ @file = @draft.resolve(@pool).freeze!
859
+ rescue Schema::Error => e
860
+ error(nil, nil, e.message)
861
+ @file = nil
862
+ end
863
+
864
+ # FieldDescriptor::DefaultValueAsString over the parsed default
865
+ def default_text(field)
866
+ text = field.default_value
867
+ case field.type
868
+ when :TYPE_FLOAT
869
+ Compiler.simple_ftoa(parse_float_default(text))
870
+ when :TYPE_DOUBLE
871
+ Compiler.simple_dtoa(parse_float_default(text))
872
+ else
873
+ text
874
+ end
875
+ end
876
+
877
+ def parse_float_default(text)
878
+ case text
879
+ when "inf" then Float::INFINITY
880
+ when "-inf" then -Float::INFINITY
881
+ when "nan", "-nan" then Float::NAN
882
+ else Float(text)
883
+ end
884
+ end
885
+
886
+ # absl::CUnescape, as far as validity goes
887
+ def c_unescape(text)
888
+ Schema::DefaultValue.unescape_bytes(text)
889
+ rescue Schema::Error
890
+ nil
891
+ end
892
+ end
893
+ end
894
+ end