Skip to content

Commit

Permalink
Support most of edition 2023 presence semantics (#326)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewparmet authored Jan 22, 2025
1 parent fcfd24c commit f287ade
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import com.google.common.base.CaseFormat.LOWER_CAMEL
import com.google.common.base.CaseFormat.LOWER_UNDERSCORE
import com.google.common.base.CaseFormat.UPPER_CAMEL
import com.google.protobuf.DescriptorProtos.DescriptorProto
import com.google.protobuf.DescriptorProtos.FeatureSet
import com.google.protobuf.DescriptorProtos.FeatureSet.FieldPresence
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Label.LABEL_REPEATED
Expand Down Expand Up @@ -207,7 +209,19 @@ internal class FieldParser(
}

private fun optional(fdp: FieldDescriptorProto) =
(fdp.label == LABEL_OPTIONAL && ctx.proto2) || fdp.proto3Optional
when {
ctx.proto2 -> fdp.label == LABEL_OPTIONAL
ctx.proto3 -> fdp.proto3Optional
ctx.edition2023 -> optional(ctx.fileOptions.default.features, fdp.options.features)
else -> error("unexpected edition/syntax")
}

private fun optional(fileFeatures: FeatureSet, fieldFeatures: FeatureSet) =
if (fileFeatures.fieldPresence == FieldPresence.EXPLICIT) {
fieldFeatures.fieldPresence !in setOf(FieldPresence.IMPLICIT, FieldPresence.LEGACY_REQUIRED)
} else {
fieldFeatures.fieldPresence == FieldPresence.EXPLICIT
}

private fun packed(type: FieldType, fdp: FieldDescriptorProto) =
type.packable &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package protokt.v1.codegen.util

import com.google.protobuf.DescriptorProtos
import com.google.protobuf.DescriptorProtos.FileDescriptorProto
import com.toasttab.protokt.v1.ProtoktProtos
import protokt.v1.gradle.PROTOKT_VERSION
Expand Down Expand Up @@ -44,6 +45,7 @@ internal class GeneratorContext(

val proto2 = !fdp.hasSyntax() || fdp.syntax == "proto2"
val proto3 = fdp.syntax == "proto3"
val edition2023 = fdp.edition == DescriptorProtos.Edition.EDITION_2023
}

val FileDescriptorProto.fileOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 Toast, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

edition = "2023";

package protokt.v1.testing;

option features.field_presence = EXPLICIT;

import "google/protobuf/empty.proto";

message TestFileExplicit {
int32 foo = 1;
google.protobuf.Empty bar = 2;

int32 baz = 3 [features.field_presence = EXPLICIT];
google.protobuf.Empty qux = 4 [features.field_presence = EXPLICIT];

int32 corge = 5 [features.field_presence = IMPLICIT];
// not allowed: google.protobuf.Empty grault = 6 [features.field_presence = IMPLICIT];

int32 garply = 7 [features.field_presence = LEGACY_REQUIRED];
google.protobuf.Empty thud = 8 [features.field_presence = LEGACY_REQUIRED];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 Toast, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

edition = "2023";

package protokt.v1.testing;

option features.field_presence = IMPLICIT;

import "google/protobuf/empty.proto";

message TestFileImplicit {
int32 foo = 1;
google.protobuf.Empty bar = 2;

int32 baz = 3 [features.field_presence = EXPLICIT];
google.protobuf.Empty qux = 4 [features.field_presence = EXPLICIT];

int32 corge = 5 [features.field_presence = IMPLICIT];
// not allowed: google.protobuf.Empty grault = 6 [features.field_presence = IMPLICIT];

int32 garply = 7 [features.field_presence = LEGACY_REQUIRED];
google.protobuf.Empty thud = 8 [features.field_presence = LEGACY_REQUIRED];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2024 Toast, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

edition = "2023";

package protokt.v1.testing;

// not allowed: option features.field_presence = LEGACY_REQUIRED;
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2024 Toast, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package protokt.v1.testing

import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test

class Edition2023PresenceTest {
@Test
fun `file with implicit presence and no field features has correct behavior on primitive`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("foo")).isFalse()
assertThat(TestFileImplicit {}.foo).isEqualTo(0)

assertThat(TestFileImplicit::class.propertyIsMarkedNullable("corge")).isFalse()
assertThat(TestFileImplicit {}.corge).isEqualTo(0)
}

@Test
fun `file with implicit presence and no field features has correct behavior on message`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("bar")).isTrue()
assertThat(TestFileImplicit {}.bar).isNull()
}

@Test
fun `file with implicit presence and field with explicit presence has correct behavior on primitive`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("baz")).isTrue()
assertThat(TestFileImplicit {}.baz).isNull()
}

@Test
fun `file with implicit presence and field with explicit presence has correct behavior on message`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("qux")).isTrue()
assertThat(TestFileImplicit {}.qux).isNull()
}

@Test
fun `file with implicit presence and field with legacy required presence has correct behavior on primitive`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("garply")).isFalse()
assertThat(TestFileImplicit {}.garply).isEqualTo(0)
}

@Test
fun `file with implicit presence and field with legacy required for message type`() {
// protokt doesn't support non-null message types
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("thud")).isTrue()
assertThat(TestFileImplicit {}.thud).isNull()
}

@Test
fun `file with explicit presence and no field features has correct behavior on primitive`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("foo")).isFalse()
assertThat(TestFileImplicit {}.foo).isEqualTo(0)
}

@Test
fun `file with explicit presence and no field features has correct behavior on message`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("bar")).isTrue()
assertThat(TestFileImplicit {}.bar).isNull()
}

@Test
fun `file with explicit presence and field with explicit presence has correct behavior on primitive`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("baz")).isTrue()
assertThat(TestFileImplicit {}.baz).isNull()
}

@Test
fun `file with explicit presence and field with explicit presence has correct behavior on message`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("qux")).isTrue()
assertThat(TestFileImplicit {}.qux).isNull()
}

@Test
fun `file with explicit presence and field with implicit presence has correct behavior on primitive`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("corge")).isFalse()
assertThat(TestFileImplicit {}.corge).isEqualTo(0)
}

@Test
fun `file with explicit presence and field with legacy required presence has correct behavior on primitive`() {
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("garply")).isFalse()
assertThat(TestFileImplicit {}.garply).isEqualTo(0)
}

@Test
fun `file with explicit presence and field with legacy required for message type`() {
// protokt doesn't support non-null message types
assertThat(TestFileImplicit::class.propertyIsMarkedNullable("thud")).isTrue()
assertThat(TestFileImplicit {}.thud).isNull()
}
}

0 comments on commit f287ade

Please sign in to comment.