diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 2cd46932b232..17003c3c57b1 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -50,6 +50,15 @@ static auto GetPhase(SemIR::ConstantId constant_id) -> Phase { } } +// Gets the earliest possible phase for a constant whose type is `type_id`. The +// type of a constant is effectively treated as an operand of that constant when +// determining its phase. For example, an empty struct with a symbolic type is a +// symbolic constant, not a template constant. +static auto GetTypePhase(Context& context, SemIR::TypeId type_id) -> Phase { + CARBON_CHECK(type_id.is_valid()); + return GetPhase(context.types().GetConstantId(type_id)); +} + // Returns the later of two phases. static auto LatestPhase(Phase a, Phase b) -> Phase { return static_cast( @@ -200,7 +209,11 @@ static auto RebuildAndValidateIfFieldsAreConstant( // Build a constant instruction by replacing each non-constant operand with // its constant value. auto typed_inst = inst.As(); - Phase phase = Phase::Template; + // Some instruction kinds don't have a `type_id` field. For those that do, the + // type contributes to the phase. + Phase phase = inst.type_id().is_valid() + ? GetTypePhase(context, inst.type_id()) + : Phase::Template; if ((ReplaceFieldWithConstantValue(context, &typed_inst, each_field_id, &phase) && ...)) { @@ -227,7 +240,7 @@ static auto RebuildInitAsValue(Context& context, SemIR::Inst inst, SemIR::InstKind value_kind) -> SemIR::ConstantId { auto init_inst = inst.As(); - Phase phase = Phase::Template; + Phase phase = GetTypePhase(context, init_inst.type_id); auto elements_id = GetConstantValue(context, init_inst.elements_id, &phase); return MakeConstantResult( context, @@ -1035,7 +1048,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst) context, SemIR::StructValue{.type_id = fn_decl.type_id, .elements_id = SemIR::InstBlockId::Empty}, - Phase::Template); + GetTypePhase(context, fn_decl.type_id)); } case CARBON_KIND(SemIR::ClassDecl class_decl): { @@ -1046,7 +1059,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst) context, SemIR::StructValue{.type_id = class_decl.type_id, .elements_id = SemIR::InstBlockId::Empty}, - Phase::Template); + GetTypePhase(context, class_decl.type_id)); } // A non-generic class declaration evaluates to the class type. return MakeConstantResult( @@ -1063,7 +1076,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst) context, SemIR::StructValue{.type_id = interface_decl.type_id, .elements_id = SemIR::InstBlockId::Empty}, - Phase::Template); + GetTypePhase(context, interface_decl.type_id)); } // A non-generic interface declaration evaluates to the interface type. return MakeConstantResult( diff --git a/toolchain/check/subst.cpp b/toolchain/check/subst.cpp index 08ce8fcf9b70..821e04b41a9e 100644 --- a/toolchain/check/subst.cpp +++ b/toolchain/check/subst.cpp @@ -63,7 +63,9 @@ static auto PushOperand(Context& context, Worklist& worklist, worklist.Push(static_cast(arg)); break; case SemIR::IdKind::For: - worklist.Push(context.types().GetInstId(static_cast(arg))); + if (auto type_id = static_cast(arg); type_id.is_valid()) { + worklist.Push(context.types().GetInstId(type_id)); + } break; case SemIR::IdKind::For: for (auto inst_id : @@ -88,6 +90,8 @@ static auto ExpandOperands(Context& context, Worklist& worklist, SemIR::InstId inst_id) -> void { auto inst = context.insts().Get(inst_id); auto kinds = inst.ArgKinds(); + PushOperand(context, worklist, SemIR::IdKind::For, + inst.type_id().index); PushOperand(context, worklist, kinds.first, inst.arg0()); PushOperand(context, worklist, kinds.second, inst.arg1()); } @@ -98,8 +102,13 @@ static auto PopOperand(Context& context, Worklist& worklist, SemIR::IdKind kind, switch (kind) { case SemIR::IdKind::For: return worklist.Pop().index; - case SemIR::IdKind::For: + case SemIR::IdKind::For: { + auto type_id = static_cast(arg); + if (!type_id.is_valid()) { + return arg; + } return context.GetTypeIdForTypeInst(worklist.Pop()).index; + } case SemIR::IdKind::For: { auto old_inst_block_id = static_cast(arg); auto size = context.inst_blocks().Get(old_inst_block_id).size(); @@ -136,14 +145,16 @@ static auto Rebuild(Context& context, Worklist& worklist, SemIR::InstId inst_id) // Note that we pop in reverse order because we pushed them in forwards order. int32_t arg1 = PopOperand(context, worklist, kinds.second, inst.arg1()); int32_t arg0 = PopOperand(context, worklist, kinds.first, inst.arg0()); - if (arg0 == inst.arg0() && arg1 == inst.arg1()) { + int32_t type_id = + PopOperand(context, worklist, SemIR::IdKind::For, + inst.type_id().index); + if (type_id == inst.type_id().index && arg0 == inst.arg0() && + arg1 == inst.arg1()) { return inst_id; } - // TODO: Updating the arguments might result in the instruction having a - // different type. We should consider either recomputing the type or - // substituting into it. In the latter case, consider caching, as we may - // substitute into related types repeatedly. + // TODO: Do we need to require this type to be complete? + inst.SetType(SemIR::TypeId(type_id)); inst.SetArgs(arg0, arg1); auto result_id = TryEvalInst(context, SemIR::InstId::Invalid, inst); CARBON_CHECK(result_id.is_constant()) @@ -165,6 +176,7 @@ auto SubstConstant(Context& context, SemIR::ConstantId const_id, return const_id; } + // TODO: Consider caching; we may perform the same substitutions repeatedly. Worklist worklist(context.constant_values().GetInstId(const_id)); // For each instruction that forms part of the constant, we will visit it diff --git a/toolchain/check/testdata/array/generic_empty.carbon b/toolchain/check/testdata/array/generic_empty.carbon new file mode 100644 index 000000000000..1d918824cc50 --- /dev/null +++ b/toolchain/check/testdata/array/generic_empty.carbon @@ -0,0 +1,54 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/array/generic_empty.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/array/generic_empty.carbon + +fn G(T:! type) { + // We can initialize this without knowing T. + var arr: [T; 0] = (); +} + +// CHECK:STDOUT: --- generic_empty.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %T: type = bind_symbolic_name T 0 [symbolic] +// CHECK:STDOUT: %G.type: type = fn_type @G [template] +// CHECK:STDOUT: %.1: type = tuple_type () [template] +// CHECK:STDOUT: %G: %G.type = struct_value () [template] +// CHECK:STDOUT: %.2: i32 = int_literal 0 [template] +// CHECK:STDOUT: %.3: type = array_type %.2, %T [symbolic] +// CHECK:STDOUT: %.4: type = ptr_type %.3 [symbolic] +// CHECK:STDOUT: %array: %.3 = tuple_value () [symbolic] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Core = %Core +// CHECK:STDOUT: .G = %G.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core: = namespace [template] {} +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [template = constants.%G] { +// CHECK:STDOUT: %T.loc11_6.1: type = param T +// CHECK:STDOUT: @G.%T: type = bind_symbolic_name T 0, %T.loc11_6.1 [symbolic = constants.%T] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%T: type) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %T.ref: type = name_ref T, %T [symbolic = constants.%T] +// CHECK:STDOUT: %.loc13_16: i32 = int_literal 0 [template = constants.%.2] +// CHECK:STDOUT: %.loc13_17: type = array_type %.loc13_16, %T [symbolic = constants.%.3] +// CHECK:STDOUT: %arr.var: ref %.3 = var arr +// CHECK:STDOUT: %arr: ref %.3 = bind_name arr, %arr.var +// CHECK:STDOUT: %.loc13_22.1: %.1 = tuple_literal () +// CHECK:STDOUT: %.loc13_22.2: init %.3 = array_init () to %arr.var [symbolic = constants.%array] +// CHECK:STDOUT: %.loc13_23: init %.3 = converted %.loc13_22.1, %.loc13_22.2 [symbolic = constants.%array] +// CHECK:STDOUT: assign %arr.var, %.loc13_23 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/sem_ir/inst.h b/toolchain/sem_ir/inst.h index 72b6d9ed7958..edd867a8d5eb 100644 --- a/toolchain/sem_ir/inst.h +++ b/toolchain/sem_ir/inst.h @@ -237,6 +237,9 @@ class Inst : public Printable { // such argument. auto arg1() const -> int32_t { return arg1_; } + // Sets the type of this instruction. + auto SetType(TypeId type_id) { type_id_ = type_id; } + // Sets the arguments of this instruction. auto SetArgs(int32_t arg0, int32_t arg1) { arg0_ = arg0;