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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +60 -0
- data/LICENSE.txt +21 -0
- data/README.md +647 -0
- data/THIRD_PARTY_NOTICES.md +61 -0
- data/exe/protoc-gen-protostar +7 -0
- data/exe/protostar +7 -0
- data/lib/protostar/builder/generator.rb +71 -0
- data/lib/protostar/builder/sink.rb +35 -0
- data/lib/protostar/builder.rb +965 -0
- data/lib/protostar/cli.rb +267 -0
- data/lib/protostar/codec/decode_generator.rb +199 -0
- data/lib/protostar/codec/decoder.rb +296 -0
- data/lib/protostar/codec/encode_generator.rb +224 -0
- data/lib/protostar/codec/encoder.rb +121 -0
- data/lib/protostar/codec/sizer.rb +91 -0
- data/lib/protostar/codec.rb +15 -0
- data/lib/protostar/compiler/ast.rb +193 -0
- data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
- data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
- data/lib/protostar/compiler/emitters/grpc.rb +175 -0
- data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
- data/lib/protostar/compiler/emitters/rbs.rb +394 -0
- data/lib/protostar/compiler/emitters/ruby.rb +503 -0
- data/lib/protostar/compiler/emitters.rb +268 -0
- data/lib/protostar/compiler/include/LICENSE +32 -0
- data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
- data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
- data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
- data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
- data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
- data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
- data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
- data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
- data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
- data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
- data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
- data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
- data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
- data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
- data/lib/protostar/compiler/lexer.rb +659 -0
- data/lib/protostar/compiler/linker.rb +894 -0
- data/lib/protostar/compiler/option_interpreter.rb +363 -0
- data/lib/protostar/compiler/parser.rb +1696 -0
- data/lib/protostar/compiler/retention.rb +104 -0
- data/lib/protostar/compiler/run.rb +184 -0
- data/lib/protostar/compiler/source_info.rb +161 -0
- data/lib/protostar/compiler/symbol_table.rb +223 -0
- data/lib/protostar/compiler/text_format.rb +340 -0
- data/lib/protostar/compiler/validator.rb +944 -0
- data/lib/protostar/compiler.rb +304 -0
- data/lib/protostar/diagnostics/codes.rb +1307 -0
- data/lib/protostar/diagnostics/formats.rb +64 -0
- data/lib/protostar/diagnostics/renderer.rb +240 -0
- data/lib/protostar/diagnostics/source.rb +153 -0
- data/lib/protostar/diagnostics.rb +117 -0
- data/lib/protostar/dsl.rb +367 -0
- data/lib/protostar/enum.rb +70 -0
- data/lib/protostar/errors.rb +23 -0
- data/lib/protostar/google/protobuf/any_pb.rb +90 -0
- data/lib/protostar/google/protobuf/api_pb.rb +199 -0
- data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
- data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
- data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
- data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
- data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
- data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
- data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
- data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
- data/lib/protostar/google/protobuf/type_pb.rb +202 -0
- data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
- data/lib/protostar/google_protobuf_shim.rb +24 -0
- data/lib/protostar/json/encoder.rb +340 -0
- data/lib/protostar/json/errors.rb +34 -0
- data/lib/protostar/json/parser.rb +437 -0
- data/lib/protostar/json/path.rb +39 -0
- data/lib/protostar/json.rb +95 -0
- data/lib/protostar/lint/command.rb +197 -0
- data/lib/protostar/lint/config.rb +98 -0
- data/lib/protostar/lint/context.rb +126 -0
- data/lib/protostar/lint/directives.rb +56 -0
- data/lib/protostar/lint/fixer.rb +165 -0
- data/lib/protostar/lint/naming.rb +54 -0
- data/lib/protostar/lint/rule.rb +120 -0
- data/lib/protostar/lint/rules/comment_enum.rb +31 -0
- data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
- data/lib/protostar/lint/rules/comment_field.rb +33 -0
- data/lib/protostar/lint/rules/comment_message.rb +31 -0
- data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
- data/lib/protostar/lint/rules/comment_service.rb +31 -0
- data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
- data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
- data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
- data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
- data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
- data/lib/protostar/lint/rules/field_not_required.rb +39 -0
- data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
- data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
- data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/import_no_public.rb +29 -0
- data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
- data/lib/protostar/lint/rules/import_used.rb +33 -0
- data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
- data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
- data/lib/protostar/lint/rules/no_groups.rb +34 -0
- data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/package_defined.rb +28 -0
- data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
- data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
- data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
- data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
- data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
- data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
- data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
- data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/service_suffix.rb +28 -0
- data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
- data/lib/protostar/lint/runner.rb +135 -0
- data/lib/protostar/lint.rb +122 -0
- data/lib/protostar/message/accessors.rb +86 -0
- data/lib/protostar/message/coercion.rb +171 -0
- data/lib/protostar/message/field_info.rb +145 -0
- data/lib/protostar/message/reflection.rb +149 -0
- data/lib/protostar/message.rb +572 -0
- data/lib/protostar/plugin.rb +94 -0
- data/lib/protostar/schema/default_value.rb +113 -0
- data/lib/protostar/schema/descriptor.rb +41 -0
- data/lib/protostar/schema/draft/copy.rb +88 -0
- data/lib/protostar/schema/draft/resolution.rb +177 -0
- data/lib/protostar/schema/draft.rb +779 -0
- data/lib/protostar/schema/enum.rb +106 -0
- data/lib/protostar/schema/extension_range.rb +37 -0
- data/lib/protostar/schema/features.rb +153 -0
- data/lib/protostar/schema/field.rb +201 -0
- data/lib/protostar/schema/file.rb +217 -0
- data/lib/protostar/schema/freezer.rb +61 -0
- data/lib/protostar/schema/message.rb +104 -0
- data/lib/protostar/schema/oneof.rb +38 -0
- data/lib/protostar/schema/options.rb +367 -0
- data/lib/protostar/schema/pool.rb +179 -0
- data/lib/protostar/schema/proto.rb +576 -0
- data/lib/protostar/schema/resolver.rb +118 -0
- data/lib/protostar/schema/service.rb +89 -0
- data/lib/protostar/schema/source_info.rb +53 -0
- data/lib/protostar/schema.rb +65 -0
- data/lib/protostar/short_names/any.rb +7 -0
- data/lib/protostar/short_names/duration.rb +7 -0
- data/lib/protostar/short_names/empty.rb +7 -0
- data/lib/protostar/short_names/field_mask.rb +7 -0
- data/lib/protostar/short_names/struct.rb +9 -0
- data/lib/protostar/short_names/timestamp.rb +7 -0
- data/lib/protostar/short_names/wrappers.rb +15 -0
- data/lib/protostar/text/extensions.rb +114 -0
- data/lib/protostar/text/parser.rb +439 -0
- data/lib/protostar/text/printer.rb +304 -0
- data/lib/protostar/text/tokenizer.rb +116 -0
- data/lib/protostar/text.rb +67 -0
- data/lib/protostar/types.rb +152 -0
- data/lib/protostar/unknown_field.rb +58 -0
- data/lib/protostar/version.rb +5 -0
- data/lib/protostar/view/generator.rb +298 -0
- data/lib/protostar/view/map.rb +194 -0
- data/lib/protostar/view/repeated.rb +143 -0
- data/lib/protostar/view/scan.rb +183 -0
- data/lib/protostar/view.rb +283 -0
- data/lib/protostar/well_known/any.rb +52 -0
- data/lib/protostar/well_known/duration.rb +53 -0
- data/lib/protostar/well_known/list_value.rb +63 -0
- data/lib/protostar/well_known/struct.rb +55 -0
- data/lib/protostar/well_known/timestamp.rb +64 -0
- data/lib/protostar/well_known/value.rb +60 -0
- data/lib/protostar/well_known.rb +38 -0
- data/lib/protostar/wire/io_source.rb +40 -0
- data/lib/protostar/wire/reader.rb +254 -0
- data/lib/protostar/wire/writer.rb +201 -0
- data/lib/protostar/wire.rb +96 -0
- data/lib/protostar.rb +95 -0
- data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
- data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
- data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
- data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
- data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
- data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
- data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
- data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
- data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
- data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
- data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
- data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
- data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
- data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
- data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
- data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
- data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
- data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
- data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
- data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
- data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
- data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
- data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
- data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
- data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
- data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
- data/sig/protostar/lint.rbs +216 -0
- data/sig/protostar.rbs +326 -0
- 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
|