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;