Improve precedence computation in stringifier. (#7833)

Assisted-by: Claude via Antigravity
This commit is contained in:
Richard Smith
2026-09-29 00:55:21 +00:00
committed by GitHub
parent b0bc5ed338
commit 8ac36e7daa
3 changed files with 133 additions and 13 deletions
+39
View File
@@ -127,6 +127,45 @@ fn PassConstReferenceToReference(p: const X*) {
p->(X.TakeSelf)();
}
// --- fail_print_const_facet_type.carbon
library "[[@TEST_NAME]]";
interface I { let T: type; }
interface J {}
// A redeclaration with a mismatched return type prints the original return
// type. The operand of `const` is parenthesized if it's a facet type formed
// with `&` or `where`.
fn F() -> const (I & J);
// CHECK:STDERR: fail_print_const_facet_type.carbon:[[@LINE+7]]:1: error: function redeclaration differs because return type is `{}` [FunctionRedeclReturnTypeDiffers]
// CHECK:STDERR: fn F() -> {};
// CHECK:STDERR: ^~~~~~~~~~~~~
// CHECK:STDERR: fail_print_const_facet_type.carbon:[[@LINE-4]]:1: note: previously declared with return type `const (I & J)` [FunctionRedeclReturnTypePrevious]
// CHECK:STDERR: fn F() -> const (I & J);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
fn F() -> {};
fn G() -> const (I where .T = ());
// CHECK:STDERR: fail_print_const_facet_type.carbon:[[@LINE+7]]:1: error: function redeclaration differs because return type is `{}` [FunctionRedeclReturnTypeDiffers]
// CHECK:STDERR: fn G() -> {};
// CHECK:STDERR: ^~~~~~~~~~~~~
// CHECK:STDERR: fail_print_const_facet_type.carbon:[[@LINE-4]]:1: note: previously declared with return type `const (I where .(I.T) = ())` [FunctionRedeclReturnTypePrevious]
// CHECK:STDERR: fn G() -> const (I where .T = ());
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
fn G() -> {};
// No parentheses are needed for a single interface.
fn H() -> const I;
// CHECK:STDERR: fail_print_const_facet_type.carbon:[[@LINE+7]]:1: error: function redeclaration differs because return type is `{}` [FunctionRedeclReturnTypeDiffers]
// CHECK:STDERR: fn H() -> {};
// CHECK:STDERR: ^~~~~~~~~~~~~
// CHECK:STDERR: fail_print_const_facet_type.carbon:[[@LINE-4]]:1: note: previously declared with return type `const I` [FunctionRedeclReturnTypePrevious]
// CHECK:STDERR: fn H() -> const I;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
fn H() -> {};
// CHECK:STDOUT: --- basic.carbon
// CHECK:STDOUT:
+18
View File
@@ -145,6 +145,24 @@ fn UseM(generic c: C, x: c.M(true));
// CHECK:STDERR:
fn UseM(generic c: C, x: bool);
// Parentheses are added around the `self` argument if needed.
interface J {
eval fn N(self: Self) -> type;
}
impl type as J {
eval fn N(self: Self) -> type { return self; }
}
fn UseN(generic T: type, x: (const T).(J.N)());
// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE+7]]:26: error: type `<pattern for bool>` of parameter 2 in redeclaration differs from previous parameter type `<pattern for (const T).N()>` [RedeclParamDiffersType]
// CHECK:STDERR: fn UseN(generic T: type, x: bool);
// CHECK:STDERR: ^~~~~~~
// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE-4]]:26: note: previous declaration's corresponding parameter here [RedeclParamPrevious]
// CHECK:STDERR: fn UseN(generic T: type, x: (const T).(J.N)());
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
fn UseN(generic T: type, x: bool);
// CHECK:STDOUT: --- dependent_symbolic_instruction.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
+76 -13
View File
@@ -25,18 +25,82 @@
namespace Carbon::SemIR {
// Map an instruction kind representing an expression into an integer describing
// the precedence of that expression's syntax. Higher numbers correspond to
// higher precedence.
static auto GetPrecedence(InstKind kind) -> int {
if (kind == ConstType::Kind) {
return -1;
// Precedence levels for the syntax produced when stringifying an instruction.
// Higher numbers correspond to higher precedence. An operand needs to be
// parenthesized if its precedence is lower than that required by the enclosing
// syntax.
//
// Carbon's precedence is a partial order, not a total order, but the only
// enclosing syntax that we currently check for is prefix and postfix operators,
// which have higher precedence than all infix operators, so a total order
// suffices for now.
enum class Precedence : int8_t {
// `A where ...`
Where = -5,
// `A & B`
BitwiseAnd = -4,
// `A as B`
As = -3,
// `T*`
PostfixStar = -2,
// `const T`, `partial T`
PrefixType = -1,
// Names, literals, calls, member access, and anything parenthesized or
// otherwise bracketed.
Primary = 0,
};
// Returns the precedence of the syntax that stringifying `inst_id` produces.
static auto GetPrecedence(const File& sem_ir, InstId inst_id) -> Precedence {
while (inst_id.has_value()) {
auto inst = sem_ir.insts().Get(inst_id);
switch (inst.kind()) {
case Call::Kind:
case TypeOfInst::Kind: {
// These print their constant value instead, if it's different.
auto const_inst_id =
sem_ir.constant_values().GetConstantInstId(inst_id);
if (!const_inst_id.has_value() || const_inst_id == inst_id) {
return Precedence::Primary;
}
inst_id = const_inst_id;
break;
}
case FacetAccessType::Kind: {
inst_id = inst.As<FacetAccessType>().facet_value_inst_id;
break;
}
case FacetType::Kind: {
const auto& info = sem_ir.declared_facet_types().Get(
inst.As<FacetType>().declared_facet_type_id);
if (info.other_requirements || !info.rewrite_constraints.empty() ||
!info.self_impls_constraints.empty() ||
!info.self_impls_named_constraints.empty() ||
!info.type_impls_interfaces.empty() ||
!info.type_impls_named_constraints.empty()) {
return Precedence::Where;
}
if (info.extend_constraints.size() +
info.extend_named_constraints.size() >
1) {
return Precedence::BitwiseAnd;
}
return Precedence::Primary;
}
case FacetValue::Kind:
case LookupImplWitness::Kind:
return Precedence::As;
case PointerType::Kind:
return Precedence::PostfixStar;
case ConstType::Kind:
case PartialType::Kind:
return Precedence::PrefixType;
default:
// TODO: Handle other kinds of expressions with precedence.
return Precedence::Primary;
}
}
if (kind == PointerType::Kind) {
return -2;
}
// TODO: Handle other kinds of expressions with precedence.
return 0;
return Precedence::Primary;
}
namespace {
@@ -532,8 +596,7 @@ class Stringifier {
}
// Add parentheses if required.
if (GetPrecedence(sem_ir_->insts().Get(inner_id).kind()) <
GetPrecedence(ConstType::Kind)) {
if (GetPrecedence(*sem_ir_, inner_id) < Precedence::PrefixType) {
*out_ << "(";
// Note the `inner_id` ends up here.
step_stack_->PushString(")");