Use negative UninterpretedOption field tags for interpreted option locations.

This allows us to extend improved SourceCodeInfo support to dot-notation options without breaking backwards compatibility. Dot-notation options like `(my_opt).a = 123` already emit SourceCodeInfo location in a form of `[..., 10101, 1]` where 1 is the tag of field `a`. That location spans the whole option. But these pre-existing locations are limited as they don't provide granular information about each part of the option (e.g. location of `a`) and don't provide location for value too.

Using negative values allows us to create non-overlapping set of locations. For example in the example above we will emit (in future CL):

[..., 10101] - location of the whole `(my_opt).a = 123` option
[..., 10101, 1] - legacy duplicate location of the whole `(my_opt).a = 123` option
[..., 10101, -2] - location of `(my_opt)` part
[..., 10101, -8, 1, -2] - location of `a` part
[..., 10101, -8, 1, -4] - location of `123` part

PiperOrigin-RevId: 958626527
This commit is contained in:
Mikita Belahlazau 2026-08-03 16:03:01 -07:00 committed by Copybara-Service
parent 6836552e25
commit 9a0059ff0e
2 changed files with 23 additions and 16 deletions

View file

@ -1406,32 +1406,39 @@ message SourceCodeInfo {
// .options() // 7
// .my_opt() // 10101
//
// Sub-locations corresponding to the interpreted option's corresponding
// `UninterpretedOption` are also appended to the interpreted option, which
// deviates from the actual FileDescriptorProto path. E.g.:
// [ 4, 3, 7, 10101, 2 ]
// refers to the option name `(my_opt)`, and:
// [ 4, 3, 7, 10101, 7 ]
// refers to the "foo" string value of the option.
// Option parts, e.g. name and value are also appended using field numbers
// from `UninterpretedOption`, which deviates from the actual
// FileDescriptorProto path and uses negative values.
// E.g., for `(my_opt) = "foo"` the name `(my_opt)` is:
// [ 4, 3, 7, 10101, -2 ]
// where -2 is negative of `UninterpretedOption.name`.
// The value "foo" is:
// [ 4, 3, 7, 10101, -7 ]
// where -7 is the negative of `UninterpretedOption.string_value`.
//
// For complex options (e.g., "(my_opt) = {a: 100}"), the path
// will include UninterpretedOption.aggregate_value (field number 8) as a
// will include -UninterpretedOption.aggregate_value (field number -8) as a
// marker for each level of nesting.
//
// For example, given:
// option (my_opt) = {a: 100};
//
// The path for the `a` identifier would look like:
// [ 4, 3, 7, 10101, 8, 1, 2 ]
// [ 4, 3, 7, 10101, -8, 1, -2 ]
//
// And for the value 100:
// [ 4, 3, 7, 10101, 8, 1, 4 ]
// [ 4, 3, 7, 10101, -8, 1, -4 ]
//
// Where:
// 8: UninterpretedOption.aggregate_value marker
// 1: The field number of "a" inside "my_opt"
// 2: UninterpretedOption.name
// 4: UninterpretedOption.positive_int_value
// -8: UninterpretedOption.aggregate_value marker
// 1: The field number of "a" inside "my_opt"
// -2: UninterpretedOption.name
// -4: UninterpretedOption.positive_int_value
//
// We use negative values for UninterpretedOption due to keep it backward
// compatible with pre-existing undocumented behavior where options using
// dot-notation (e.g. `(my_opt).a = 100`) also produce a path like
// [ 4, 3, 7, 10101, 1] that spans the entire option.
repeated int32 path = 1 [packed = true];
// Always has exactly three or four elements: start line, start column,

View file

@ -1009,7 +1009,7 @@ bool OptionInterpreter::SetAggregateOption(const FieldDescriptor* option_field,
UninterpretedOption::kAggregateValueFieldNumber);
SourceCodePath mutable_dest_path = dest_path;
mutable_dest_path.push_back(
UninterpretedOption::kAggregateValueFieldNumber);
-UninterpretedOption::kAggregateValueFieldNumber);
CollectAggregateFieldLocations(*dynamic, info_tree, mutable_src_path,
mutable_dest_path);
}
@ -1060,7 +1060,7 @@ void OptionInterpreter::CollectAggregateFieldLocations(
const TextFormat::ParseInfoTree* sub_tree =
tree.GetTreeForNested(field, val_marker_idx);
if (sub_tree != nullptr) {
dest_path.push_back(UninterpretedOption::kAggregateValueFieldNumber);
dest_path.push_back(-UninterpretedOption::kAggregateValueFieldNumber);
CollectAggregateFieldLocations(sub_message, *sub_tree,
uninterpreted_path, dest_path);
dest_path.pop_back();