From 1b2ae912fcee44bae41c018e9b7539c4a069926a Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 10 Feb 2026 18:08:16 -0800 Subject: [PATCH] Add basic support for `eval fn` and `musteval fn`. (#6694) Add support for compile-time functions. `eval fn` is analogous to C++ `constexpr`, and is evaluated at compile time when it has compile-time arguments. `musteval fn` is analogous to C++ `consteval`, and requires that its arguments be available at compile time and is always evaluated at compile time. For now we require the modifier to match across redeclarations of the function. The specific modifier syntax here is a placeholder and not yet part of an approved design. Limitations: Only very basic support for evaluation is provided. So far there's no support for mutable state or `if` expressions, but otherwise control flow and passing and returning values should work. Carbon evaluation recursion is modeled by C++ recursion for now, so you can overflow the toolchain stack easily. Functions that use in-place initialization will generally not work yet, as they are modeled as passing a non-compile-time-constant reference to a temporary to the call. Add missing categorization of `name_binding_decl` as `NotExpr` to match other similar declaration instructions like `FunctionDecl`, so that we can uniformly skip over them when they occur within function bodies. Assisted-by: Gemini 3 Pro and Flash via Antigravity --- toolchain/check/decl_introducer_state.h | 4 +- toolchain/check/eval.cpp | 431 +++++++++++++++++- toolchain/check/function.cpp | 55 ++- toolchain/check/handle_function.cpp | 21 +- toolchain/check/handle_modifier.cpp | 12 +- toolchain/check/import_ref.cpp | 3 +- toolchain/check/keyword_modifier_set.h | 28 +- toolchain/check/modifiers.cpp | 2 + toolchain/check/testdata/eval/binding.carbon | 198 ++++++++ toolchain/check/testdata/eval/branch.carbon | 142 ++++++ toolchain/check/testdata/eval/call.carbon | 111 +++++ .../check/testdata/eval/recursion.carbon | 28 ++ toolchain/check/testdata/eval/symbolic.carbon | 52 +++ .../function/call/eval_musteval.carbon | 72 +++ .../function/declaration/eval_musteval.carbon | 172 +++++++ .../check/testdata/impl/eval_musteval.carbon | 232 ++++++++++ toolchain/check/thunk.cpp | 1 + toolchain/diagnostics/format_providers.cpp | 21 +- .../diagnostics/format_providers_test.cpp | 31 ++ toolchain/diagnostics/kind.def | 3 + toolchain/lex/testdata/keywords.carbon | 7 + toolchain/lex/token_kind.def | 2 + toolchain/lower/file_context.cpp | 6 +- .../function/definition/eval_musteval.carbon | 115 +++++ toolchain/parse/node_kind.def | 2 + .../testdata/function/eval_musteval.carbon | 56 +++ toolchain/sem_ir/function.h | 7 + toolchain/sem_ir/type_iterator.cpp | 4 + toolchain/sem_ir/typed_insts.h | 3 + 29 files changed, 1779 insertions(+), 42 deletions(-) create mode 100644 toolchain/check/testdata/eval/binding.carbon create mode 100644 toolchain/check/testdata/eval/branch.carbon create mode 100644 toolchain/check/testdata/eval/call.carbon create mode 100644 toolchain/check/testdata/eval/recursion.carbon create mode 100644 toolchain/check/testdata/eval/symbolic.carbon create mode 100644 toolchain/check/testdata/function/call/eval_musteval.carbon create mode 100644 toolchain/check/testdata/function/declaration/eval_musteval.carbon create mode 100644 toolchain/check/testdata/impl/eval_musteval.carbon create mode 100644 toolchain/lower/testdata/function/definition/eval_musteval.carbon create mode 100644 toolchain/parse/testdata/function/eval_musteval.carbon diff --git a/toolchain/check/decl_introducer_state.h b/toolchain/check/decl_introducer_state.h index 618e28fbc5b2..53f56d0f4099 100644 --- a/toolchain/check/decl_introducer_state.h +++ b/toolchain/check/decl_introducer_state.h @@ -29,9 +29,9 @@ struct DeclIntroducerState { // Nodes of modifiers on this declaration, in expected order. `None` if no // modifier of that kind is present. Parse::NodeId - ordered_modifier_node_ids[static_cast(ModifierOrder::Decl) + 1] = + ordered_modifier_node_ids[static_cast(ModifierOrder::Last) + 1] = {Parse::NodeId::None, Parse::NodeId::None, Parse::NodeId::None, - Parse::NodeId::None}; + Parse::NodeId::None, Parse::NodeId::None}; // Invariant: contains just the modifiers represented by `saw_*_modifier`. KeywordModifierSet modifier_set = KeywordModifierSet(); diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 7b4f5b005b57..b86ca95a6324 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -48,6 +48,21 @@ struct SpecificEvalInfo { llvm::ArrayRef values; }; +// Information about a local scope that we're currently evaluating, such as a +// call to an `eval fn`. In this scope, instructions with runtime phase may +// locally have constant values, for example values that are computed from the +// arguments to the call. These values are specific to the current evaluation +// and not global properties of the instruction. +struct LocalEvalInfo { + // A mapping from instructions with runtime phase within the local scope to + // the values that they have in the current evaluation. This is populated as + // the local scope is evaluated, and due to control flow, the same instruction + // may have its value set multiple times. This map tracks the most recent + // value that the instruction had, which is the one that a reference to it in + // well-formed SemIR should refer to. + Map* locals; +}; + // Information about the context within which we are performing evaluation. // `context` must not be null. class EvalContext { @@ -61,6 +76,9 @@ class EvalContext { specific_id_(specific_id), specific_eval_info_(specific_eval_info) {} + EvalContext(const EvalContext&) = delete; + auto operator=(const EvalContext&) -> EvalContext& = delete; + // Gets the location to use for diagnostics if a better location is // unavailable. // TODO: This is also sometimes unavailable. @@ -129,6 +147,18 @@ class EvalContext { // Gets the constant value of the specified instruction in this context. auto GetConstantValue(SemIR::InstId inst_id) -> SemIR::ConstantId { auto const_id = constant_values().GetAttached(inst_id); + + // While evaluating a function, map from local non-constant instructions to + // their earlier-evaluated values. + if (!const_id.is_constant()) { + if (local_eval_info_) { + if (auto local = local_eval_info_->locals->Lookup(inst_id)) { + return local.value(); + } + } + return const_id; + } + if (!const_id.is_symbolic()) { return const_id; } @@ -212,6 +242,20 @@ class EvalContext { auto emitter() -> DiagnosticEmitterBase& { return context().emitter(); } + protected: + explicit EvalContext(Context* context, SemIR::LocId fallback_loc_id, + SemIR::SpecificId specific_id, + std::optional local_eval_info) + : context_(context), + fallback_loc_id_(fallback_loc_id), + specific_id_(specific_id), + local_eval_info_(local_eval_info) {} + + // Returns the current locals map, which is assumed to exist. + auto locals() -> Map& { + return *local_eval_info_->locals; + } + private: // The type-checking context in which we're performing evaluation. Context* context_; @@ -222,6 +266,10 @@ class EvalContext { // If we are currently evaluating an eval block for `specific_id_`, // information about that evaluation. std::optional specific_eval_info_; + // If we are currently evaluating within a local scope, values of local + // instructions that have already been evaluated. This is here rather than in + // `FunctionEvalContext` so we can reference it from `GetConstantValue`. + std::optional local_eval_info_; }; } // namespace @@ -312,6 +360,17 @@ static auto MakeNonConstantResult(Phase phase) -> SemIR::ConstantId { : SemIR::ConstantId::NotConstant; } +// Forms a constant for an empty tuple value. +static auto MakeEmptyTupleResult(EvalContext& eval_context) + -> SemIR::ConstantId { + auto type_id = GetTupleType(eval_context.context(), {}); + return MakeConstantResult( + eval_context.context(), + SemIR::TupleValue{.type_id = type_id, + .elements_id = SemIR::InstBlockId::Empty}, + Phase::Concrete); +} + // Converts a bool value into a ConstantId. static auto MakeBoolResult(Context& context, SemIR::TypeId bool_type_id, bool result) -> SemIR::ConstantId { @@ -377,6 +436,18 @@ static auto GetConstantValue(EvalContext& eval_context, SemIR::InstId inst_id, return eval_context.constant_values().GetInstId(const_id); } +// Issue a suitable diagnostic for an instruction that evaluated to a +// non-constant value but was required to evaluate to a constant. +static auto DiagnoseNonConstantValue(EvalContext& eval_context, + SemIR::InstId inst_id) -> void { + if (inst_id != SemIR::ErrorInst::InstId) { + CARBON_DIAGNOSTIC(EvalRequiresConstantValue, Error, + "expression is runtime; expected constant"); + eval_context.emitter().Emit(eval_context.GetDiagnosticLoc({inst_id}), + EvalRequiresConstantValue); + } +} + // Gets a constant value for an `inst_id`, diagnosing when the input is not a // constant value. static auto RequireConstantValue(EvalContext& eval_context, @@ -392,12 +463,7 @@ static auto RequireConstantValue(EvalContext& eval_context, return eval_context.constant_values().GetInstId(const_id); } - if (inst_id != SemIR::ErrorInst::InstId) { - CARBON_DIAGNOSTIC(EvalRequiresConstantValue, Error, - "expression is runtime; expected constant"); - eval_context.emitter().Emit(eval_context.GetDiagnosticLoc({inst_id}), - EvalRequiresConstantValue); - } + DiagnoseNonConstantValue(eval_context, inst_id); *phase = Phase::UnknownDueToError; return SemIR::ErrorInst::InstId; } @@ -429,6 +495,22 @@ static auto RequireConstantValueIgnoringPeriodSelf(EvalContext& eval_context, return const_inst_id; } +// Gets a constant value for an `inst_id`, diagnosing when the input is not +// constant, and CHECKing that it is concrete. Should only be used in contexts +// where non-concrete constants cannot appear. +static auto CheckConcreteValue(EvalContext& eval_context, SemIR::InstId inst_id) + -> SemIR::InstId { + auto phase = Phase::Concrete; + auto value_inst_id = RequireConstantValue(eval_context, inst_id, &phase); + if (phase == Phase::UnknownDueToError) { + return SemIR::ErrorInst::InstId; + } + CARBON_CHECK(phase == Phase::Concrete, + "expression evaluates to symbolic value {0}", + eval_context.insts().Get(value_inst_id)); + return value_inst_id; +} + // Find the instruction that the given instruction instantiates to, and return // that. static auto GetConstantValue(EvalContext& eval_context, @@ -1689,13 +1771,7 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context, CARBON_FATAL("Not a builtin function."); case SemIR::BuiltinFunctionKind::NoOp: { - // Return an empty tuple value. - auto type_id = GetTupleType(eval_context.context(), {}); - return MakeConstantResult( - eval_context.context(), - SemIR::TupleValue{.type_id = type_id, - .elements_id = SemIR::InstBlockId::Empty}, - phase); + return MakeEmptyTupleResult(eval_context); } case SemIR::BuiltinFunctionKind::PrimitiveCopy: { @@ -2001,6 +2077,11 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context, return SemIR::ConstantId::NotConstant; } +static auto TryEvalCall(EvalContext& outer_eval_context, SemIR::LocId loc_id, + const SemIR::Function& function, + SemIR::SpecificId specific_id, + SemIR::InstBlockId args_id) -> SemIR::ConstantId; + // Makes a constant for a call instruction. static auto MakeConstantForCall(EvalContext& eval_context, SemIR::InstId inst_id, SemIR::Call call) @@ -2020,14 +2101,17 @@ static auto MakeConstantForCall(EvalContext& eval_context, eval_context, &call, &SemIR::Call::callee_id, &phase); auto callee = SemIR::GetCallee(eval_context.sem_ir(), call.callee_id); + const SemIR::Function* function = nullptr; auto builtin_kind = SemIR::BuiltinFunctionKind::None; - if (auto* fn = std::get_if(&callee)) { - // Calls to builtins might be constant. - builtin_kind = - eval_context.functions().Get(fn->function_id).builtin_function_kind(); - if (builtin_kind == SemIR::BuiltinFunctionKind::None) { - // TODO: Eventually we'll want to treat some kinds of non-builtin - // functions as producing constants. + auto evaluation_mode = SemIR::Function::EvaluationMode::None; + if (auto* callee_function = std::get_if(&callee)) { + function = &eval_context.functions().Get(callee_function->function_id); + builtin_kind = function->builtin_function_kind(); + evaluation_mode = function->evaluation_mode; + // Calls to builtins and to `eval` or `musteval` functions might be + // constant. + if (builtin_kind == SemIR::BuiltinFunctionKind::None && + evaluation_mode == SemIR::Function::EvaluationMode::None) { return SemIR::ConstantId::NotConstant; } } else { @@ -2050,7 +2134,8 @@ static auto MakeConstantForCall(EvalContext& eval_context, if (!has_constant_operands) { if (builtin_kind.IsCompTimeOnly( eval_context.sem_ir(), eval_context.inst_blocks().Get(call.args_id), - call.type_id)) { + call.type_id) || + evaluation_mode == SemIR::Function::EvaluationMode::MustEval) { CARBON_DIAGNOSTIC(NonConstantCallToCompTimeOnlyFunction, Error, "non-constant call to compile-time-only function"); CARBON_DIAGNOSTIC(CompTimeOnlyFunctionHere, Note, @@ -2072,6 +2157,24 @@ static auto MakeConstantForCall(EvalContext& eval_context, eval_context.inst_blocks().Get(call.args_id), phase); } + // Handle calls to `eval` and `musteval` functions. + if (evaluation_mode != SemIR::Function::EvaluationMode::None) { + // A non-concrete call to `eval` or `musteval` is a template symbolic + // constant, regardless of the phase of the arguments. + if (phase != Phase::Concrete) { + CARBON_CHECK(phase <= Phase::TemplateSymbolic); + return MakeConstantResult(eval_context.context(), call, + Phase::TemplateSymbolic); + } + + // TODO: Instead of performing the call immediately, add it to a work queue + // and do it non-recursively. + return TryEvalCall( + eval_context, SemIR::LocId(inst_id), *function, + std::get(callee).resolved_specific_id, + call.args_id); + } + return SemIR::ConstantId::NotConstant; } @@ -2463,4 +2566,290 @@ auto TryEvalBlockForSpecific(Context& context, SemIR::LocId loc_id, return context.inst_blocks().Add(result); } +// Information about the function call we are currently executing. Unlike +// evaluation, execution sequentially interprets instructions, and can handle +// control flow and (eventually) side effects and mutable state. +class FunctionExecContext : public EvalContext { + public: + FunctionExecContext(Context* context, SemIR::LocId loc_id, + SemIR::SpecificId specific_id, + Map* locals, + SemIR::InstBlockId args_id) + : EvalContext(context, loc_id, specific_id, + LocalEvalInfo{.locals = locals}), + args_(context->inst_blocks().Get(args_id)) {} + + // Returns the instructions to execute to complete this function invocation. + // In order to support `splice_block`, this is a stack of blocks. When the + // innermost block is complete, it will be popped and the next outer block + // will execute. + auto blocks() -> llvm::SmallVectorImpl>& { + return blocks_; + } + + // Returns the argument values supplied in the call to the function. + auto args() const -> llvm::ArrayRef { return args_; } + + using EvalContext::locals; + + private: + llvm::SmallVector, 4> blocks_; + llvm::ArrayRef args_; +}; + +// Handles the result of executing an instruction in a function. Returns an +// error the result is not a constant, and otherwise updates the locals map to +// track the result as an input to later evaluations in this function and +// returns None. +static auto HandleExecResult(FunctionExecContext& eval_context, + SemIR::InstId inst_id, SemIR::ConstantId const_id) + -> SemIR::ConstantId { + if (!const_id.has_value() || !const_id.is_constant()) { + DiagnoseNonConstantValue(eval_context, inst_id); + return SemIR::ErrorInst::ConstantId; + } + if (const_id == SemIR::ErrorInst::ConstantId) { + return const_id; + } + eval_context.locals().Update(inst_id, const_id); + return SemIR::ConstantId::None; +} + +// Executes an instruction for TryEvalCall. By default, performs normal +// evaluation of the instruction within a context that supplies the values +// produced by executing prior instructions in this function execution. This is +// specialized for instructions that have special handling in function +// execution, such as those that access parameters or perform flow control. If +// execution should continue, returns `SemIR::ConstantId::None`, otherwise +// returns the result to produce for the enclosing function call, which should +// be either the returned value or an error. +template +static auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId inst_id, SemIR::Inst inst) + -> SemIR::ConstantId { + if constexpr (InstT::Kind.expr_category().TryAsFixedCategory() == + SemIR::ExprCategory::NotExpr) { + // Instructions in this category are assumed to not have a runtime effect. + // This includes some kinds of declaration. + return SemIR::ConstantId::None; + } + + if constexpr (InstT::Kind.constant_kind() != SemIR::InstConstantKind::Never) { + if (eval_context.constant_values().Get(inst_id).is_concrete()) { + // Instruction has a concrete constant value that doesn't depend on the + // context. We don't need to evaluate it again. + return SemIR::ConstantId::None; + } + } + + // Evaluate the instruction in the current context. + auto const_id = TryEvalTypedInst(eval_context, inst_id, inst); + return HandleExecResult(eval_context, inst_id, const_id); +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId /*inst_id*/, + SemIR::Inst inst) -> SemIR::ConstantId { + auto branch = inst.As(); + eval_context.blocks().back() = + eval_context.context().inst_blocks().Get(branch.target_id); + return SemIR::ConstantId::None; +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId /*inst_id*/, + SemIR::Inst inst) -> SemIR::ConstantId { + auto branch_if = inst.As(); + auto cond_id = CheckConcreteValue(eval_context, branch_if.cond_id); + if (cond_id == SemIR::ErrorInst::InstId) { + return SemIR::ErrorInst::ConstantId; + } + auto cond = eval_context.insts().GetAs(cond_id); + if (cond.value == SemIR::BoolValue::True) { + eval_context.blocks().back() = + eval_context.context().inst_blocks().Get(branch_if.target_id); + } + return SemIR::ConstantId::None; +} + +// TODO: BranchWithArg and BlockArg. + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId /*inst_id*/, + SemIR::Inst /*inst*/) + -> SemIR::ConstantId { + return MakeEmptyTupleResult(eval_context); +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId /*inst_id*/, + SemIR::Inst inst) + -> SemIR::ConstantId { + auto return_expr = inst.As(); + return eval_context.GetConstantValue(return_expr.expr_id); +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId inst_id, + SemIR::Inst inst) + -> SemIR::ConstantId { + auto return_slot = inst.As(); + // In the case where the function's return type is not in-place, the return + // slot will refer to an out parameter that doesn't have an argument. In that + // case, we don't have a constant value for storage_id. To handle this, copy + // the value directly from the locals map rather than using GetConstantValue. + // + // TODO: Remove this and use a normal call to `GetConstantValue` if we stop + // adding out parameters with no corresponding argument. + eval_context.locals().Insert( + inst_id, eval_context.locals().Lookup(return_slot.storage_id).value()); + return SemIR::ConstantId::None; +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId /*inst_id*/, + SemIR::Inst inst) + -> SemIR::ConstantId { + auto splice_block = inst.As(); + eval_context.blocks().push_back( + eval_context.context().inst_blocks().Get(splice_block.block_id)); + // TODO: Copy the values from the result_id instruction to the result of + // the splice_block instruction once the spliced block finishes. + return SemIR::ConstantId::None; +} + +// Executes the introduction of a parameter into the local scope. Copies the +// argument supplied by the caller for the parameter into the locals map. +static auto TryExecTypedParam(FunctionExecContext& eval_context, + SemIR::InstId inst_id, SemIR::Inst inst) + -> SemIR::ConstantId { + auto param = inst.As(); + CARBON_CHECK(static_cast(param.index.index) < + eval_context.args().size()); + eval_context.locals().Insert(inst_id, + eval_context.constant_values().Get( + eval_context.args()[param.index.index])); + return SemIR::ConstantId::None; +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId inst_id, SemIR::Inst inst) + -> SemIR::ConstantId { + auto param = inst.As(); + if (static_cast(param.index.index) >= eval_context.args().size()) { + // For return values that have a copy initializing representation, the SemIR + // has an OutParam with an index that has no corresponding argument. In that + // case, we do not have a constant value for the parameter, but this doesn't + // prevent the call from being constant. + // + // TODO: Remove this once we stop adding out parameters with no + // corresponding argument. + eval_context.locals().Insert(inst_id, SemIR::ConstantId::None); + return SemIR::ConstantId::None; + } + + return TryExecTypedParam(eval_context, inst_id, inst); +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId inst_id, SemIR::Inst inst) + -> SemIR::ConstantId { + return TryExecTypedParam(eval_context, inst_id, inst); +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId inst_id, + SemIR::Inst inst) + -> SemIR::ConstantId { + return TryExecTypedParam(eval_context, inst_id, inst); +} + +template <> +auto TryExecTypedInst(FunctionExecContext& eval_context, + SemIR::InstId inst_id, + SemIR::Inst inst) + -> SemIR::ConstantId { + auto value_binding = inst.As(); + auto local_value_id = eval_context.GetConstantValue(value_binding.value_id); + eval_context.locals().Insert(inst_id, local_value_id); + return SemIR::ConstantId::None; +} + +static auto TryExecInst(FunctionExecContext& eval_context, + SemIR::InstId inst_id, SemIR::Inst inst) + -> SemIR::ConstantId { + using ExecInstFn = auto(FunctionExecContext & eval_context, + SemIR::InstId inst_id, SemIR::Inst inst) + ->SemIR::ConstantId; + static constexpr ExecInstFn* ExecInstFns[] = { +#define CARBON_SEM_IR_INST_KIND(Kind) &TryExecTypedInst, +#include "toolchain/sem_ir/inst_kind.def" + }; + [[clang::musttail]] return ExecInstFns[inst.kind().AsInt()](eval_context, + inst_id, inst); +} + +// Evaluates a call to an `eval` or `musteval` function by executing the +// function body. +static auto TryEvalCall(EvalContext& outer_eval_context, SemIR::LocId loc_id, + const SemIR::Function& function, + SemIR::SpecificId specific_id, + SemIR::InstBlockId args_id) -> SemIR::ConstantId { + if (function.body_block_ids.empty()) { + // TODO: Diagnose this. + return SemIR::ConstantId::NotConstant; + } + + // TODO: Consider tracking the lowest and highest inst_id in the function and + // using an array instead of a map. We would still need a map for instantiated + // portions of a function template. + Map locals; + + FunctionExecContext eval_context(&outer_eval_context.context(), loc_id, + specific_id, &locals, args_id); + + Diagnostics::AnnotationScope annotate_diagnostics( + &eval_context.emitter(), [&](auto& builder) { + CARBON_DIAGNOSTIC(InCallToEvalFn, Note, "in call to {0} here", + SemIR::NameId); + builder.Note(loc_id, InCallToEvalFn, function.name_id); + }); + + // Execute the function decl block followed by the body. + auto push_block = [&](SemIR::InstBlockId block_id) { + eval_context.blocks().push_back( + eval_context.context().inst_blocks().Get(block_id)); + }; + push_block(function.body_block_ids.front()); + push_block(eval_context.insts() + .GetAs(function.definition_id) + .decl_block_id); + + // Execute the blocks. This is mostly expression evaluation, with special + // handling for control flow and parameters. + while (true) { + if (eval_context.blocks().back().empty()) { + eval_context.blocks().pop_back(); + CARBON_CHECK(!eval_context.blocks().empty(), "Fell off end of function"); + continue; + } + + auto inst_id = eval_context.blocks().back().consume_front(); + auto inst = eval_context.context().insts().Get(inst_id); + if (auto result = TryExecInst(eval_context, inst_id, inst); + result.has_value()) { + return result; + } + } +} + } // namespace Carbon::Check diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp index 70184f172a26..1cfcbe94548e 100644 --- a/toolchain/check/function.cpp +++ b/toolchain/check/function.cpp @@ -15,6 +15,7 @@ #include "toolchain/check/scope_stack.h" #include "toolchain/check/type.h" #include "toolchain/check/type_completion.h" +#include "toolchain/diagnostics/format_providers.h" #include "toolchain/sem_ir/builtin_function_kind.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/pattern.h" @@ -248,6 +249,49 @@ auto CheckFunctionReturnTypeMatches(Context& context, return true; } +// Checks that a function declaration's evaluation mode matches the previous +// declaration's evaluation mode. Returns `false` and optionally produces a +// diagnostic on mismatch. +static auto CheckFunctionEvaluationModeMatches( + Context& context, const SemIR::Function& new_function, + const SemIR::Function& prev_function, bool diagnose) -> bool { + if (prev_function.evaluation_mode == new_function.evaluation_mode) { + return true; + } + if (!diagnose) { + return false; + } + auto eval_mode_index = [](SemIR::Function::EvaluationMode mode) { + switch (mode) { + case SemIR::Function::EvaluationMode::None: + return 0; + case SemIR::Function::EvaluationMode::Eval: + return 1; + case SemIR::Function::EvaluationMode::MustEval: + return 2; + } + }; + auto prev_eval_mode_index = eval_mode_index(prev_function.evaluation_mode); + auto new_eval_mode_index = eval_mode_index(new_function.evaluation_mode); + CARBON_DIAGNOSTIC( + FunctionRedeclEvaluationModeDiffers, Error, + "function redeclaration differs because new function is " + "{0:=-1:not `eval`|=-2:not `musteval`|=1:`eval`|=2:`musteval`}", + Diagnostics::IntAsSelect); + CARBON_DIAGNOSTIC(FunctionRedeclEvaluationModePrevious, Note, + "previously {0:<0:not |:}declared as " + "{0:=-1:`eval`|=-2:`musteval`|=1:`eval`|=2:`musteval`}", + Diagnostics::IntAsSelect); + context.emitter() + .Build(new_function.latest_decl_id(), FunctionRedeclEvaluationModeDiffers, + new_eval_mode_index ? new_eval_mode_index : -prev_eval_mode_index) + .Note(prev_function.latest_decl_id(), + FunctionRedeclEvaluationModePrevious, + prev_eval_mode_index ? prev_eval_mode_index : -new_eval_mode_index) + .Emit(); + return false; +} + auto CheckFunctionTypeMatches(Context& context, const SemIR::Function& new_function, const SemIR::Function& prev_function, @@ -259,8 +303,15 @@ auto CheckFunctionTypeMatches(Context& context, diagnose, check_syntax, check_self)) { return false; } - return CheckFunctionReturnTypeMatches(context, new_function, prev_function, - prev_specific_id, diagnose); + if (!CheckFunctionReturnTypeMatches(context, new_function, prev_function, + prev_specific_id, diagnose)) { + return false; + } + if (!CheckFunctionEvaluationModeMatches(context, new_function, prev_function, + diagnose)) { + return false; + } + return true; } auto CheckFunctionReturnPatternType(Context& context, SemIR::LocId loc_id, diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 8c8c3d1671bf..c95d58d95dea 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -90,10 +90,11 @@ static auto DiagnoseModifiers(Context& context, std::optional parent_scope_inst, SemIR::InstId self_param_id) -> void { CheckAccessModifiersOnDecl(context, introducer, parent_scope_inst); - LimitModifiersOnDecl(context, introducer, - KeywordModifierSet::Access | KeywordModifierSet::Extern | - KeywordModifierSet::Method | - KeywordModifierSet::Interface); + LimitModifiersOnDecl( + context, introducer, + KeywordModifierSet::Access | KeywordModifierSet::Extern | + KeywordModifierSet::Export | KeywordModifierSet::Method | + KeywordModifierSet::Interface | KeywordModifierSet::Evaluation); RestrictExternModifierOnDecl(context, introducer, parent_scope_inst, is_definition); CheckMethodModifiersOnFunction(context, introducer, parent_scope_inst_id, @@ -128,6 +129,16 @@ static auto GetVirtualModifier(const KeywordModifierSet& modifier_set) .Default(SemIR::Function::VirtualModifier::None); } +// Returns the evaluation modifier as an enum. +static auto GetEvaluationMode(const KeywordModifierSet& modifier_set) + -> SemIR::Function::EvaluationMode { + return modifier_set.ToEnum() + .Case(KeywordModifierSet::Eval, SemIR::Function::EvaluationMode::Eval) + .Case(KeywordModifierSet::MustEval, + SemIR::Function::EvaluationMode::MustEval) + .Default(SemIR::Function::EvaluationMode::None); +} + // Tries to merge new_function into prev_function_id. Since new_function won't // have a definition even if one is upcoming, set is_definition to indicate the // planned result. @@ -422,6 +433,7 @@ static auto BuildFunctionDecl(Context& context, parent_scope_inst_id, parent_scope_inst, self_param_id); bool is_extern = introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extern); auto virtual_modifier = GetVirtualModifier(introducer.modifier_set); + auto evaluation_mode = GetEvaluationMode(introducer.modifier_set); // Add the function declaration. SemIR::FunctionDecl function_decl = {SemIR::TypeId::None, @@ -440,6 +452,7 @@ static auto BuildFunctionDecl(Context& context, .return_form_inst_id = return_form_inst_id, .return_patterns_id = return_patterns_id, .virtual_modifier = virtual_modifier, + .evaluation_mode = evaluation_mode, .self_param_id = self_param_id}}; if (is_definition) { function_info.definition_id = decl_id; diff --git a/toolchain/check/handle_modifier.cpp b/toolchain/check/handle_modifier.cpp index 2ebff2a8ec0a..faa0f9c38405 100644 --- a/toolchain/check/handle_modifier.cpp +++ b/toolchain/check/handle_modifier.cpp @@ -46,16 +46,22 @@ static auto HandleModifier(Context& context, Parse::NodeId node_id, KeywordModifierSet later_modifiers; if (keyword.HasAnyOf(KeywordModifierSet::Access)) { order = ModifierOrder::Access; - later_modifiers = KeywordModifierSet::Extern | KeywordModifierSet::Decl; + later_modifiers = KeywordModifierSet::Extern | KeywordModifierSet::Decl | + KeywordModifierSet::Evaluation; } else if (keyword.HasAnyOf(KeywordModifierSet::Extern)) { order = ModifierOrder::Extern; - later_modifiers = KeywordModifierSet::Decl; + later_modifiers = KeywordModifierSet::Decl | KeywordModifierSet::Evaluation; } else if (keyword.HasAnyOf(KeywordModifierSet::Extend)) { order = ModifierOrder::Extend; later_modifiers = KeywordModifierSet::Decl; - } else { + } else if (keyword.HasAnyOf(KeywordModifierSet::Decl)) { order = ModifierOrder::Decl; + later_modifiers = KeywordModifierSet::Evaluation; + } else if (keyword.HasAnyOf(KeywordModifierSet::Evaluation)) { + order = ModifierOrder::Evaluation; later_modifiers = KeywordModifierSet::None; + } else { + CARBON_FATAL("Unexpected modifier keyword."); } auto current_modifier_node_id = s.modifier_node_id(order); diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index 126c532b3a4f..6f72ec5448e7 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -2193,7 +2193,8 @@ static auto MakeFunctionDecl(ImportContext& context, .return_form_inst_id = SemIR::InstId::None, .return_patterns_id = SemIR::InstBlockId::None, .virtual_modifier = import_function.virtual_modifier, - .virtual_index = import_function.virtual_index}}); + .virtual_index = import_function.virtual_index, + .evaluation_mode = import_function.evaluation_mode}}); // Directly add the function type constant. Don't use `GetFunctionType` // because that will evaluate the function type, which we can't do if the diff --git a/toolchain/check/keyword_modifier_set.h b/toolchain/check/keyword_modifier_set.h index 88d13d5da35f..156b999512a0 100644 --- a/toolchain/check/keyword_modifier_set.h +++ b/toolchain/check/keyword_modifier_set.h @@ -14,7 +14,14 @@ namespace Carbon::Check { // The order of modifiers. Each of these corresponds to a group on // KeywordModifierSet, and can be used as an array index. -enum class ModifierOrder : int8_t { Access, Extern, Extend, Decl, Last = Decl }; +enum class ModifierOrder : int8_t { + Access, + Extern, + Extend, + Decl, + Evaluation, + Last = Evaluation +}; // A single X-macro to cover modifier groups. These are split out to make groups // clearer. @@ -40,7 +47,11 @@ enum class ModifierOrder : int8_t { Access, Extern, Extend, Decl, Last = Decl }; X(Impl) \ X(Override) \ X(Returned) \ - X(Virtual) + X(Virtual) \ + \ + /* Eval and MustEval are mutually exclusive. */ \ + X(Eval) \ + X(MustEval) // We expect this to grow, so are using a bigger size than needed. CARBON_DEFINE_RAW_ENUM_MASK(KeywordModifierSet, uint32_t) { @@ -58,6 +69,7 @@ class KeywordModifierSet : public CARBON_ENUM_MASK_BASE(KeywordModifierSet) { static const KeywordModifierSet Method; static const KeywordModifierSet ImplDecl; static const KeywordModifierSet Interface; + static const KeywordModifierSet Evaluation; static const KeywordModifierSet Decl; // Return a builder that returns the new enumeration type once a series of @@ -125,17 +137,25 @@ inline constexpr KeywordModifierSet KeywordModifierSet::Interface(Default | inline constexpr KeywordModifierSet KeywordModifierSet::Decl(Class | Method | Impl | Interface | Export | Returned); +inline constexpr KeywordModifierSet KeywordModifierSet::Evaluation(Eval | + MustEval); +// TODO: This and the ordering checking logic in handle_modifiers.cpp are +// becoming unwieldy. Find a better representation. static_assert( !KeywordModifierSet::Access.HasAnyOf(KeywordModifierSet::Extern) && !(KeywordModifierSet::Access | KeywordModifierSet::Extern | KeywordModifierSet::Extend) - .HasAnyOf(KeywordModifierSet::Decl), + .HasAnyOf(KeywordModifierSet::Decl) && + !(KeywordModifierSet::Access | KeywordModifierSet::Extern | + KeywordModifierSet::Decl) + .HasAnyOf(KeywordModifierSet::Evaluation), "Order-related sets must not overlap"); #define CARBON_KEYWORD_MODIFIER_SET_IN_GROUP(Modifier) \ static_assert((KeywordModifierSet::Access | KeywordModifierSet::Extern | \ - KeywordModifierSet::Extend | KeywordModifierSet::Decl) \ + KeywordModifierSet::Extend | KeywordModifierSet::Decl | \ + KeywordModifierSet::Evaluation) \ .HasAnyOf(KeywordModifierSet::Modifier), \ "Modifier missing from all modifier sets: " #Modifier); CARBON_KEYWORD_MODIFIER_SET(CARBON_KEYWORD_MODIFIER_SET_IN_GROUP) diff --git a/toolchain/check/modifiers.cpp b/toolchain/check/modifiers.cpp index 8e0fb70149e3..ab95211a7f2c 100644 --- a/toolchain/check/modifiers.cpp +++ b/toolchain/check/modifiers.cpp @@ -61,6 +61,8 @@ static auto ModifierOrderAsSet(ModifierOrder order) -> KeywordModifierSet { return KeywordModifierSet::Extend; case ModifierOrder::Decl: return KeywordModifierSet::Decl; + case ModifierOrder::Evaluation: + return KeywordModifierSet::Evaluation; } } diff --git a/toolchain/check/testdata/eval/binding.carbon b/toolchain/check/testdata/eval/binding.carbon new file mode 100644 index 000000000000..2f68f9531792 --- /dev/null +++ b/toolchain/check/testdata/eval/binding.carbon @@ -0,0 +1,198 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/eval/binding.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/eval/binding.carbon + +// --- local.carbon + +library "[[@TEST_NAME]]"; + +eval fn F(a: i32) -> i32 { + let b: i32 = a; + let c: i32 = b; + return c; +} + +//@dump-sem-ir-begin +let n: array(i32, F(3)) = (1, 2, 3); +//@dump-sem-ir-end + +// --- fail_global.carbon + +library "[[@TEST_NAME]]"; + +// This is a runtime binding, even though it has a compile-time-constant value, +// so is not accessible from within G. +let a: i32 = 3; + +eval fn G() -> i32 { + // CHECK:STDERR: fail_global.carbon:[[@LINE+3]]:10: error: expression is runtime; expected constant [EvalRequiresConstantValue] + // CHECK:STDERR: return a; + // CHECK:STDERR: ^ + return a; +} + +// CHECK:STDERR: fail_global.carbon:[[@LINE+4]]:19: note: in call to G here [InCallToEvalFn] +// CHECK:STDERR: let n: array(i32, G()) = (1, 2, 3); +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +let n: array(i32, G()) = (1, 2, 3); + +// --- fail_runtime_value.carbon + +library "[[@TEST_NAME]]"; + +fn F() -> i32; + +let a: i32 = F(); + +eval fn G() -> i32 { + // CHECK:STDERR: fail_runtime_value.carbon:[[@LINE+3]]:10: error: expression is runtime; expected constant [EvalRequiresConstantValue] + // CHECK:STDERR: return a; + // CHECK:STDERR: ^ + return a; +} + +// CHECK:STDERR: fail_runtime_value.carbon:[[@LINE+4]]:19: note: in call to G here [InCallToEvalFn] +// CHECK:STDERR: let n: array(i32, G()) = (1, 2, 3); +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +let n: array(i32, G()) = (1, 2, 3); + +// CHECK:STDOUT: --- local.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %int_3.1ba: Core.IntLiteral = int_value 3 [concrete] +// CHECK:STDOUT: %ImplicitAs.type.e8c: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete] +// CHECK:STDOUT: %ImplicitAs.Convert.type.1b6: type = fn_type @ImplicitAs.Convert, @ImplicitAs(%i32) [concrete] +// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.3c2: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6 = struct_value () [symbolic] +// CHECK:STDOUT: %ImplicitAs.type.139: type = facet_type <@ImplicitAs, @ImplicitAs(Core.IntLiteral)> [concrete] +// CHECK:STDOUT: %ImplicitAs.Convert.type.71e: type = fn_type @ImplicitAs.Convert, @ImplicitAs(Core.IntLiteral) [concrete] +// CHECK:STDOUT: %From: Core.IntLiteral = symbolic_binding From, 0 [symbolic] +// CHECK:STDOUT: %Int.as.ImplicitAs.impl.Convert.type.2ed: type = fn_type @Int.as.ImplicitAs.impl.Convert, @Int.as.ImplicitAs.impl(%From) [symbolic] +// CHECK:STDOUT: %Int.as.ImplicitAs.impl.Convert.d29: %Int.as.ImplicitAs.impl.Convert.type.2ed = struct_value () [symbolic] +// CHECK:STDOUT: %ImplicitAs.impl_witness.6bc: = impl_witness imports.%ImplicitAs.impl_witness_table.74f, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e0d: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e0d = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.facet.b94: %ImplicitAs.type.e8c = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.6bc) [concrete] +// CHECK:STDOUT: %.863: type = fn_type_with_self_type %ImplicitAs.Convert.type.1b6, %ImplicitAs.facet.b94 [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.061: = bound_method %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete] +// CHECK:STDOUT: %bound_method.fa7: = bound_method %int_3.1ba, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] +// CHECK:STDOUT: %int_3.822: %i32 = int_value 3 [concrete] +// CHECK:STDOUT: %ImplicitAs.impl_witness.640: = impl_witness imports.%ImplicitAs.impl_witness_table.ea2, @Int.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Int.as.ImplicitAs.impl.Convert.type.240: type = fn_type @Int.as.ImplicitAs.impl.Convert, @Int.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Int.as.ImplicitAs.impl.Convert.dd4: %Int.as.ImplicitAs.impl.Convert.type.240 = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.facet.290: %ImplicitAs.type.139 = facet_value %i32, (%ImplicitAs.impl_witness.640) [concrete] +// CHECK:STDOUT: %.71e: type = fn_type_with_self_type %ImplicitAs.Convert.type.71e, %ImplicitAs.facet.290 [concrete] +// CHECK:STDOUT: %Int.as.ImplicitAs.impl.Convert.bound: = bound_method %int_3.822, %Int.as.ImplicitAs.impl.Convert.dd4 [concrete] +// CHECK:STDOUT: %Int.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Int.as.ImplicitAs.impl.Convert.dd4, @Int.as.ImplicitAs.impl.Convert(%int_32) [concrete] +// CHECK:STDOUT: %bound_method.17a: = bound_method %int_3.822, %Int.as.ImplicitAs.impl.Convert.specific_fn [concrete] +// CHECK:STDOUT: %array_type: type = array_type %int_3.1ba, %i32 [concrete] +// CHECK:STDOUT: %pattern_type.5d8: type = pattern_type %array_type [concrete] +// CHECK:STDOUT: %int_1.5b8: Core.IntLiteral = int_value 1 [concrete] +// CHECK:STDOUT: %int_2.ecc: Core.IntLiteral = int_value 2 [concrete] +// CHECK:STDOUT: %tuple.type.37f: type = tuple_type (Core.IntLiteral, Core.IntLiteral, Core.IntLiteral) [concrete] +// CHECK:STDOUT: %tuple.2d5: %tuple.type.37f = tuple_value (%int_1.5b8, %int_2.ecc, %int_3.1ba) [concrete] +// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.215: = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete] +// CHECK:STDOUT: %bound_method.38b: = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] +// CHECK:STDOUT: %int_1.5d2: %i32 = int_value 1 [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.4e5: = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete] +// CHECK:STDOUT: %bound_method.646: = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] +// CHECK:STDOUT: %int_2.ef8: %i32 = int_value 2 [concrete] +// CHECK:STDOUT: %array: %array_type = tuple_value (%int_1.5d2, %int_2.ef8, %int_3.822) [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core.import_ref.42d: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.3c2)] +// CHECK:STDOUT: %ImplicitAs.impl_witness_table.74f = impl_witness_table (%Core.import_ref.42d), @Core.IntLiteral.as.ImplicitAs.impl [concrete] +// CHECK:STDOUT: %Core.import_ref.0bc: @Int.as.ImplicitAs.impl.%Int.as.ImplicitAs.impl.Convert.type (%Int.as.ImplicitAs.impl.Convert.type.2ed) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.ImplicitAs.impl.%Int.as.ImplicitAs.impl.Convert (constants.%Int.as.ImplicitAs.impl.Convert.d29)] +// CHECK:STDOUT: %ImplicitAs.impl_witness_table.ea2 = impl_witness_table (%Core.import_ref.0bc), @Int.as.ImplicitAs.impl [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %n.patt: %pattern_type.5d8 = value_binding_pattern n [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc11_23: type = splice_block %array_type [concrete = constants.%array_type] { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32] +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, %F.decl [concrete = constants.%F] +// CHECK:STDOUT: %int_3: Core.IntLiteral = int_value 3 [concrete = constants.%int_3.1ba] +// CHECK:STDOUT: %impl.elem0.loc11_21: %.863 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5] +// CHECK:STDOUT: %bound_method.loc11_21.1: = bound_method %int_3, %impl.elem0.loc11_21 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.061] +// CHECK:STDOUT: %specific_fn.loc11_21: = specific_function %impl.elem0.loc11_21, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.loc11_21.2: = bound_method %int_3, %specific_fn.loc11_21 [concrete = constants.%bound_method.fa7] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc11_21: init %i32 = call %bound_method.loc11_21.2(%int_3) [concrete = constants.%int_3.822] +// CHECK:STDOUT: %.loc11_21.1: %i32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc11_21 [concrete = constants.%int_3.822] +// CHECK:STDOUT: %.loc11_21.2: %i32 = converted %int_3, %.loc11_21.1 [concrete = constants.%int_3.822] +// CHECK:STDOUT: %F.call: init %i32 = call %F.ref(%.loc11_21.2) [concrete = constants.%int_3.822] +// CHECK:STDOUT: %impl.elem0.loc11_22: %.71e = impl_witness_access constants.%ImplicitAs.impl_witness.640, element0 [concrete = constants.%Int.as.ImplicitAs.impl.Convert.dd4] +// CHECK:STDOUT: %bound_method.loc11_22.1: = bound_method %F.call, %impl.elem0.loc11_22 [concrete = constants.%Int.as.ImplicitAs.impl.Convert.bound] +// CHECK:STDOUT: %specific_fn.loc11_22: = specific_function %impl.elem0.loc11_22, @Int.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Int.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.loc11_22.2: = bound_method %F.call, %specific_fn.loc11_22 [concrete = constants.%bound_method.17a] +// CHECK:STDOUT: %.loc11_22.1: %i32 = value_of_initializer %F.call [concrete = constants.%int_3.822] +// CHECK:STDOUT: %.loc11_22.2: %i32 = converted %F.call, %.loc11_22.1 [concrete = constants.%int_3.822] +// CHECK:STDOUT: %Int.as.ImplicitAs.impl.Convert.call: init Core.IntLiteral = call %bound_method.loc11_22.2(%.loc11_22.2) [concrete = constants.%int_3.1ba] +// CHECK:STDOUT: %.loc11_22.3: Core.IntLiteral = value_of_initializer %Int.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_3.1ba] +// CHECK:STDOUT: %.loc11_22.4: Core.IntLiteral = converted %F.call, %.loc11_22.3 [concrete = constants.%int_3.1ba] +// CHECK:STDOUT: %array_type: type = array_type %.loc11_22.4, %i32 [concrete = constants.%array_type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %impl.elem0.loc11_35.1: %.863 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5] +// CHECK:STDOUT: %bound_method.loc11_35.1: = bound_method @__global_init.%int_1, %impl.elem0.loc11_35.1 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.215] +// CHECK:STDOUT: %specific_fn.loc11_35.1: = specific_function %impl.elem0.loc11_35.1, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.loc11_35.2: = bound_method @__global_init.%int_1, %specific_fn.loc11_35.1 [concrete = constants.%bound_method.38b] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc11_35.1: init %i32 = call %bound_method.loc11_35.2(@__global_init.%int_1) [concrete = constants.%int_1.5d2] +// CHECK:STDOUT: %.loc11_35.1: init %i32 = converted @__global_init.%int_1, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc11_35.1 [concrete = constants.%int_1.5d2] +// CHECK:STDOUT: %.loc11_35.2: ref %array_type = temporary_storage +// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0] +// CHECK:STDOUT: %.loc11_35.3: ref %i32 = array_index %.loc11_35.2, %int_0 +// CHECK:STDOUT: %.loc11_35.4: init %i32 to %.loc11_35.3 = in_place_init %.loc11_35.1 [concrete = constants.%int_1.5d2] +// CHECK:STDOUT: %impl.elem0.loc11_35.2: %.863 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5] +// CHECK:STDOUT: %bound_method.loc11_35.3: = bound_method @__global_init.%int_2, %impl.elem0.loc11_35.2 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.4e5] +// CHECK:STDOUT: %specific_fn.loc11_35.2: = specific_function %impl.elem0.loc11_35.2, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.loc11_35.4: = bound_method @__global_init.%int_2, %specific_fn.loc11_35.2 [concrete = constants.%bound_method.646] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc11_35.2: init %i32 = call %bound_method.loc11_35.4(@__global_init.%int_2) [concrete = constants.%int_2.ef8] +// CHECK:STDOUT: %.loc11_35.5: init %i32 = converted @__global_init.%int_2, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc11_35.2 [concrete = constants.%int_2.ef8] +// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8] +// CHECK:STDOUT: %.loc11_35.6: ref %i32 = array_index %.loc11_35.2, %int_1 +// CHECK:STDOUT: %.loc11_35.7: init %i32 to %.loc11_35.6 = in_place_init %.loc11_35.5 [concrete = constants.%int_2.ef8] +// CHECK:STDOUT: %impl.elem0.loc11_35.3: %.863 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5] +// CHECK:STDOUT: %bound_method.loc11_35.5: = bound_method @__global_init.%int_3, %impl.elem0.loc11_35.3 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.061] +// CHECK:STDOUT: %specific_fn.loc11_35.3: = specific_function %impl.elem0.loc11_35.3, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.loc11_35.6: = bound_method @__global_init.%int_3, %specific_fn.loc11_35.3 [concrete = constants.%bound_method.fa7] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc11_35.3: init %i32 = call %bound_method.loc11_35.6(@__global_init.%int_3) [concrete = constants.%int_3.822] +// CHECK:STDOUT: %.loc11_35.8: init %i32 = converted @__global_init.%int_3, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc11_35.3 [concrete = constants.%int_3.822] +// CHECK:STDOUT: %int_2: Core.IntLiteral = int_value 2 [concrete = constants.%int_2.ecc] +// CHECK:STDOUT: %.loc11_35.9: ref %i32 = array_index %.loc11_35.2, %int_2 +// CHECK:STDOUT: %.loc11_35.10: init %i32 to %.loc11_35.9 = in_place_init %.loc11_35.8 [concrete = constants.%int_3.822] +// CHECK:STDOUT: %.loc11_35.11: init %array_type to %.loc11_35.2 = array_init (%.loc11_35.4, %.loc11_35.7, %.loc11_35.10) [concrete = constants.%array] +// CHECK:STDOUT: %.loc11_35.12: init %array_type = converted @__global_init.%.loc11, %.loc11_35.11 [concrete = constants.%array] +// CHECK:STDOUT: %.loc11_35.13: ref %array_type = temporary %.loc11_35.2, %.loc11_35.12 +// CHECK:STDOUT: %.loc11_35.14: %array_type = acquire_value %.loc11_35.13 +// CHECK:STDOUT: %n: %array_type = value_binding n, %.loc11_35.14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8] +// CHECK:STDOUT: %int_2: Core.IntLiteral = int_value 2 [concrete = constants.%int_2.ecc] +// CHECK:STDOUT: %int_3: Core.IntLiteral = int_value 3 [concrete = constants.%int_3.1ba] +// CHECK:STDOUT: %.loc11: %tuple.type.37f = tuple_literal (%int_1, %int_2, %int_3) [concrete = constants.%tuple.2d5] +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/eval/branch.carbon b/toolchain/check/testdata/eval/branch.carbon new file mode 100644 index 000000000000..6f32aaa60cd7 --- /dev/null +++ b/toolchain/check/testdata/eval/branch.carbon @@ -0,0 +1,142 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/bool.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/eval/branch.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/eval/branch.carbon + +// --- types.carbon + +library "[[@TEST_NAME]]"; + +class A {} +class B {} + +fn MakeA() -> A { return {}; } +fn MakeB() -> B { return {}; } + +// --- if_statement.carbon + +library "[[@TEST_NAME]]"; +import library "types"; + +eval fn F(b: bool) -> type { + if (b) { + return A; + } else { + return B; + } +} + +//@dump-sem-ir-begin +var a: F(true) = MakeA(); +var b: F(false) = MakeB(); +//@dump-sem-ir-end + +// --- fail_todo_if_expression_in_eval_fn.carbon + +library "[[@TEST_NAME]]"; +import library "types"; + +eval fn F(b: bool) -> type { + // CHECK:STDERR: fail_todo_if_expression_in_eval_fn.carbon:[[@LINE+3]]:10: error: expression is runtime; expected constant [EvalRequiresConstantValue] + // CHECK:STDERR: return if b then A else B; + // CHECK:STDERR: ^~~~ + return if b then A else B; +} + +// CHECK:STDERR: fail_todo_if_expression_in_eval_fn.carbon:[[@LINE+7]]:8: note: in call to F here [InCallToEvalFn] +// CHECK:STDERR: var a: F(true) = MakeA(); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: +// CHECK:STDERR: fail_todo_if_expression_in_eval_fn.carbon:[[@LINE-7]]:10: error: expression is runtime; expected constant [EvalRequiresConstantValue] +// CHECK:STDERR: return if b then A else B; +// CHECK:STDERR: ^~~~ +var a: F(true) = MakeA(); +// CHECK:STDERR: fail_todo_if_expression_in_eval_fn.carbon:[[@LINE+4]]:8: note: in call to F here [InCallToEvalFn] +// CHECK:STDERR: var b: F(false) = MakeB(); +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: +var b: F(false) = MakeB(); + +// --- fail_todo_if_expression_direct.carbon + +library "[[@TEST_NAME]]"; +import library "types"; + +// CHECK:STDERR: fail_todo_if_expression_direct.carbon:[[@LINE+4]]:8: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo] +// CHECK:STDERR: var a: if true then A else B = MakeA(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +var a: if true then A else B = MakeA(); +var b: if false then A else B = MakeB(); + +// CHECK:STDOUT: --- if_statement.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %A: type = class_type @A [concrete] +// CHECK:STDOUT: %B: type = class_type @B [concrete] +// CHECK:STDOUT: %true: bool = bool_literal true [concrete] +// CHECK:STDOUT: %pattern_type.1ab: type = pattern_type %A [concrete] +// CHECK:STDOUT: %MakeA.type: type = fn_type @MakeA [concrete] +// CHECK:STDOUT: %MakeA: %MakeA.type = struct_value () [concrete] +// CHECK:STDOUT: %false: bool = bool_literal false [concrete] +// CHECK:STDOUT: %pattern_type.1f4: type = pattern_type %B [concrete] +// CHECK:STDOUT: %MakeB.type: type = fn_type @MakeB [concrete] +// CHECK:STDOUT: %MakeB: %MakeB.type = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Main.MakeA: %MakeA.type = import_ref Main//types, MakeA, loaded [concrete = constants.%MakeA] +// CHECK:STDOUT: %Main.MakeB: %MakeB.type = import_ref Main//types, MakeB, loaded [concrete = constants.%MakeB] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %a.patt: %pattern_type.1ab = ref_binding_pattern a [concrete] +// CHECK:STDOUT: %a.var_patt: %pattern_type.1ab = var_pattern %a.patt [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: %a.var: ref %A = var %a.var_patt [concrete] +// CHECK:STDOUT: %.loc14_14.1: type = splice_block %.loc14_14.3 [concrete = constants.%A] { +// CHECK:STDOUT: %F.ref.loc14: %F.type = name_ref F, %F.decl [concrete = constants.%F] +// CHECK:STDOUT: %true: bool = bool_literal true [concrete = constants.%true] +// CHECK:STDOUT: %F.call.loc14: init type = call %F.ref.loc14(%true) [concrete = constants.%A] +// CHECK:STDOUT: %.loc14_14.2: type = value_of_initializer %F.call.loc14 [concrete = constants.%A] +// CHECK:STDOUT: %.loc14_14.3: type = converted %F.call.loc14, %.loc14_14.2 [concrete = constants.%A] +// CHECK:STDOUT: } +// CHECK:STDOUT: %a: ref %A = ref_binding a, %a.var [concrete = %a.var] +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %b.patt: %pattern_type.1f4 = ref_binding_pattern b [concrete] +// CHECK:STDOUT: %b.var_patt: %pattern_type.1f4 = var_pattern %b.patt [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: %b.var: ref %B = var %b.var_patt [concrete] +// CHECK:STDOUT: %.loc15_15.1: type = splice_block %.loc15_15.3 [concrete = constants.%B] { +// CHECK:STDOUT: %F.ref.loc15: %F.type = name_ref F, %F.decl [concrete = constants.%F] +// CHECK:STDOUT: %false: bool = bool_literal false [concrete = constants.%false] +// CHECK:STDOUT: %F.call.loc15: init type = call %F.ref.loc15(%false) [concrete = constants.%B] +// CHECK:STDOUT: %.loc15_15.2: type = value_of_initializer %F.call.loc15 [concrete = constants.%B] +// CHECK:STDOUT: %.loc15_15.3: type = converted %F.call.loc15, %.loc15_15.2 [concrete = constants.%B] +// CHECK:STDOUT: } +// CHECK:STDOUT: %b: ref %B = ref_binding b, %b.var [concrete = %b.var] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %MakeA.ref: %MakeA.type = name_ref MakeA, imports.%Main.MakeA [concrete = constants.%MakeA] +// CHECK:STDOUT: %.loc14: ref %A = splice_block file.%a.var [concrete = file.%a.var] {} +// CHECK:STDOUT: %MakeA.call: init %A to %.loc14 = call %MakeA.ref() +// CHECK:STDOUT: assign file.%a.var, %MakeA.call +// CHECK:STDOUT: %MakeB.ref: %MakeB.type = name_ref MakeB, imports.%Main.MakeB [concrete = constants.%MakeB] +// CHECK:STDOUT: %.loc15: ref %B = splice_block file.%b.var [concrete = file.%b.var] {} +// CHECK:STDOUT: %MakeB.call: init %B to %.loc15 = call %MakeB.ref() +// CHECK:STDOUT: assign file.%b.var, %MakeB.call +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/eval/call.carbon b/toolchain/check/testdata/eval/call.carbon new file mode 100644 index 000000000000..7443883e5629 --- /dev/null +++ b/toolchain/check/testdata/eval/call.carbon @@ -0,0 +1,111 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/eval/call.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/eval/call.carbon + +// --- call.carbon + +library "[[@TEST_NAME]]"; + +eval fn F() -> i32 { return 3; } + +var a: array(i32, F()) = (0, 1, 2); + +// --- fail_call_wrong_value.carbon + +library "[[@TEST_NAME]]"; + +eval fn F() -> i32 { return 3; } + +// Ensure we get an error about the over-sized initializer and aren't just +// treating all `eval fn` calls as silent errors. +// CHECK:STDERR: fail_call_wrong_value.carbon:[[@LINE+4]]:26: error: cannot initialize array of 3 elements from 4 initializers [ArrayInitFromLiteralArgCountMismatch] +// CHECK:STDERR: var a: array(i32, F()) = (0, 1, 2, 3); +// CHECK:STDERR: ^~~~~~~~~~~~ +// CHECK:STDERR: +var a: array(i32, F()) = (0, 1, 2, 3); + +// --- fail_call_fn_not_eval.carbon + +library "[[@TEST_NAME]]"; + +fn F() -> i32 { return 3; } + +// CHECK:STDERR: fail_call_fn_not_eval.carbon:[[@LINE+4]]:19: error: array bound is not a constant [InvalidArrayExpr] +// CHECK:STDERR: var a: array(i32, F()) = (0, 1, 2); +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +var a: array(i32, F()) = (0, 1, 2); + +// --- fail_call_eval_fn_not_defined.carbon + +library "[[@TEST_NAME]]"; + +eval fn F() -> i32; + +// TODO: We should be able to diagnose this better. +// CHECK:STDERR: fail_call_eval_fn_not_defined.carbon:[[@LINE+4]]:19: error: array bound is not a constant [InvalidArrayExpr] +// CHECK:STDERR: var a: array(i32, F()) = (0, 1, 2); +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +var a: array(i32, F()) = (0, 1, 2); + +// --- param_and_return.carbon + +library "[[@TEST_NAME]]"; + +eval fn F(x: i32) -> i32 { return x; } + +var a: array(i32, F(3)) = (1, 2, 3); + +// --- fail_todo_dependent_call.carbon + +library "[[@TEST_NAME]]"; + +eval fn F(x: i32) -> i32 { return x; } + +fn G(N:! i32) { + // CHECK:STDERR: fail_todo_dependent_call.carbon:[[@LINE+4]]:10: error: cannot evaluate type expression [TypeExprEvaluationFailure] + // CHECK:STDERR: var a: array(i32, F(N)) = (0, 1, 2); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~ + // CHECK:STDERR: + var a: array(i32, F(N)) = (0, 1, 2); +} + +fn H() { G(3); } + +// --- fail_todo_dependent_call_type.carbon + +library "[[@TEST_NAME]]"; + +class C {} +eval fn F(_: i32) -> type { + return C; +} + +fn UseFGenerically(X:! i32) { + // CHECK:STDERR: fail_todo_dependent_call_type.carbon:[[@LINE+12]]:3: error: member name of type `` in compound member access is not an instance member or an interface member [CompoundMemberAccessDoesNotUseBase] + // CHECK:STDERR: var v: F(X) = {}; + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: + // CHECK:STDERR: fail_todo_dependent_call_type.carbon:[[@LINE+8]]:3: error: value of type `` is not callable [CallToNonCallable] + // CHECK:STDERR: var v: F(X) = {}; + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: + // CHECK:STDERR: fail_todo_dependent_call_type.carbon:[[@LINE+4]]:3: error: cannot access member of interface `Core.Destroy` in type `` that does not implement that interface [MissingImplInMemberAccess] + // CHECK:STDERR: var v: F(X) = {}; + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: + var v: F(X) = {}; +} + +fn UseFSpecifically() { + UseFGenerically(3); +} diff --git a/toolchain/check/testdata/eval/recursion.carbon b/toolchain/check/testdata/eval/recursion.carbon new file mode 100644 index 000000000000..f81037ce5a5d --- /dev/null +++ b/toolchain/check/testdata/eval/recursion.carbon @@ -0,0 +1,28 @@ +// 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 +// +// TODO: Extend the "int" min_prelude to support basic integer operations, so we +// can use it here. +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/eval/recursion.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/eval/recursion.carbon + +// --- recursion.carbon + +library "[[@TEST_NAME]]"; + +eval fn F(n: i32) -> type { + if (n == 0) { + return i32; + } + return F(n - 1); +} + +var i: F(3) = 0; + +// TODO: Diagnose infinite recursion. diff --git a/toolchain/check/testdata/eval/symbolic.carbon b/toolchain/check/testdata/eval/symbolic.carbon new file mode 100644 index 000000000000..7cd0f7b1a88c --- /dev/null +++ b/toolchain/check/testdata/eval/symbolic.carbon @@ -0,0 +1,52 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/bool.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/eval/symbolic.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/eval/symbolic.carbon + +// --- symbolic_call_to_eval_fn.carbon + +library "[[@TEST_NAME]]"; + +eval fn F(B:! bool) -> type { + if (B) { return (); } + return ({}, {}); +} + +fn G(B:! bool) { + let n: F(B) = ({}, {}); +} + +fn H() { + G(false); +} + + +// --- fail_symbolic_call_to_eval_fn_invalid_instantiation.carbon +// CHECK:STDERR: fail_symbolic_call_to_eval_fn_invalid_instantiation.carbon: error: cannot initialize tuple of 0 elements from tuple with 2 elements [TupleInitElementCountMismatch] +// TODO: Provide a location for the error message. + +library "[[@TEST_NAME]]"; + +eval fn F(B:! bool) -> type { + if (B) { return (); } + return ({}, {}); +} + +fn G(B:! bool) { + let n: F(B) = ({}, {}); +} + +fn H() { + // CHECK:STDERR: fail_symbolic_call_to_eval_fn_invalid_instantiation.carbon:[[@LINE+4]]:3: note: in `G(true)` used here [ResolvingSpecificHere] + // CHECK:STDERR: G(true); + // CHECK:STDERR: ^ + // CHECK:STDERR: + G(true); +} diff --git a/toolchain/check/testdata/function/call/eval_musteval.carbon b/toolchain/check/testdata/function/call/eval_musteval.carbon new file mode 100644 index 000000000000..b0893c33a46a --- /dev/null +++ b/toolchain/check/testdata/function/call/eval_musteval.carbon @@ -0,0 +1,72 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/function/call/eval_musteval.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/call/eval_musteval.carbon + +// --- no_eval.carbon + +library "[[@TEST_NAME]]"; + +fn Basic(n: i32) {} +eval fn Eval(n: i32) {} + +fn RuntimeArg(n: i32) { + Basic(n); + Eval(n); +} + +// --- fail_no_eval_musteval.carbon + +library "[[@TEST_NAME]]"; + +musteval fn MustEval(n: i32) {} + +fn RuntimeArg(n: i32) { + // CHECK:STDERR: fail_no_eval_musteval.carbon:[[@LINE+7]]:3: error: non-constant call to compile-time-only function [NonConstantCallToCompTimeOnlyFunction] + // CHECK:STDERR: MustEval(n); + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: fail_no_eval_musteval.carbon:[[@LINE-6]]:1: note: compile-time-only function declared here [CompTimeOnlyFunctionHere] + // CHECK:STDERR: musteval fn MustEval(n: i32) {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + MustEval(n); +} + +// --- eval.carbon + +library "[[@TEST_NAME]]"; + +fn Basic(n: i32) {} +eval fn Eval(n: i32) {} +musteval fn MustEval(n: i32) {} + +fn CompileTimeArg() { + Basic(1); + Eval(1); + MustEval(1); +} + +// --- fail_todo_musteval_calls_musteval.carbon + +library "[[@TEST_NAME]]"; + +musteval fn MustEval(n: i32) {} + +// TODO: This should be accepted. +musteval fn CallMustEval(n: i32) { + // CHECK:STDERR: fail_todo_musteval_calls_musteval.carbon:[[@LINE+7]]:3: error: non-constant call to compile-time-only function [NonConstantCallToCompTimeOnlyFunction] + // CHECK:STDERR: MustEval(n); + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: fail_todo_musteval_calls_musteval.carbon:[[@LINE-7]]:1: note: compile-time-only function declared here [CompTimeOnlyFunctionHere] + // CHECK:STDERR: musteval fn MustEval(n: i32) {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + MustEval(n); +} diff --git a/toolchain/check/testdata/function/declaration/eval_musteval.carbon b/toolchain/check/testdata/function/declaration/eval_musteval.carbon new file mode 100644 index 000000000000..a43b0299a79c --- /dev/null +++ b/toolchain/check/testdata/function/declaration/eval_musteval.carbon @@ -0,0 +1,172 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/function/declaration/eval_musteval.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/declaration/eval_musteval.carbon + +// --- basic.carbon + +library "[[@TEST_NAME]]"; + +fn Basic() -> i32 { return 0; } +eval fn Eval() -> i32 { return 0; } +musteval fn MustEval() -> i32 { return 0; } + +// --- member.carbon + +library "[[@TEST_NAME]]"; + +class C { + eval fn Eval() {} + musteval fn MustEval() {} +} + +// --- private.carbon + +library "[[@TEST_NAME]]"; + +private eval fn Eval() -> i32 { return 0; } +private musteval fn MustEval() -> i32 { return 0; } + +// --- fail_conflict.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_conflict.carbon:[[@LINE+7]]:6: error: `musteval` not allowed on declaration with `eval` [ModifierNotAllowedWith] +// CHECK:STDERR: eval musteval fn Mixed(); +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_conflict.carbon:[[@LINE+4]]:1: note: `eval` previously appeared here [ModifierPrevious] +// CHECK:STDERR: eval musteval fn Mixed(); +// CHECK:STDERR: ^~~~ +// CHECK:STDERR: +eval musteval fn Mixed(); + +// --- fail_order.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_order.carbon:[[@LINE+7]]:6: error: `export` must appear before `eval` [ModifierMustAppearBefore] +// CHECK:STDERR: eval export fn ExportEval(); +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: fail_order.carbon:[[@LINE+4]]:1: note: `eval` previously appeared here [ModifierPrevious] +// CHECK:STDERR: eval export fn ExportEval(); +// CHECK:STDERR: ^~~~ +// CHECK:STDERR: +eval export fn ExportEval(); + +// CHECK:STDERR: fail_order.carbon:[[@LINE+7]]:6: error: `private` must appear before `eval` [ModifierMustAppearBefore] +// CHECK:STDERR: eval private fn PrivateEval(); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_order.carbon:[[@LINE+4]]:1: note: `eval` previously appeared here [ModifierPrevious] +// CHECK:STDERR: eval private fn PrivateEval(); +// CHECK:STDERR: ^~~~ +// CHECK:STDERR: +eval private fn PrivateEval(); + +class C { + // CHECK:STDERR: fail_order.carbon:[[@LINE+7]]:8: error: `virtual` must appear before `eval` [ModifierMustAppearBefore] + // CHECK:STDERR: eval virtual fn VirtualEval(); + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: fail_order.carbon:[[@LINE+4]]:3: note: `eval` previously appeared here [ModifierPrevious] + // CHECK:STDERR: eval virtual fn VirtualEval(); + // CHECK:STDERR: ^~~~ + // CHECK:STDERR: + eval virtual fn VirtualEval(); +} + +// --- fail_not_fn.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_not_fn.carbon:[[@LINE+4]]:1: error: `eval` not allowed on `var` declaration [ModifierNotAllowedOnDeclaration] +// CHECK:STDERR: eval var x: i32; +// CHECK:STDERR: ^~~~ +// CHECK:STDERR: +eval var x: i32; + +// CHECK:STDERR: fail_not_fn.carbon:[[@LINE+4]]:1: error: `eval` not allowed on `class` declaration [ModifierNotAllowedOnDeclaration] +// CHECK:STDERR: eval class C {}; +// CHECK:STDERR: ^~~~ +// CHECK:STDERR: +eval class C {}; + +// --- redecl.carbon + +library "[[@TEST_NAME]]"; + +eval fn A() -> i32; +eval fn A() -> i32 { return 0; } + +musteval fn B() -> i32; +musteval fn B() -> i32 { return 0; } + +// --- fail_differ_in_redecl.carbon + +library "[[@TEST_NAME]]"; + +fn NoneThenEval(); +fn NoneThenMustEval(); +eval fn EvalThenNone(); +eval fn EvalThenMustEval(); +musteval fn MustEvalThenNone(); +musteval fn MustEvalThenEval(); + +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE+7]]:1: error: function redeclaration differs because new function is `eval` [FunctionRedeclEvaluationModeDiffers] +// CHECK:STDERR: eval fn NoneThenEval() {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE-10]]:1: note: previously not declared as `eval` [FunctionRedeclEvaluationModePrevious] +// CHECK:STDERR: fn NoneThenEval(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +eval fn NoneThenEval() {} + +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE+7]]:1: error: function redeclaration differs because new function is `musteval` [FunctionRedeclEvaluationModeDiffers] +// CHECK:STDERR: musteval fn NoneThenMustEval() {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE-18]]:1: note: previously not declared as `musteval` [FunctionRedeclEvaluationModePrevious] +// CHECK:STDERR: fn NoneThenMustEval(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +musteval fn NoneThenMustEval() {} + +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE+7]]:1: error: function redeclaration differs because new function is not `eval` [FunctionRedeclEvaluationModeDiffers] +// CHECK:STDERR: fn EvalThenNone() {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE-26]]:1: note: previously declared as `eval` [FunctionRedeclEvaluationModePrevious] +// CHECK:STDERR: eval fn EvalThenNone(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +fn EvalThenNone() {} + +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE+7]]:1: error: function redeclaration differs because new function is `musteval` [FunctionRedeclEvaluationModeDiffers] +// CHECK:STDERR: musteval fn EvalThenMustEval() {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE-34]]:1: note: previously declared as `eval` [FunctionRedeclEvaluationModePrevious] +// CHECK:STDERR: eval fn EvalThenMustEval(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +musteval fn EvalThenMustEval() {} + +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE+7]]:1: error: function redeclaration differs because new function is not `musteval` [FunctionRedeclEvaluationModeDiffers] +// CHECK:STDERR: fn MustEvalThenNone() {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE-42]]:1: note: previously declared as `musteval` [FunctionRedeclEvaluationModePrevious] +// CHECK:STDERR: musteval fn MustEvalThenNone(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +fn MustEvalThenNone() {} + +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE+7]]:1: error: function redeclaration differs because new function is `eval` [FunctionRedeclEvaluationModeDiffers] +// CHECK:STDERR: eval fn MustEvalThenEval() {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_differ_in_redecl.carbon:[[@LINE-50]]:1: note: previously declared as `musteval` [FunctionRedeclEvaluationModePrevious] +// CHECK:STDERR: musteval fn MustEvalThenEval(); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +eval fn MustEvalThenEval() {} diff --git a/toolchain/check/testdata/impl/eval_musteval.carbon b/toolchain/check/testdata/impl/eval_musteval.carbon new file mode 100644 index 000000000000..07edfa358eb8 --- /dev/null +++ b/toolchain/check/testdata/impl/eval_musteval.carbon @@ -0,0 +1,232 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/eval_musteval.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/eval_musteval.carbon + +// --- interface.carbon + +library "[[@TEST_NAME]]"; + +interface Runtime { + fn F[self: Self]() -> type; +} + +interface Eval { + eval fn F[self: Self]() -> type; +} + +interface MustEval { + musteval fn F[self: Self]() -> type; +} + +// --- impl_runtime.carbon + +library "[[@TEST_NAME]]"; + +import library "interface"; + +class A { adapt {}; } + +impl A as Runtime { + fn F[self: Self]() -> type { return A; } +} + +impl A as Eval { + // TODO: Consider rejecting this, as compile-time evaluation would always + // fail. + fn F[self: Self]() -> type { return A; } +} + +impl A as MustEval { + // TODO: Consider rejecting this, as compile-time evaluation would always + // fail. + fn F[self: Self]() -> type { return A; } +} + +fn Call() { + ({} as A).(Runtime.F)(); + ({} as A).(Eval.F)(); + ({} as A).(MustEval.F)(); +} + +// --- fail_impl_runtime_eval.carbon + +library "[[@TEST_NAME]]"; + +import library "interface"; + +class A { adapt {}; } + +impl A as Runtime { + fn F[self: Self]() -> type { return A; } +} + +impl A as Eval { + fn F[self: Self]() -> type { return A; } +} + +impl A as MustEval { + fn F[self: Self]() -> type { return A; } +} + +fn Call() { + // CHECK:STDERR: fail_impl_runtime_eval.carbon:[[@LINE+4]]:10: error: cannot evaluate type expression [TypeExprEvaluationFailure] + // CHECK:STDERR: let t: ({} as A).(Runtime.F)() = {} as A; + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + let t: ({} as A).(Runtime.F)() = {} as A; + // CHECK:STDERR: fail_impl_runtime_eval.carbon:[[@LINE+4]]:10: error: cannot evaluate type expression [TypeExprEvaluationFailure] + // CHECK:STDERR: let u: ({} as A).(Eval.F)() = {} as A; + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + let u: ({} as A).(Eval.F)() = {} as A; + // CHECK:STDERR: fail_impl_runtime_eval.carbon:[[@LINE+4]]:10: error: cannot evaluate type expression [TypeExprEvaluationFailure] + // CHECK:STDERR: let v: ({} as A).(MustEval.F)() = {} as A; + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + let v: ({} as A).(MustEval.F)() = {} as A; +} + +// --- impl_eval.carbon + +library "[[@TEST_NAME]]"; + +import library "interface"; + +class A { adapt {}; } + +impl A as Runtime { + eval fn F[self: Self]() -> type { return A; } +} + +impl A as Eval { + eval fn F[self: Self]() -> type { return A; } +} + +impl A as MustEval { + eval fn F[self: Self]() -> type { return A; } +} + +fn Call() { + // TODO: Should we allow calling this at compile time? This is not allowed if + // we generate a thunk; see the next split for an example. + let t: ({} as A).(Runtime.F)() = {} as A; + let u: ({} as A).(Eval.F)() = {} as A; + let v: ({} as A).(MustEval.F)() = {} as A; +} + +// --- fail_todo_impl_eval_from_runtime_thunk.carbon + +library "[[@TEST_NAME]]"; + +import library "interface"; + +class B {} + +class BView {} +impl B as Core.ImplicitAs(BView) { + fn Convert[self: Self]() -> BView { return {}; } +} + +impl B as Runtime { + eval fn F[self: BView]() -> type { return B; } +} + +fn Call() { + // TODO: Should we allow calling this at compile time? This is allowed if we + // don't generate a thunk. + // CHECK:STDERR: fail_todo_impl_eval_from_runtime_thunk.carbon:[[@LINE+4]]:10: error: cannot evaluate type expression [TypeExprEvaluationFailure] + // CHECK:STDERR: let t: ({} as B).(Runtime.F)() = {} as B; + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + let t: ({} as B).(Runtime.F)() = {} as B; +} + +// --- fail_impl_musteval.carbon + +library "[[@TEST_NAME]]"; + +import library "interface"; + +class B { adapt {}; } + +impl B as Runtime { + // CHECK:STDERR: fail_impl_musteval.carbon:[[@LINE+11]]:3: error: non-constant call to compile-time-only function [NonConstantCallToCompTimeOnlyFunction] + // CHECK:STDERR: musteval fn F[self: Self]() -> type { return B; } + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_impl_musteval.carbon:[[@LINE+8]]:3: note: compile-time-only function declared here [CompTimeOnlyFunctionHere] + // CHECK:STDERR: musteval fn F[self: Self]() -> type { return B; } + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_impl_musteval.carbon:[[@LINE-11]]:1: in import [InImport] + // CHECK:STDERR: interface.carbon:5:3: note: while building thunk to match the signature of this function [ThunkSignature] + // CHECK:STDERR: fn F[self: Self]() -> type; + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + musteval fn F[self: Self]() -> type { return B; } +} + +impl B as Eval { + // CHECK:STDERR: fail_impl_musteval.carbon:[[@LINE+11]]:3: error: non-constant call to compile-time-only function [NonConstantCallToCompTimeOnlyFunction] + // CHECK:STDERR: musteval fn F[self: Self]() -> type { return B; } + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_impl_musteval.carbon:[[@LINE+8]]:3: note: compile-time-only function declared here [CompTimeOnlyFunctionHere] + // CHECK:STDERR: musteval fn F[self: Self]() -> type { return B; } + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_impl_musteval.carbon:[[@LINE-26]]:1: in import [InImport] + // CHECK:STDERR: interface.carbon:9:3: note: while building thunk to match the signature of this function [ThunkSignature] + // CHECK:STDERR: eval fn F[self: Self]() -> type; + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + musteval fn F[self: Self]() -> type { return B; } +} + +// --- impl_musteval_from_musteval.carbon + +library "[[@TEST_NAME]]"; + +import library "interface"; + +class B { adapt {}; } + +impl B as MustEval { + musteval fn F[self: Self]() -> type { return B; } +} + +fn Call() { + let t: ({} as B).(MustEval.F)() = {} as B; +} + +// --- fail_todo_impl_musteval_from_musteval_thunk.carbon + +library "[[@TEST_NAME]]"; + +import library "interface"; + +class B {} + +class BView {} +impl B as Core.ImplicitAs(BView) { + fn Convert[self: Self]() -> BView { return {}; } +} + +impl B as MustEval { + // CHECK:STDERR: fail_todo_impl_musteval_from_musteval_thunk.carbon:[[@LINE+11]]:3: error: non-constant call to compile-time-only function [NonConstantCallToCompTimeOnlyFunction] + // CHECK:STDERR: musteval fn F[self: BView]() -> type { return B; } + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_todo_impl_musteval_from_musteval_thunk.carbon:[[@LINE+8]]:3: note: compile-time-only function declared here [CompTimeOnlyFunctionHere] + // CHECK:STDERR: musteval fn F[self: BView]() -> type { return B; } + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_todo_impl_musteval_from_musteval_thunk.carbon:[[@LINE-16]]:1: in import [InImport] + // CHECK:STDERR: interface.carbon:13:3: note: while building thunk to match the signature of this function [ThunkSignature] + // CHECK:STDERR: musteval fn F[self: Self]() -> type; + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + musteval fn F[self: BView]() -> type { return B; } +} diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index 5755725c612a..d21baa26a4a5 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -229,6 +229,7 @@ static auto CloneFunctionDecl(Context& context, SemIR::LocId loc_id, .return_patterns_id = return_patterns_id, .virtual_modifier = callee.virtual_modifier, .virtual_index = callee.virtual_index, + .evaluation_mode = signature.evaluation_mode, .self_param_id = self_param_id, }}); context.inst_block_stack().AddInstId(decl_id); diff --git a/toolchain/diagnostics/format_providers.cpp b/toolchain/diagnostics/format_providers.cpp index f885b8ac3fe5..bb5250620e3d 100644 --- a/toolchain/diagnostics/format_providers.cpp +++ b/toolchain/diagnostics/format_providers.cpp @@ -69,17 +69,30 @@ auto llvm::format_provider::format( style); out << output_string; return; - } else if (comp.consume_front("=")) { - // Equality comparison. + } else if (auto op = comp.take_while( + [](char c) { return c == '<' || c == '=' || c == '>'; }); + !op.empty()) { + // Comparison. + comp = comp.drop_front(op.size()); int value; CARBON_CHECK(to_integer(comp, value), "IntAsSelect has invalid value in comparison: `{0}`", style); - if (value == wrapper.value) { + auto result = llvm::StringSwitch>(op) + .Case("=", wrapper.value == value) + .Case("<", wrapper.value < value) + .Case("<=", wrapper.value <= value) + .Case(">", wrapper.value > value) + .Case(">=", wrapper.value >= value) + .Default(std::nullopt); + if (!result) { + CARBON_FATAL("IntAsSelect has unrecognized comparison: `{0}`", style); + } + if (*result) { out << output_string; return; } } else { - CARBON_FATAL("IntAsSelect has unrecognized comparison: `{0}`", style); + CARBON_FATAL("IntAsSelect has unrecognized syntax: `{0}`", style); } } diff --git a/toolchain/diagnostics/format_providers_test.cpp b/toolchain/diagnostics/format_providers_test.cpp index 7444b9d24614..bf63307aa40f 100644 --- a/toolchain/diagnostics/format_providers_test.cpp +++ b/toolchain/diagnostics/format_providers_test.cpp @@ -68,6 +68,37 @@ TEST(IntAsSelect, TwoEqualsAndDefault) { Eq("default")); } +TEST(IntAsSelect, LessAndGreater) { + constexpr char Format[] = + "{0:<0:negative|>10:huge|=0:zero|<=4:small|>=7:large|:moderate}"; + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(-1)).str(), + Eq("negative")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(0)).str(), + Eq("zero")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(1)).str(), + Eq("small")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(2)).str(), + Eq("small")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(3)).str(), + Eq("small")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(4)).str(), + Eq("small")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(5)).str(), + Eq("moderate")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(6)).str(), + Eq("moderate")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(7)).str(), + Eq("large")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(8)).str(), + Eq("large")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(9)).str(), + Eq("large")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(10)).str(), + Eq("large")); + EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(11)).str(), + Eq("huge")); +} + TEST(IntAsSelect, Spaces) { constexpr char Format[] = "{0:=0: zero |=1: one |: default }"; EXPECT_THAT(llvm::formatv(Format, Diagnostics::IntAsSelect(0)).str(), diff --git a/toolchain/diagnostics/kind.def b/toolchain/diagnostics/kind.def index 2d22f5032140..3f3524a4e133 100644 --- a/toolchain/diagnostics/kind.def +++ b/toolchain/diagnostics/kind.def @@ -191,6 +191,7 @@ CARBON_DIAGNOSTIC_KIND(InCppInclude) CARBON_DIAGNOSTIC_KIND(InCppModule) CARBON_DIAGNOSTIC_KIND(InCppMacroExpansion) CARBON_DIAGNOSTIC_KIND(ResolvingSpecificHere) +CARBON_DIAGNOSTIC_KIND(InCallToEvalFn) CARBON_DIAGNOSTIC_KIND(InCppThunk) CARBON_DIAGNOSTIC_KIND(InCppTypeCompletion) @@ -273,6 +274,8 @@ CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypeDiffers) CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypeDiffersNoReturn) CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypePrevious) CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypePreviousNoReturn) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclEvaluationModeDiffers) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclEvaluationModePrevious) CARBON_DIAGNOSTIC_KIND(InvalidMainRunSignature) CARBON_DIAGNOSTIC_KIND(MissingReturnStatement) CARBON_DIAGNOSTIC_KIND(UnknownBuiltinFunctionName) diff --git a/toolchain/lex/testdata/keywords.carbon b/toolchain/lex/testdata/keywords.carbon index e0459d264dcc..6c7f85b5d8fb 100644 --- a/toolchain/lex/testdata/keywords.carbon +++ b/toolchain/lex/testdata/keywords.carbon @@ -38,3 +38,10 @@ and or not if else for return var break continue _ notakeyword // CHECK:STDOUT: - { index: 1, kind: "Identifier", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "notakeyword", identifier: 0, has_leading_space: true } +// --- eval_musteval.carbon +// CHECK:STDOUT: - filename: eval_musteval.carbon +// CHECK:STDOUT: tokens: + +eval musteval +// CHECK:STDOUT: - { index: 1, kind: "Eval", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "eval", has_leading_space: true } +// CHECK:STDOUT: - { index: 2, kind: "MustEval", line: {{ *}}[[@LINE-2]], column: 6, indent: 1, spelling: "musteval", has_leading_space: true } diff --git a/toolchain/lex/token_kind.def b/toolchain/lex/token_kind.def index 934134557ccf..5247f745cd05 100644 --- a/toolchain/lex/token_kind.def +++ b/toolchain/lex/token_kind.def @@ -186,6 +186,7 @@ CARBON_KEYWORD_TOKEN(Core, "Core") CARBON_KEYWORD_TOKEN(Cpp, "Cpp") CARBON_KEYWORD_TOKEN(Default, "default") CARBON_KEYWORD_TOKEN(Else, "else") +CARBON_KEYWORD_TOKEN(Eval, "eval") CARBON_KEYWORD_TOKEN(Extend, "extend") CARBON_KEYWORD_TOKEN(Extern, "extern") CARBON_KEYWORD_TOKEN(False, "false") @@ -200,6 +201,7 @@ CARBON_KEYWORD_TOKEN(In, "in") CARBON_KEYWORD_TOKEN(Inline, "inline") CARBON_KEYWORD_TOKEN(Like, "like") CARBON_KEYWORD_TOKEN(Match, "match") +CARBON_KEYWORD_TOKEN(MustEval, "musteval") CARBON_KEYWORD_TOKEN(Not, "not") CARBON_KEYWORD_TOKEN(Observe, "observe") CARBON_TOKEN_WITH_VIRTUAL_NODE( diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index 44631f7bfc57..de22a4228b4a 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -80,12 +80,16 @@ auto FileContext::PrepareToLower() -> void { } // Lower function declarations. - for (auto [id, _] : sem_ir_->functions().enumerate()) { + for (auto [id, function] : sem_ir_->functions().enumerate()) { if (id == sem_ir().global_ctor_id()) { // The global constructor is only lowered when we generate its definition. // LLVM doesn't allow an internal linkage function to be undefined. continue; } + if (function.evaluation_mode == SemIR::Function::EvaluationMode::MustEval) { + // musteval functions are never lowered. + continue; + } functions_.Set(id, BuildFunctionDecl(id)); } diff --git a/toolchain/lower/testdata/function/definition/eval_musteval.carbon b/toolchain/lower/testdata/function/definition/eval_musteval.carbon new file mode 100644 index 000000000000..47d0ccae9afa --- /dev/null +++ b/toolchain/lower/testdata/function/definition/eval_musteval.carbon @@ -0,0 +1,115 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/function/definition/eval_musteval.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/function/definition/eval_musteval.carbon + +// --- eval.carbon + +library "[[@TEST_NAME]]"; + +// Should emit a definition of `Eval`. +eval fn Eval(a: i32) -> i32 { + return a; +} + +fn F() -> i32 { + // Should not emit a call to `Eval`. + return Eval(1); +} + +fn G(n: i32) -> i32 { + // Should emit a call to `Eval`. + return Eval(n); +} + +// --- musteval.carbon + +library "[[@TEST_NAME]]"; + +// Should not emit a definition of `MustEval`. +musteval fn MustEval(a: i32) -> i32 { + return a; +} + +// Should not emit a call to `MustEval`. +fn F() -> i32 { + return MustEval(1); +} + +// CHECK:STDOUT: ; ModuleID = 'eval.carbon' +// CHECK:STDOUT: source_filename = "eval.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define i32 @_CEval.Main(i32 %a) #0 !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret i32 %a, !dbg !10 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define i32 @_CF.Main() #0 !dbg !11 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret i32 1, !dbg !14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define i32 @_CG.Main(i32 %n) #0 !dbg !15 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %Eval.call = call i32 @_CEval.Main(i32 %n), !dbg !18 +// CHECK:STDOUT: ret i32 %Eval.call, !dbg !19 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nounwind } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "eval.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Eval", linkageName: "_CEval.Main", scope: null, file: !3, line: 5, type: !5, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !8) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{!7, !7} +// CHECK:STDOUT: !7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +// CHECK:STDOUT: !8 = !{!9} +// CHECK:STDOUT: !9 = !DILocalVariable(arg: 1, scope: !4, type: !7) +// CHECK:STDOUT: !10 = !DILocation(line: 6, column: 3, scope: !4) +// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 9, type: !12, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !12 = !DISubroutineType(types: !13) +// CHECK:STDOUT: !13 = !{!7} +// CHECK:STDOUT: !14 = !DILocation(line: 11, column: 3, scope: !11) +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "G", linkageName: "_CG.Main", scope: null, file: !3, line: 14, type: !5, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16) +// CHECK:STDOUT: !16 = !{!17} +// CHECK:STDOUT: !17 = !DILocalVariable(arg: 1, scope: !15, type: !7) +// CHECK:STDOUT: !18 = !DILocation(line: 16, column: 10, scope: !15) +// CHECK:STDOUT: !19 = !DILocation(line: 16, column: 3, scope: !15) +// CHECK:STDOUT: ; ModuleID = 'musteval.carbon' +// CHECK:STDOUT: source_filename = "musteval.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define i32 @_CF.Main() #0 !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret i32 1, !dbg !8 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nounwind } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1} +// CHECK:STDOUT: !llvm.dbg.cu = !{!2} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !3 = !DIFile(filename: "musteval.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 10, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{!7} +// CHECK:STDOUT: !7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +// CHECK:STDOUT: !8 = !DILocation(line: 11, column: 3, scope: !4) diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 46bf010f48be..6ddb6b1702ce 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -352,10 +352,12 @@ CARBON_PARSE_NODE_KIND(ExternModifier) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Abstract) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Base) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Default) +CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Eval) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Export) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Extend) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Final) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Impl) +CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(MustEval) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Override) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Private) CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Protected) diff --git a/toolchain/parse/testdata/function/eval_musteval.carbon b/toolchain/parse/testdata/function/eval_musteval.carbon new file mode 100644 index 000000000000..6f41c89a584b --- /dev/null +++ b/toolchain/parse/testdata/function/eval_musteval.carbon @@ -0,0 +1,56 @@ +// 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/parse/testdata/function/eval_musteval.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/function/eval_musteval.carbon + +// --- eval.carbon + +eval fn F(); + +// --- musteval.carbon + +musteval fn F(); + +// --- mixed.carbon + +eval musteval fn F(); + +// CHECK:STDOUT: - filename: eval.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'EvalModifier', text: 'eval'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: musteval.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'MustEvalModifier', text: 'musteval'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: mixed.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'EvalModifier', text: 'eval'}, +// CHECK:STDOUT: {kind: 'MustEvalModifier', text: 'musteval'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/sem_ir/function.h b/toolchain/sem_ir/function.h index bdf33eff72fe..540a327d0199 100644 --- a/toolchain/sem_ir/function.h +++ b/toolchain/sem_ir/function.h @@ -28,6 +28,9 @@ struct FunctionFields { // Kinds of virtual modifiers that can apply to functions. enum class VirtualModifier : uint8_t { None, Virtual, Abstract, Override }; + // Kinds of evaluation modifiers that can apply to functions. + enum class EvaluationMode : uint8_t { None, Eval, MustEval }; + // The following members always have values, and do not change throughout the // lifetime of the function. @@ -79,6 +82,10 @@ struct FunctionFields { // is not virtual (ie: (virtual_modifier == None) == (virtual_index == -1)). int32_t virtual_index = -1; + // Which, if any, evaluation modifier (eval or musteval) is applied to this + // function. + EvaluationMode evaluation_mode = EvaluationMode::None; + // The implicit self parameter pattern, if any, in // implicit_param_patterns_id from EntityWithParamsBase. InstId self_param_id = InstId::None; diff --git a/toolchain/sem_ir/type_iterator.cpp b/toolchain/sem_ir/type_iterator.cpp index 2c15cb601032..9c6f7d80d46d 100644 --- a/toolchain/sem_ir/type_iterator.cpp +++ b/toolchain/sem_ir/type_iterator.cpp @@ -60,6 +60,8 @@ auto TypeIterator::Next() -> Step { auto TypeIterator::ProcessTypeId(TypeId type_id) -> std::optional { auto inst_id = sem_ir_->types().GetTypeInstId(type_id); auto inst = sem_ir_->insts().Get(inst_id); + // TODO: This categorization should mostly be driven by information in the + // inst kind. CARBON_KIND_SWITCH(inst) { // ==== Symbolic types ==== @@ -67,6 +69,8 @@ auto TypeIterator::ProcessTypeId(TypeId type_id) -> std::optional { case SymbolicBindingPattern::Kind: { return Step::SymbolicType{.facet_type_id = type_id}; } + + case Call::Kind: case TypeOfInst::Kind: { return Step::TemplateType(); } diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index fdd03866d57e..52528baf22e2 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -373,6 +373,8 @@ struct Call { static constexpr auto Kind = InstKind::Call.Define( {.ir_name = "call", .expr_category = ComputedExprCategory::DependsOnOperands, + .is_type = InstIsType::Maybe, + .constant_kind = InstConstantKind::SymbolicOnly, .constant_needs_inst_id = InstConstantNeedsInstIdKind::DuringEvaluation}); @@ -1196,6 +1198,7 @@ struct NameBindingDecl { // TODO: Make Parse::NodeId more specific. static constexpr auto Kind = InstKind::NameBindingDecl.Define( {.ir_name = "name_binding_decl", + .expr_category = ExprCategory::NotExpr, .constant_kind = InstConstantKind::Never}); InstBlockId pattern_block_id;