google/protobuf.dart

Impossible to distinguish the `oneof` fields from non-`oneof` fields when creating/using a generated class instance.

s0nerik opened this issue · 4 comments

Imagine looking at the following factory constructor (and the available properties) of a generated class:

factory FormEntry({
  $core.String? name,
  StaticTextField? staticText,
  TextInputField? textInput,
})
...
StaticTextField get staticText => $_getN(1);
TextInputField get textInput => $_getN(2);

Can I specify both the staticText and textInput at the same time? Looks like yes.

In reality, though, I can't, since it corresponds to the following protobuf:

message FormEntry {
  string name = 1;
  oneof field {
    StaticTextField static_text = 100;
    TextInputField text_input = 101;
  }
}

Notice how the generated class structure differs from protobuf, making it look like the staticText and textInput are simple fields that can both exist at the same time.

Using only the generated code (without thoroughly looking into its source), it seems to be impossible to figure out if certain fields belong to a oneof group or not.

If you look at the specification https://protobuf.dev/programming-guides/proto3/#oneof

Oneof fields are like regular fields except all the fields in a oneof share memory, and at most one field can be set at the same time. Setting any member of the oneof automatically clears all the other members. You can check which value in a oneof is set (if any) using a special case() or WhichOneof() method, depending on your chosen language.

So really in the interface they look like normal fields. The special semantics is when setting:

Note that if multiple values are set, the last set value as determined by the order in the proto will overwrite all previous ones.

So if you pass in multiple values in the constructor only one will end up being set. Don't do that.

How would you have expected oneofs to be represented? If Dart had some kind of union-types it could perhaps be modeled nicer in the constructor interface.

How would you have expected oneofs to be represented? If Dart had some kind of union-types it could perhaps be modeled nicer in the constructor interface.

I would imagine something like this to be possible to represent in Dart 3 via sealed classes. Generated classes could look something like this:

class FormEntry {
  factory FormEntry({
    $core.String? name,
    FormEntry_Field? field,
  })
  ...
  FormEntry_Field? get field => ...;
}

sealed class FormEntry_Field {}
class FormEntry_Field_StaticText extends FormEntry_Field {
  factory FormEntry_Field_StaticText({
    StaticTextField? staticText,
  })
  ...
  StaticTextField get staticText => ...;
}
class FormEntry_Field_TextInput extends FormEntry_Field {
  factory FormEntry_Field_TextInput({
    TextInputField? textInput,
  })
  ...
  TextInputField get textInput => ...;
}