Skip to content

Commit 7a5e354

Browse files
mcyemcfarlane
andauthored
Implement Edition 2024 (#626)
This PR implements support for edition 2024. In particular, we now have: 1. Support for `import option`. 2. Support for `export` and `local`, including bug-compatibility with protoc when it comes to the `STRICT` non-local nested type exception. 3. Support validation use of `java_multiple_files` option 4. Support `features.enforce_naming_style` enforcing `STYLE2024` 5. Support all new feature additions It also factors some diagnostics out of `ir`, for re-use across other parts of the compiler. --------- Co-authored-by: Edward McFarlane <emcfarlane@buf.build>
1 parent a85d2ef commit 7a5e354

File tree

71 files changed

+4107
-347
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+4107
-347
lines changed

experimental/ast/syntax/editions.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121

2222
// LatestImplementedEdition is the most recent edition that the compiler
2323
// implements.
24-
const LatestImplementedEdition = Edition2023
24+
const LatestImplementedEdition = Edition2024
2525

2626
// All returns an iterator over all known [Syntax] values.
2727
func All() iter.Seq[Syntax] {
@@ -49,7 +49,7 @@ func (s Syntax) IsEdition() bool {
4949
// IsSupported returns whether this syntax is fully supported.
5050
func (s Syntax) IsSupported() bool {
5151
switch s {
52-
case Proto2, Proto3, Edition2023:
52+
case Proto2, Proto3, Edition2023, Edition2024:
5353
return true
5454
default:
5555
return false

experimental/ast/syntax/name.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package syntax
16+
17+
import (
18+
"fmt"
19+
"strconv"
20+
)
21+
22+
var names = func() map[Syntax]string {
23+
names := make(map[Syntax]string)
24+
for syntax := range All() {
25+
if syntax.IsEdition() {
26+
names[syntax] = fmt.Sprintf("Edition %s", syntax)
27+
} else {
28+
names[syntax] = strconv.Quote(syntax.String())
29+
}
30+
}
31+
return names
32+
}()
33+
34+
// Name returns the name of this syntax as it should appear in diagnostics.
35+
func (s Syntax) Name() string {
36+
name, ok := names[s]
37+
if !ok {
38+
return "Edition <?>"
39+
}
40+
return name
41+
}

experimental/ast/syntax/syntax_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ import (
2121
"github.com/stretchr/testify/assert"
2222

2323
"github.com/bufbuild/protocompile/experimental/ast/syntax"
24-
"github.com/bufbuild/protocompile/internal/editions"
2524
"github.com/bufbuild/protocompile/internal/ext/iterx"
26-
"github.com/bufbuild/protocompile/internal/ext/mapsx"
2725
)
2826

2927
func TestEditions(t *testing.T) {
@@ -34,8 +32,7 @@ func TestEditions(t *testing.T) {
3432
[]syntax.Syntax{syntax.Edition2023, syntax.Edition2024},
3533
slices.Collect(syntax.Editions()),
3634
)
37-
assert.Equal(t,
38-
mapsx.KeySet(editions.SupportedEditions),
39-
mapsx.CollectSet(iterx.Strings(iterx.Filter(syntax.Editions(), syntax.Syntax.IsSupported))),
40-
)
35+
// Verify all editions report as supported
36+
supported := slices.Collect(iterx.Filter(syntax.Editions(), syntax.Syntax.IsSupported))
37+
assert.Equal(t, []syntax.Syntax{syntax.Edition2023, syntax.Edition2024}, supported)
4138
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package erredition defines common diagnostics for issuing errors about
16+
// the wrong edition being used.
17+
package erredition
18+
19+
import (
20+
"github.com/bufbuild/protocompile/experimental/ast"
21+
"github.com/bufbuild/protocompile/experimental/ast/syntax"
22+
"github.com/bufbuild/protocompile/experimental/report"
23+
"github.com/bufbuild/protocompile/experimental/source"
24+
)
25+
26+
// TooOld diagnoses an edition that is too old for the feature used.
27+
type TooOld struct {
28+
What any
29+
Where source.Spanner
30+
Decl ast.DeclSyntax
31+
Current syntax.Syntax
32+
Intro syntax.Syntax
33+
}
34+
35+
// Diagnose implements [report.Diagnoser].
36+
func (e TooOld) Diagnose(d *report.Diagnostic) {
37+
kind := "syntax"
38+
if e.Current.IsEdition() {
39+
kind = "edition"
40+
}
41+
42+
d.Apply(
43+
report.Message("`%s` is not supported in %s", e.What, e.Current.Name()),
44+
report.Snippet(e.Where),
45+
report.Snippetf(e.Decl.Value(), "%s specified here", kind),
46+
)
47+
48+
if e.Intro != syntax.Unknown {
49+
d.Apply(report.Helpf("`%s` requires at least %s", e.What, e.Intro.Name()))
50+
}
51+
}
52+
53+
// TooNew diagnoses an edition that is too new for the feature used.
54+
type TooNew struct {
55+
Current syntax.Syntax
56+
Decl ast.DeclSyntax
57+
58+
Deprecated, Removed syntax.Syntax
59+
DeprecatedReason, RemovedReason string
60+
61+
What any
62+
Where source.Spanner
63+
}
64+
65+
// Diagnose implements [report.Diagnoser].
66+
func (e TooNew) Diagnose(d *report.Diagnostic) {
67+
kind := "syntax"
68+
if e.Current.IsEdition() {
69+
kind = "edition"
70+
}
71+
72+
err := "not supported"
73+
if !e.isRemoved() {
74+
err = "deprecated"
75+
}
76+
77+
d.Apply(
78+
report.Message("`%s` is %s in %s", e.What, err, e.Current.Name()),
79+
report.Snippet(e.Where),
80+
report.Snippetf(e.Decl.Value(), "%s specified here", kind),
81+
)
82+
83+
if e.isRemoved() {
84+
if e.isDeprecated() {
85+
d.Apply(report.Helpf("deprecated since %s, removed in %s", e.Deprecated.Name(), e.Removed.Name()))
86+
} else {
87+
d.Apply(report.Helpf("removed in %s", e.Removed.Name()))
88+
}
89+
90+
if e.RemovedReason != "" {
91+
d.Apply(report.Helpf("%s", normalizeReason(e.RemovedReason)))
92+
return
93+
}
94+
} else if e.isDeprecated() {
95+
if e.Removed != syntax.Unknown {
96+
d.Apply(report.Helpf("deprecated since %s, to be removed in %s", e.Deprecated.Name(), e.Removed.Name()))
97+
} else {
98+
d.Apply(report.Helpf("deprecated since %s", e.Deprecated.Name()))
99+
}
100+
}
101+
102+
if e.DeprecatedReason != "" {
103+
d.Apply(report.Helpf("%s", normalizeReason(e.DeprecatedReason)))
104+
}
105+
}
106+
107+
func (e TooNew) isDeprecated() bool {
108+
return e.Deprecated != syntax.Unknown && e.Deprecated <= e.Current
109+
}
110+
111+
func (e TooNew) isRemoved() bool {
112+
return e.Removed != syntax.Unknown && e.Removed <= e.Current
113+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package erredition
16+
17+
import (
18+
"regexp"
19+
"strings"
20+
"unicode"
21+
"unicode/utf8"
22+
)
23+
24+
var (
25+
whitespacePattern = regexp.MustCompile(`[ \t\r\n]+`)
26+
protoDevPattern = regexp.MustCompile(` See http:\/\/protobuf\.dev\/[^ ]+ for more information\.?`)
27+
periodPattern = regexp.MustCompile(`\.( [A-Z]|$)`)
28+
editionPattern = regexp.MustCompile(`edition [0-9]+`)
29+
)
30+
31+
// normalizeReason canonicalizes the appearance of deprecation reasons.
32+
// Some built-in deprecation warnings have double spaces after periods.
33+
func normalizeReason(text string) string {
34+
// First, normalize all whitespace.
35+
text = whitespacePattern.ReplaceAllString(text, " ")
36+
37+
// Delete protobuf.dev links; these should ideally use our specialized link
38+
// formatting instead.
39+
text = protoDevPattern.ReplaceAllString(text, "")
40+
41+
// Replace all sentence-ending periods with semicolons.
42+
text = periodPattern.ReplaceAllStringFunc(text, func(match string) string {
43+
if match == "." {
44+
return ""
45+
}
46+
return ";" + strings.ToLower(match[1:])
47+
})
48+
49+
// Capitalize "Edition" when followed by an edition number.
50+
text = editionPattern.ReplaceAllStringFunc(text, func(s string) string {
51+
return "E" + s[1:]
52+
})
53+
54+
// Finally, de-capitalize the first letter.
55+
r, n := utf8.DecodeRuneInString(text)
56+
return string(unicode.ToLower(r)) + text[n:]
57+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package erredition
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
)
22+
23+
func TestNormalize(t *testing.T) {
24+
t.Parallel()
25+
26+
assert.Equal(t,
27+
"the legacy closed enum behavior in Java is deprecated and is scheduled to be removed in Edition 2025",
28+
normalizeReason("The legacy closed enum behavior in Java is deprecated and is scheduled to be removed in edition 2025. See http://protobuf.dev/programming-guides/enum/#java for more information."),
29+
)
30+
}

0 commit comments

Comments
 (0)