Skip to content

Commit b5a837a

Browse files
jonmeowzygoloid
andauthored
Refactor modifier formatting to remove string passing. (#4418)
I'm taking the approach of making DiagnosticBase an API so that we can pass similar diagnostics as parameters. An alternative would be to do the function_ref approach we've done elsewhere, but these felt more boilerplate to me. Note I'm also modifying messages here. Let me know if you'd like different changes and/or just keeping current formatting (keeping current formatting would also allow removing some of the templating I've added, but it felt helpful putting explicit tokens where possible). But also, things like "`protected` not allowed on `interface` declaration at file scope" were part of the phrasing issue, I think. --------- Co-authored-by: Richard Smith <[email protected]>
1 parent 5a795db commit b5a837a

File tree

18 files changed

+156
-107
lines changed

18 files changed

+156
-107
lines changed

toolchain/check/check.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ static auto ProcessNodeIds(Context& context, llvm::raw_ostream* vlog_stream,
861861
// On crash, report which token we were handling.
862862
PrettyStackTraceFunction node_dumper([&](llvm::raw_ostream& output) {
863863
auto loc = converter.ConvertLoc(
864-
node_id, [](DiagnosticLoc, const Internal::DiagnosticBase<>&) {});
864+
node_id, [](DiagnosticLoc, const DiagnosticBase<>&) {});
865865
loc.FormatLocation(output);
866866
output << ": checking " << context.parse_tree().node_kind(node_id) << "\n";
867867
// Crash output has a tab indent; try to indent slightly past that.

toolchain/check/modifiers.cpp

Lines changed: 99 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,37 @@
88

99
namespace Carbon::Check {
1010

11-
static auto DiagnoseNotAllowed(Context& context, Parse::NodeId modifier_node,
12-
Lex::TokenKind decl_kind,
13-
llvm::StringRef context_string,
14-
SemIR::LocId context_loc_id) -> void {
15-
CARBON_DIAGNOSTIC(ModifierNotAllowedOn, Error,
16-
"`{0}` not allowed on `{1}` declaration{2}", Lex::TokenKind,
17-
Lex::TokenKind, std::string);
18-
auto diag = context.emitter().Build(modifier_node, ModifierNotAllowedOn,
19-
context.token_kind(modifier_node),
20-
decl_kind, context_string.str());
11+
// Builds the diagnostic for DiagnoseNotAllowed.
12+
template <typename... TokenKinds>
13+
static auto StartDiagnoseNotAllowed(
14+
Context& context, const DiagnosticBase<TokenKinds...>& diagnostic_base,
15+
Parse::NodeId modifier_node, Lex::TokenKind declaration_kind)
16+
-> DiagnosticEmitter<SemIRLoc>::DiagnosticBuilder {
17+
if constexpr (sizeof...(TokenKinds) == 0) {
18+
return context.emitter().Build(modifier_node, diagnostic_base);
19+
} else if constexpr (sizeof...(TokenKinds) == 1) {
20+
return context.emitter().Build(modifier_node, diagnostic_base,
21+
context.token_kind(modifier_node));
22+
} else {
23+
static_assert(sizeof...(TokenKinds) == 2);
24+
return context.emitter().Build(modifier_node, diagnostic_base,
25+
context.token_kind(modifier_node),
26+
declaration_kind);
27+
}
28+
}
29+
30+
// Diagnoses that a modifier wasn't allowed. Handles adding context when
31+
// possible.
32+
//
33+
// The diagnostic can take up to two TokenKinds: the modifier kind, and the
34+
// declaration kind.
35+
template <typename... TokenKinds>
36+
static auto DiagnoseNotAllowed(
37+
Context& context, const DiagnosticBase<TokenKinds...>& diagnostic_base,
38+
Parse::NodeId modifier_node, Lex::TokenKind decl_kind,
39+
SemIR::LocId context_loc_id) -> void {
40+
auto diag = StartDiagnoseNotAllowed(context, diagnostic_base, modifier_node,
41+
decl_kind);
2142
if (context_loc_id.is_valid()) {
2243
CARBON_DIAGNOSTIC(ModifierNotInContext, Note, "containing definition here");
2344
diag.Note(context_loc_id, ModifierNotInContext);
@@ -37,10 +58,16 @@ static auto ModifierOrderAsSet(ModifierOrder order) -> KeywordModifierSet {
3758
}
3859
}
3960

40-
auto ForbidModifiersOnDecl(Context& context, DeclIntroducerState& introducer,
41-
KeywordModifierSet forbidden,
42-
llvm::StringRef context_string,
43-
SemIR::LocId context_loc_id) -> void {
61+
// Like `LimitModifiersOnDecl`, except says which modifiers are forbidden, and a
62+
// `context_string` (and optional `context_loc_id`) specifying the context in
63+
// which those modifiers are forbidden.
64+
//
65+
// See DiagnoseNotAllowed for details regarding diagnostic_base.
66+
template <typename DiagnosticBaseT>
67+
static auto ForbidModifiersOnDecl(
68+
Context& context, const DiagnosticBaseT& diagnostic_base,
69+
DeclIntroducerState& introducer, KeywordModifierSet forbidden,
70+
SemIR::LocId context_loc_id = SemIR::LocId::Invalid) -> void {
4471
auto not_allowed = introducer.modifier_set & forbidden;
4572
if (not_allowed.empty()) {
4673
return;
@@ -50,28 +77,50 @@ auto ForbidModifiersOnDecl(Context& context, DeclIntroducerState& introducer,
5077
order_index <= static_cast<int8_t>(ModifierOrder::Last); ++order_index) {
5178
auto order = static_cast<ModifierOrder>(order_index);
5279
if (not_allowed.HasAnyOf(ModifierOrderAsSet(order))) {
53-
DiagnoseNotAllowed(context, introducer.modifier_node_id(order),
54-
introducer.kind, context_string, context_loc_id);
80+
DiagnoseNotAllowed(context, diagnostic_base,
81+
introducer.modifier_node_id(order), introducer.kind,
82+
context_loc_id);
5583
introducer.set_modifier_node_id(order, Parse::NodeId::Invalid);
5684
}
5785
}
5886

5987
introducer.modifier_set.Remove(forbidden);
6088
}
6189

90+
auto LimitModifiersOnDecl(Context& context, DeclIntroducerState& introducer,
91+
KeywordModifierSet allowed) -> void {
92+
CARBON_DIAGNOSTIC(ModifierNotAllowedOnDeclaration, Error,
93+
"`{0}` not allowed on `{1}` declaration", Lex::TokenKind,
94+
Lex::TokenKind);
95+
ForbidModifiersOnDecl(context, ModifierNotAllowedOnDeclaration, introducer,
96+
~allowed);
97+
}
98+
99+
auto LimitModifiersOnNotDefinition(Context& context,
100+
DeclIntroducerState& introducer,
101+
KeywordModifierSet allowed) -> void {
102+
CARBON_DIAGNOSTIC(
103+
ModifierOnlyAllowedOnDefinition, Error,
104+
"`{0}` not allowed on `{1}` forward declaration, only definition",
105+
Lex::TokenKind, Lex::TokenKind);
106+
ForbidModifiersOnDecl(context, ModifierOnlyAllowedOnDefinition, introducer,
107+
~allowed);
108+
}
109+
62110
auto CheckAccessModifiersOnDecl(Context& context,
63111
DeclIntroducerState& introducer,
64112
std::optional<SemIR::Inst> parent_scope_inst)
65113
-> void {
114+
CARBON_DIAGNOSTIC(ModifierProtectedNotAllowed, Error,
115+
"`protected` not allowed; requires class scope");
66116
if (parent_scope_inst) {
67117
if (parent_scope_inst->Is<SemIR::Namespace>()) {
68118
// TODO: This assumes that namespaces can only be declared at file scope.
69119
// If we add support for non-file-scope namespaces, we will need to check
70120
// the parents of the target scope to determine whether we're at file
71121
// scope.
72-
ForbidModifiersOnDecl(
73-
context, introducer, KeywordModifierSet::Protected,
74-
" at file scope, `protected` is only allowed on class members");
122+
ForbidModifiersOnDecl(context, ModifierProtectedNotAllowed, introducer,
123+
KeywordModifierSet::Protected);
75124
return;
76125
}
77126

@@ -82,11 +131,13 @@ auto CheckAccessModifiersOnDecl(Context& context,
82131
}
83132

84133
// Otherwise neither `private` nor `protected` allowed.
85-
ForbidModifiersOnDecl(context, introducer, KeywordModifierSet::Protected,
86-
", `protected` is only allowed on class members");
87-
ForbidModifiersOnDecl(
88-
context, introducer, KeywordModifierSet::Private,
89-
", `private` is only allowed on class members and at file scope");
134+
ForbidModifiersOnDecl(context, ModifierProtectedNotAllowed, introducer,
135+
KeywordModifierSet::Protected);
136+
137+
CARBON_DIAGNOSTIC(ModifierPrivateNotAllowed, Error,
138+
"`private` not allowed; requires class or file scope");
139+
ForbidModifiersOnDecl(context, ModifierPrivateNotAllowed, introducer,
140+
KeywordModifierSet::Private);
90141
}
91142

92143
auto CheckMethodModifiersOnFunction(
@@ -98,21 +149,29 @@ auto CheckMethodModifiersOnFunction(
98149
auto inheritance_kind =
99150
context.classes().Get(class_decl->class_id).inheritance_kind;
100151
if (inheritance_kind == SemIR::Class::Final) {
101-
ForbidModifiersOnDecl(context, introducer, KeywordModifierSet::Virtual,
102-
" in a non-abstract non-base `class` definition",
152+
CARBON_DIAGNOSTIC(
153+
ModifierVirtualNotAllowed, Error,
154+
"`virtual` not allowed; requires `abstract` or `base` class scope");
155+
ForbidModifiersOnDecl(context, ModifierVirtualNotAllowed, introducer,
156+
KeywordModifierSet::Virtual,
103157
context.insts().GetLocId(parent_scope_inst_id));
104158
}
105159
if (inheritance_kind != SemIR::Class::Abstract) {
106-
ForbidModifiersOnDecl(context, introducer, KeywordModifierSet::Abstract,
107-
" in a non-abstract `class` definition",
160+
CARBON_DIAGNOSTIC(
161+
ModifierAbstractNotAllowed, Error,
162+
"`abstract` not allowed; requires `abstract` class scope");
163+
ForbidModifiersOnDecl(context, ModifierAbstractNotAllowed, introducer,
164+
KeywordModifierSet::Abstract,
108165
context.insts().GetLocId(parent_scope_inst_id));
109166
}
110167
return;
111168
}
112169
}
113170

114-
ForbidModifiersOnDecl(context, introducer, KeywordModifierSet::Method,
115-
" outside of a class");
171+
CARBON_DIAGNOSTIC(ModifierRequiresClass, Error,
172+
"`{0}` not allowed; requires class scope", Lex::TokenKind);
173+
ForbidModifiersOnDecl(context, ModifierRequiresClass, introducer,
174+
KeywordModifierSet::Method);
116175
}
117176

118177
auto RestrictExternModifierOnDecl(Context& context,
@@ -124,8 +183,11 @@ auto RestrictExternModifierOnDecl(Context& context,
124183
}
125184

126185
if (parent_scope_inst && !parent_scope_inst->Is<SemIR::Namespace>()) {
127-
ForbidModifiersOnDecl(context, introducer, KeywordModifierSet::Extern,
128-
" that is a member");
186+
CARBON_DIAGNOSTIC(ModifierExternNotAllowed, Error,
187+
"`{0}` not allowed; requires file or namespace scope",
188+
Lex::TokenKind);
189+
ForbidModifiersOnDecl(context, ModifierExternNotAllowed, introducer,
190+
KeywordModifierSet::Extern);
129191
// Treat as unset.
130192
introducer.extern_library = SemIR::LibraryNameId::Invalid;
131193
return;
@@ -158,8 +220,11 @@ auto RequireDefaultFinalOnlyInInterfaces(
158220
// Both `default` and `final` allowed in an interface definition.
159221
return;
160222
}
161-
ForbidModifiersOnDecl(context, introducer, KeywordModifierSet::Interface,
162-
" outside of an interface");
223+
CARBON_DIAGNOSTIC(ModifierRequiresInterface, Error,
224+
"`{0}` not allowed; requires interface scope",
225+
Lex::TokenKind);
226+
ForbidModifiersOnDecl(context, ModifierRequiresInterface, introducer,
227+
KeywordModifierSet::Interface);
163228
}
164229

165230
} // namespace Carbon::Check

toolchain/check/modifiers.h

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,14 @@ auto CheckMethodModifiersOnFunction(
2828
SemIR::InstId parent_scope_inst_id,
2929
std::optional<SemIR::Inst> parent_scope_inst) -> void;
3030

31-
// Like `LimitModifiersOnDecl`, except says which modifiers are forbidden, and a
32-
// `context_string` (and optional `context_loc_id`) specifying the context in
33-
// which those modifiers are forbidden.
34-
// TODO: Take another look at diagnostic phrasing for callers.
35-
auto ForbidModifiersOnDecl(Context& context, DeclIntroducerState& introducer,
36-
KeywordModifierSet forbidden,
37-
llvm::StringRef context_string,
38-
SemIR::LocId context_loc_id = SemIR::LocId::Invalid)
39-
-> void;
40-
4131
// Reports a diagnostic (using `decl_kind`) if modifiers on this declaration are
4232
// not in `allowed`. Updates `introducer`.
43-
inline auto LimitModifiersOnDecl(Context& context,
44-
DeclIntroducerState& introducer,
45-
KeywordModifierSet allowed) -> void {
46-
ForbidModifiersOnDecl(context, introducer, ~allowed, "");
47-
}
33+
auto LimitModifiersOnDecl(Context& context, DeclIntroducerState& introducer,
34+
KeywordModifierSet allowed) -> void;
4835

49-
inline auto LimitModifiersOnNotDefinition(Context& context,
50-
DeclIntroducerState& introducer,
51-
KeywordModifierSet allowed) -> void {
52-
ForbidModifiersOnDecl(context, introducer, ~allowed, ", only definition");
53-
}
36+
auto LimitModifiersOnNotDefinition(Context& context,
37+
DeclIntroducerState& introducer,
38+
KeywordModifierSet allowed) -> void;
5439

5540
// Restricts the `extern` modifier to only be used on namespace-scoped
5641
// declarations. Diagnoses and cleans up:

toolchain/check/testdata/class/fail_base_bad_type.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ fn AccessMemberWithInvalidBaseStruct(p: DeriveFromStruct*) -> i32 { return (*p).
118118

119119
// --- fail_derive_from_incomplete.carbon
120120

121-
// CHECK:STDERR: fail_derive_from_incomplete.carbon:[[@LINE+4]]:1: error: `base` not allowed on `class` declaration, only definition
121+
// CHECK:STDERR: fail_derive_from_incomplete.carbon:[[@LINE+4]]:1: error: `base` not allowed on `class` forward declaration, only definition
122122
// CHECK:STDERR: base class Incomplete;
123123
// CHECK:STDERR: ^~~~
124124
// CHECK:STDERR:

toolchain/check/testdata/class/fail_field_modifiers.carbon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ class Class {
2222
// CHECK:STDERR:
2323
final var k: i32;
2424

25-
// CHECK:STDERR: fail_field_modifiers.carbon:[[@LINE+4]]:3: error: `default` not allowed on `let` declaration outside of an interface
25+
// CHECK:STDERR: fail_field_modifiers.carbon:[[@LINE+4]]:3: error: `default` not allowed; requires interface scope
2626
// CHECK:STDERR: default let l: i32 = 0;
2727
// CHECK:STDERR: ^~~~~~~
2828
// CHECK:STDERR:
2929
default let l: i32 = 0;
3030

31-
// CHECK:STDERR: fail_field_modifiers.carbon:[[@LINE+3]]:3: error: `final` not allowed on `let` declaration outside of an interface
31+
// CHECK:STDERR: fail_field_modifiers.carbon:[[@LINE+3]]:3: error: `final` not allowed; requires interface scope
3232
// CHECK:STDERR: final let m: i32 = 1;
3333
// CHECK:STDERR: ^~~~~
3434
final let m: i32 = 1;

toolchain/check/testdata/class/fail_method_modifiers.carbon

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
class FinalClass {
1212

13-
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+7]]:3: error: `abstract` not allowed on `fn` declaration in a non-abstract `class` definition
13+
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+7]]:3: error: `abstract` not allowed; requires `abstract` class scope
1414
// CHECK:STDERR: abstract fn Abstract[self: Self]();
1515
// CHECK:STDERR: ^~~~~~~~
1616
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE-5]]:1: note: containing definition here
@@ -19,7 +19,7 @@ class FinalClass {
1919
// CHECK:STDERR:
2020
abstract fn Abstract[self: Self]();
2121

22-
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+7]]:3: error: `virtual` not allowed on `fn` declaration in a non-abstract non-base `class` definition
22+
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+7]]:3: error: `virtual` not allowed; requires `abstract` or `base` class scope
2323
// CHECK:STDERR: virtual fn Virtual[self: Self]();
2424
// CHECK:STDERR: ^~~~~~~
2525
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE-14]]:1: note: containing definition here
@@ -31,13 +31,13 @@ class FinalClass {
3131

3232
abstract class AbstractClass {
3333

34-
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+4]]:3: error: `default` not allowed on `fn` declaration outside of an interface
34+
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+4]]:3: error: `default` not allowed; requires interface scope
3535
// CHECK:STDERR: default fn Default[self: Self]();
3636
// CHECK:STDERR: ^~~~~~~
3737
// CHECK:STDERR:
3838
default fn Default[self: Self]();
3939

40-
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+4]]:3: error: `final` not allowed on `fn` declaration outside of an interface
40+
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+4]]:3: error: `final` not allowed; requires interface scope
4141
// CHECK:STDERR: final fn Final[self: Self]();
4242
// CHECK:STDERR: ^~~~~
4343
// CHECK:STDERR:
@@ -46,7 +46,7 @@ abstract class AbstractClass {
4646

4747
base class BaseClass {
4848

49-
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+6]]:3: error: `abstract` not allowed on `fn` declaration in a non-abstract `class` definition
49+
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE+6]]:3: error: `abstract` not allowed; requires `abstract` class scope
5050
// CHECK:STDERR: abstract fn Abstract[self: Self]();
5151
// CHECK:STDERR: ^~~~~~~~
5252
// CHECK:STDERR: fail_method_modifiers.carbon:[[@LINE-5]]:1: note: containing definition here

toolchain/check/testdata/class/fail_modifiers.carbon

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// CHECK:STDERR:
1818
private private class DuplicatePrivate;
1919

20-
// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:1: error: `abstract` not allowed on `class` declaration, only definition
20+
// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:1: error: `abstract` not allowed on `class` forward declaration, only definition
2121
// CHECK:STDERR: abstract class AbstractDecl;
2222
// CHECK:STDERR: ^~~~~~~~
2323
// CHECK:STDERR:
@@ -32,7 +32,7 @@ abstract class AbstractDecl;
3232
// CHECK:STDERR:
3333
private protected class TwoAccess;
3434

35-
// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:1: error: `base` not allowed on `class` declaration, only definition
35+
// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:1: error: `base` not allowed on `class` forward declaration, only definition
3636
// CHECK:STDERR: base class BaseDecl;
3737
// CHECK:STDERR: ^~~~
3838
// CHECK:STDERR:
@@ -47,7 +47,7 @@ base class BaseDecl;
4747
// CHECK:STDERR:
4848
abstract abstract class TwoAbstract { }
4949

50-
// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+15]]:1: error: `protected` not allowed on `class` declaration at file scope, `protected` is only allowed on class members
50+
// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+15]]:1: error: `protected` not allowed; requires class scope
5151
// CHECK:STDERR: protected virtual base class Virtual {}
5252
// CHECK:STDERR: ^~~~~~~~~
5353
// CHECK:STDERR:

toolchain/check/testdata/class/no_prelude/extern.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class C;
9393
library "[[@TEST_NAME]]";
9494

9595
class C {
96-
// CHECK:STDERR: fail_extern_member_class.carbon:[[@LINE+4]]:3: error: `extern` not allowed on `class` declaration that is a member
96+
// CHECK:STDERR: fail_extern_member_class.carbon:[[@LINE+4]]:3: error: `extern` not allowed; requires file or namespace scope
9797
// CHECK:STDERR: extern class D;
9898
// CHECK:STDERR: ^~~~~~
9999
// CHECK:STDERR:

toolchain/check/testdata/function/declaration/no_prelude/extern.carbon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ fn F();
5353
library "[[@TEST_NAME]]";
5454

5555
class C {
56-
// CHECK:STDERR: fail_member_extern.carbon:[[@LINE+4]]:3: error: `extern` not allowed on `fn` declaration that is a member
56+
// CHECK:STDERR: fail_member_extern.carbon:[[@LINE+4]]:3: error: `extern` not allowed; requires file or namespace scope
5757
// CHECK:STDERR: extern fn F();
5858
// CHECK:STDERR: ^~~~~~
5959
// CHECK:STDERR:
6060
extern fn F();
61-
// CHECK:STDERR: fail_member_extern.carbon:[[@LINE+3]]:3: error: `extern` not allowed on `fn` declaration that is a member
61+
// CHECK:STDERR: fail_member_extern.carbon:[[@LINE+3]]:3: error: `extern` not allowed; requires file or namespace scope
6262
// CHECK:STDERR: extern fn G[self: Self]();
6363
// CHECK:STDERR: ^~~~~~
6464
extern fn G[self: Self]();

0 commit comments

Comments
 (0)