diff --git a/toolchain/check/testdata/const/basics.carbon b/toolchain/check/testdata/const/basics.carbon index ae45324a465c..106f69b659e6 100644 --- a/toolchain/check/testdata/const/basics.carbon +++ b/toolchain/check/testdata/const/basics.carbon @@ -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: diff --git a/toolchain/check/testdata/eval/symbolic.carbon b/toolchain/check/testdata/eval/symbolic.carbon index 284b77718348..26f1baebfbf5 100644 --- a/toolchain/check/testdata/eval/symbolic.carbon +++ b/toolchain/check/testdata/eval/symbolic.carbon @@ -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 `` of parameter 2 in redeclaration differs from previous parameter type `` [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 { diff --git a/toolchain/sem_ir/stringify.cpp b/toolchain/sem_ir/stringify.cpp index 2927f66187b0..f0035beed8cb 100644 --- a/toolchain/sem_ir/stringify.cpp +++ b/toolchain/sem_ir/stringify.cpp @@ -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().facet_value_inst_id; + break; + } + case FacetType::Kind: { + const auto& info = sem_ir.declared_facet_types().Get( + inst.As().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(")");