Skip to content

Commit 96964ee

Browse files
authored
Implement basic bool and int formatting for diagnostics (#4411)
Note, this supports plurals, but doesn't apply it anywhere. I'm mainly doing that to demonstrate the approach regarding syntax. See format_providers.h for details.
1 parent a5ba0ee commit 96964ee

File tree

13 files changed

+416
-145
lines changed

13 files changed

+416
-145
lines changed

toolchain/check/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ cc_library(
7474
"//toolchain/check:generic_region_stack",
7575
"//toolchain/check:scope_stack",
7676
"//toolchain/diagnostics:diagnostic_emitter",
77+
"//toolchain/diagnostics:format_providers",
7778
"//toolchain/lex:token_kind",
7879
"//toolchain/lex:tokenized_buffer",
7980
"//toolchain/parse:node_kind",
@@ -116,6 +117,7 @@ cc_library(
116117
"//toolchain/base:pretty_stack_trace_function",
117118
"//toolchain/base:value_store",
118119
"//toolchain/diagnostics:diagnostic_emitter",
120+
"//toolchain/diagnostics:format_providers",
119121
"//toolchain/lex:token_index",
120122
"//toolchain/lex:token_kind",
121123
"//toolchain/lex:tokenized_buffer",

toolchain/check/convert.cpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "toolchain/check/context.h"
1515
#include "toolchain/check/operator.h"
1616
#include "toolchain/check/pattern_match.h"
17+
#include "toolchain/diagnostics/format_providers.h"
1718
#include "toolchain/sem_ir/copy_on_write_block.h"
1819
#include "toolchain/sem_ir/file.h"
1920
#include "toolchain/sem_ir/generic.h"
@@ -381,8 +382,13 @@ static auto ConvertStructToStructOrClass(Context& context,
381382
SemIR::StructType src_type,
382383
SemIR::StructType dest_type,
383384
SemIR::InstId value_id,
384-
ConversionTarget target, bool is_class)
385+
ConversionTarget target)
385386
-> SemIR::InstId {
387+
static_assert(std::is_same_v<SemIR::ClassElementAccess, TargetAccessInstT> ||
388+
std::is_same_v<SemIR::StructAccess, TargetAccessInstT>);
389+
constexpr bool ToClass =
390+
std::is_same_v<SemIR::ClassElementAccess, TargetAccessInstT>;
391+
386392
auto& sem_ir = context.sem_ir();
387393
auto src_elem_fields = sem_ir.inst_blocks().Get(src_type.fields_id);
388394
auto dest_elem_fields = sem_ir.inst_blocks().Get(dest_type.fields_id);
@@ -406,14 +412,14 @@ static auto ConvertStructToStructOrClass(Context& context,
406412
// TODO: If not, include the name of the first source field that doesn't
407413
// exist in the destination or vice versa in the diagnostic.
408414
if (src_elem_fields.size() != dest_elem_fields.size()) {
409-
CARBON_DIAGNOSTIC(StructInitElementCountMismatch, Error,
410-
"cannot initialize {0} with {1} field(s) from struct "
411-
"with {2} field(s).",
412-
llvm::StringLiteral, size_t, size_t);
413-
context.emitter().Emit(
414-
value_loc_id, StructInitElementCountMismatch,
415-
is_class ? llvm::StringLiteral("class") : llvm::StringLiteral("struct"),
416-
dest_elem_fields.size(), src_elem_fields.size());
415+
CARBON_DIAGNOSTIC(
416+
StructInitElementCountMismatch, Error,
417+
"cannot initialize {0:class|struct} with {1} field(s) from struct "
418+
"with {2} field(s).",
419+
BoolAsSelect, size_t, size_t);
420+
context.emitter().Emit(value_loc_id, StructInitElementCountMismatch,
421+
ToClass, dest_elem_fields.size(),
422+
src_elem_fields.size());
417423
return SemIR::InstId::BuiltinError;
418424
}
419425

@@ -493,7 +499,7 @@ static auto ConvertStructToStructOrClass(Context& context,
493499
new_block.Set(i, init_id);
494500
}
495501

496-
if (is_class) {
502+
if (ToClass) {
497503
target.init_block->InsertHere();
498504
CARBON_CHECK(is_init,
499505
"Converting directly to a class value is not supported");
@@ -522,7 +528,7 @@ static auto ConvertStructToStruct(Context& context, SemIR::StructType src_type,
522528
SemIR::InstId value_id,
523529
ConversionTarget target) -> SemIR::InstId {
524530
return ConvertStructToStructOrClass<SemIR::StructAccess>(
525-
context, src_type, dest_type, value_id, target, /*is_class=*/false);
531+
context, src_type, dest_type, value_id, target);
526532
}
527533

528534
// Performs a conversion from a struct to a class type. This function only
@@ -554,7 +560,7 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
554560
}
555561

556562
auto result_id = ConvertStructToStructOrClass<SemIR::ClassElementAccess>(
557-
context, src_type, dest_struct_type, value_id, target, /*is_class=*/true);
563+
context, src_type, dest_struct_type, value_id, target);
558564

559565
if (need_temporary) {
560566
target_block.InsertHere();
@@ -1154,13 +1160,11 @@ static auto ConvertSelf(Context& context, SemIR::LocId call_loc_id,
11541160
bool addr_pattern = context.insts().Is<SemIR::AddrPattern>(self_param_id);
11551161
DiagnosticAnnotationScope annotate_diagnostics(
11561162
&context.emitter(), [&](auto& builder) {
1157-
CARBON_DIAGNOSTIC(
1158-
InCallToFunctionSelf, Note,
1159-
"initializing `{0}` parameter of method declared here",
1160-
llvm::StringLiteral);
1161-
builder.Note(self_param_id, InCallToFunctionSelf,
1162-
addr_pattern ? llvm::StringLiteral("addr self")
1163-
: llvm::StringLiteral("self"));
1163+
CARBON_DIAGNOSTIC(InCallToFunctionSelf, Note,
1164+
"initializing `{0:addr self|self}` parameter of "
1165+
"method declared here",
1166+
BoolAsSelect);
1167+
builder.Note(self_param_id, InCallToFunctionSelf, addr_pattern);
11641168
});
11651169

11661170
return CallerPatternMatch(context, callee_specific_id, self_param_id,

toolchain/check/eval.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "toolchain/check/diagnostic_helpers.h"
99
#include "toolchain/check/generic.h"
1010
#include "toolchain/diagnostics/diagnostic_emitter.h"
11+
#include "toolchain/diagnostics/format_providers.h"
1112
#include "toolchain/sem_ir/builtin_function_kind.h"
1213
#include "toolchain/sem_ir/function.h"
1314
#include "toolchain/sem_ir/generic.h"
@@ -745,18 +746,17 @@ static auto PerformBuiltinBinaryIntOp(Context& context, SemIRLoc loc,
745746
// Bit shift.
746747
case SemIR::BuiltinFunctionKind::IntLeftShift:
747748
case SemIR::BuiltinFunctionKind::IntRightShift:
748-
op_str = (builtin_kind == SemIR::BuiltinFunctionKind::IntLeftShift)
749-
? llvm::StringLiteral("<<")
750-
: llvm::StringLiteral(">>");
751749
if (rhs_val.uge(lhs_val.getBitWidth()) ||
752750
(rhs_val.isNegative() && context.types().IsSignedInt(rhs.type_id))) {
753-
CARBON_DIAGNOSTIC(CompileTimeShiftOutOfRange, Error,
754-
"shift distance not in range [0, {0}) in {1} {2} {3}",
755-
unsigned, TypedInt, llvm::StringLiteral, TypedInt);
756-
context.emitter().Emit(loc, CompileTimeShiftOutOfRange,
757-
lhs_val.getBitWidth(),
758-
{.type = lhs.type_id, .value = lhs_val}, op_str,
759-
{.type = rhs.type_id, .value = rhs_val});
751+
CARBON_DIAGNOSTIC(
752+
CompileTimeShiftOutOfRange, Error,
753+
"shift distance not in range [0, {0}) in {1} {2:<<|>>} {3}",
754+
unsigned, TypedInt, BoolAsSelect, TypedInt);
755+
context.emitter().Emit(
756+
loc, CompileTimeShiftOutOfRange, lhs_val.getBitWidth(),
757+
{.type = lhs.type_id, .value = lhs_val},
758+
builtin_kind == SemIR::BuiltinFunctionKind::IntLeftShift,
759+
{.type = rhs.type_id, .value = rhs_val});
760760
// TODO: Is it useful to recover by returning 0 or -1?
761761
return SemIR::ConstantId::Error;
762762
}

toolchain/check/handle_binding_pattern.cpp

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "toolchain/check/convert.h"
77
#include "toolchain/check/handle.h"
88
#include "toolchain/check/return.h"
9+
#include "toolchain/diagnostics/format_providers.h"
910
#include "toolchain/sem_ir/ids.h"
1011
#include "toolchain/sem_ir/inst.h"
1112

@@ -104,23 +105,19 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
104105
cast_type_id,
105106
[&] {
106107
CARBON_DIAGNOSTIC(IncompleteTypeInVarDecl, Error,
107-
"{0} has incomplete type {1}",
108-
llvm::StringLiteral, SemIR::TypeId);
109-
return context.emitter().Build(
110-
type_node, IncompleteTypeInVarDecl,
111-
parent_class_decl ? llvm::StringLiteral("field")
112-
: llvm::StringLiteral("variable"),
113-
cast_type_id);
108+
"{0:field|variable} has incomplete type {1}",
109+
BoolAsSelect, SemIR::TypeId);
110+
return context.emitter().Build(type_node, IncompleteTypeInVarDecl,
111+
parent_class_decl.has_value(),
112+
cast_type_id);
114113
},
115114
[&] {
116115
CARBON_DIAGNOSTIC(AbstractTypeInVarDecl, Error,
117-
"{0} has abstract type {1}", llvm::StringLiteral,
118-
SemIR::TypeId);
119-
return context.emitter().Build(
120-
type_node, AbstractTypeInVarDecl,
121-
parent_class_decl ? llvm::StringLiteral("field")
122-
: llvm::StringLiteral("variable"),
123-
cast_type_id);
116+
"{0:field|variable} has abstract type {1}",
117+
BoolAsSelect, SemIR::TypeId);
118+
return context.emitter().Build(type_node, AbstractTypeInVarDecl,
119+
parent_class_decl.has_value(),
120+
cast_type_id);
124121
});
125122
if (parent_class_decl) {
126123
CARBON_CHECK(context_node_kind == Parse::NodeKind::VariableIntroducer,

toolchain/check/merge.cpp

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "toolchain/base/kind_switch.h"
88
#include "toolchain/check/import_ref.h"
9+
#include "toolchain/diagnostics/format_providers.h"
910
#include "toolchain/sem_ir/ids.h"
1011
#include "toolchain/sem_ir/typed_insts.h"
1112

@@ -193,10 +194,12 @@ static auto EntityHasParamError(Context& context, const DeclParams& info)
193194

194195
// Returns false if a param differs for a redeclaration. The caller is expected
195196
// to provide a diagnostic.
196-
static auto CheckRedeclParam(
197-
Context& context, llvm::StringLiteral param_diag_label, int32_t param_index,
198-
SemIR::InstId new_param_pattern_id, SemIR::InstId prev_param_pattern_id,
199-
SemIR::SpecificId prev_specific_id, bool diagnose) -> bool {
197+
static auto CheckRedeclParam(Context& context, bool is_implicit_param,
198+
int32_t param_index,
199+
SemIR::InstId new_param_pattern_id,
200+
SemIR::InstId prev_param_pattern_id,
201+
SemIR::SpecificId prev_specific_id, bool diagnose)
202+
-> bool {
200203
// TODO: Consider differentiating between type and name mistakes. For now,
201204
// taking the simpler approach because I also think we may want to refactor
202205
// params.
@@ -205,15 +208,16 @@ static auto CheckRedeclParam(
205208
return;
206209
}
207210
CARBON_DIAGNOSTIC(RedeclParamDiffers, Error,
208-
"redeclaration differs at {0}parameter {1}",
209-
llvm::StringLiteral, int32_t);
210-
CARBON_DIAGNOSTIC(RedeclParamPrevious, Note,
211-
"previous declaration's corresponding {0}parameter here",
212-
llvm::StringLiteral);
211+
"redeclaration differs at {0:implicit |}parameter {1}",
212+
BoolAsSelect, int32_t);
213+
CARBON_DIAGNOSTIC(
214+
RedeclParamPrevious, Note,
215+
"previous declaration's corresponding {0:implicit |}parameter here",
216+
BoolAsSelect);
213217
context.emitter()
214-
.Build(new_param_pattern_id, RedeclParamDiffers, param_diag_label,
218+
.Build(new_param_pattern_id, RedeclParamDiffers, is_implicit_param,
215219
param_index + 1)
216-
.Note(prev_param_pattern_id, RedeclParamPrevious, param_diag_label)
220+
.Note(prev_param_pattern_id, RedeclParamPrevious, is_implicit_param)
217221
.Emit();
218222
};
219223

@@ -265,7 +269,7 @@ static auto CheckRedeclParams(Context& context, SemIRLoc new_decl_loc,
265269
SemIR::InstBlockId new_param_patterns_id,
266270
SemIRLoc prev_decl_loc,
267271
SemIR::InstBlockId prev_param_patterns_id,
268-
llvm::StringLiteral param_diag_label,
272+
bool is_implicit_param,
269273
SemIR::SpecificId prev_specific_id, bool diagnose)
270274
-> bool {
271275
// This will often occur for empty params.
@@ -279,18 +283,18 @@ static auto CheckRedeclParams(Context& context, SemIRLoc new_decl_loc,
279283
return false;
280284
}
281285
CARBON_DIAGNOSTIC(RedeclParamListDiffers, Error,
282-
"redeclaration differs because of {1}{0}parameter list",
283-
llvm::StringLiteral, llvm::StringLiteral);
286+
"redeclaration differs because of "
287+
"{1:'|missing '}{0:implicit |}parameter list",
288+
BoolAsSelect, BoolAsSelect);
284289
CARBON_DIAGNOSTIC(RedeclParamListPrevious, Note,
285-
"previously declared with{1} {0}parameter list",
286-
llvm::StringLiteral, llvm::StringLiteral);
290+
"previously declared "
291+
"{1:with|without} {0:implicit |}parameter list",
292+
BoolAsSelect, BoolAsSelect);
287293
context.emitter()
288-
.Build(new_decl_loc, RedeclParamListDiffers, param_diag_label,
289-
new_param_patterns_id.is_valid() ? llvm::StringLiteral("")
290-
: "missing ")
291-
.Note(
292-
prev_decl_loc, RedeclParamListPrevious, param_diag_label,
293-
prev_param_patterns_id.is_valid() ? llvm::StringLiteral("") : "out")
294+
.Build(new_decl_loc, RedeclParamListDiffers, is_implicit_param,
295+
new_param_patterns_id.is_valid())
296+
.Note(prev_decl_loc, RedeclParamListPrevious, is_implicit_param,
297+
prev_param_patterns_id.is_valid())
294298
.Emit();
295299
return false;
296300
}
@@ -307,22 +311,23 @@ static auto CheckRedeclParams(Context& context, SemIRLoc new_decl_loc,
307311
}
308312
CARBON_DIAGNOSTIC(
309313
RedeclParamCountDiffers, Error,
310-
"redeclaration differs because of {0}parameter count of {1}",
311-
llvm::StringLiteral, int32_t);
312-
CARBON_DIAGNOSTIC(RedeclParamCountPrevious, Note,
313-
"previously declared with {0}parameter count of {1}",
314-
llvm::StringLiteral, int32_t);
314+
"redeclaration differs because of {0:implicit |}parameter count of {1}",
315+
BoolAsSelect, int32_t);
316+
CARBON_DIAGNOSTIC(
317+
RedeclParamCountPrevious, Note,
318+
"previously declared with {0:implicit |}parameter count of {1}",
319+
BoolAsSelect, int32_t);
315320
context.emitter()
316-
.Build(new_decl_loc, RedeclParamCountDiffers, param_diag_label,
321+
.Build(new_decl_loc, RedeclParamCountDiffers, is_implicit_param,
317322
new_param_pattern_ids.size())
318-
.Note(prev_decl_loc, RedeclParamCountPrevious, param_diag_label,
323+
.Note(prev_decl_loc, RedeclParamCountPrevious, is_implicit_param,
319324
prev_param_pattern_ids.size())
320325
.Emit();
321326
return false;
322327
}
323328
for (auto [index, new_param_pattern_id, prev_param_pattern_id] :
324329
llvm::enumerate(new_param_pattern_ids, prev_param_pattern_ids)) {
325-
if (!CheckRedeclParam(context, param_diag_label, index,
330+
if (!CheckRedeclParam(context, is_implicit_param, index,
326331
new_param_pattern_id, prev_param_pattern_id,
327332
prev_specific_id, diagnose)) {
328333
return false;
@@ -410,15 +415,16 @@ auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
410415
EntityHasParamError(context, prev_entity)) {
411416
return false;
412417
}
413-
if (!CheckRedeclParams(context, new_entity.loc,
414-
new_entity.implicit_param_patterns_id, prev_entity.loc,
415-
prev_entity.implicit_param_patterns_id, "implicit ",
416-
prev_specific_id, diagnose)) {
418+
if (!CheckRedeclParams(
419+
context, new_entity.loc, new_entity.implicit_param_patterns_id,
420+
prev_entity.loc, prev_entity.implicit_param_patterns_id,
421+
/*is_implicit_param=*/true, prev_specific_id, diagnose)) {
417422
return false;
418423
}
419424
if (!CheckRedeclParams(context, new_entity.loc, new_entity.param_patterns_id,
420-
prev_entity.loc, prev_entity.param_patterns_id, "",
421-
prev_specific_id, diagnose)) {
425+
prev_entity.loc, prev_entity.param_patterns_id,
426+
/*is_implicit_param=*/false, prev_specific_id,
427+
diagnose)) {
422428
return false;
423429
}
424430
if (check_syntax &&

toolchain/diagnostics/BUILD

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ cc_library(
2525
],
2626
deps = [
2727
":diagnostic_kind",
28+
":format_providers",
2829
"//common:check",
2930
"//common:ostream",
3031
"@llvm-project//llvm:Support",
@@ -56,6 +57,31 @@ cc_library(
5657
],
5758
)
5859

60+
cc_library(
61+
name = "format_providers",
62+
srcs = ["format_providers.cpp"],
63+
hdrs = ["format_providers.h"],
64+
deps = [
65+
"//common:check",
66+
"//common:ostream",
67+
"@llvm-project//llvm:Support",
68+
],
69+
)
70+
71+
cc_test(
72+
name = "format_providers_test",
73+
size = "small",
74+
srcs = ["format_providers_test.cpp"],
75+
deps = [
76+
":diagnostic_emitter",
77+
":format_providers",
78+
":mocks",
79+
"//testing/base:gtest_main",
80+
"@googletest//:gtest",
81+
"@llvm-project//llvm:Support",
82+
],
83+
)
84+
5985
cc_library(
6086
name = "null_diagnostics",
6187
hdrs = ["null_diagnostics.h"],

0 commit comments

Comments
 (0)