Browse Source

Add basic support for `eval fn` and `musteval fn`. (#6694)

Add support for compile-time functions. `eval fn` is analogous to C++
`constexpr`, and is evaluated at compile time when it has compile-time
arguments. `musteval fn` is analogous to C++ `consteval`, and requires
that its arguments be available at compile time and is always evaluated
at compile time. For now we require the modifier to match across
redeclarations of the function. The specific modifier syntax here is a
placeholder and not yet part of an approved design.

Limitations: Only very basic support for evaluation is provided. So far
there's no support for mutable state or `if` expressions, but otherwise
control flow and passing and returning values should work. Carbon
evaluation recursion is modeled by C++ recursion for now, so you can
overflow the toolchain stack easily. Functions that use in-place
initialization will generally not work yet, as they are modeled as
passing a non-compile-time-constant reference to a temporary to the
call.

Add missing categorization of `name_binding_decl` as `NotExpr` to match
other similar declaration instructions like `FunctionDecl`, so that we
can uniformly skip over them when they occur within function bodies.

Assisted-by: Gemini 3 Pro and Flash via Antigravity
Richard Smith 2 months ago
parent
commit
1b2ae912fc

+ 2 - 2
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<int8_t>(ModifierOrder::Decl) + 1] =
+      ordered_modifier_node_ids[static_cast<int8_t>(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();

+ 410 - 21
toolchain/check/eval.cpp

@@ -48,6 +48,21 @@ struct SpecificEvalInfo {
   llvm::ArrayRef<SemIR::InstId> 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<SemIR::InstId, SemIR::ConstantId>* 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<LocalEvalInfo> 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<SemIR::InstId, SemIR::ConstantId>& {
+    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<SpecificEvalInfo> 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<LocalEvalInfo> 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<SemIR::CalleeFunction>(&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<SemIR::CalleeFunction>(&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<SemIR::CalleeFunction>(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<SemIR::InstId, SemIR::ConstantId>* 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<llvm::ArrayRef<SemIR::InstId>>& {
+    return blocks_;
+  }
+
+  // Returns the argument values supplied in the call to the function.
+  auto args() const -> llvm::ArrayRef<SemIR::InstId> { return args_; }
+
+  using EvalContext::locals;
+
+ private:
+  llvm::SmallVector<llvm::ArrayRef<SemIR::InstId>, 4> blocks_;
+  llvm::ArrayRef<SemIR::InstId> 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 <typename InstT>
+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<InstT>(eval_context, inst_id, inst);
+  return HandleExecResult(eval_context, inst_id, const_id);
+}
+
+template <>
+auto TryExecTypedInst<SemIR::Branch>(FunctionExecContext& eval_context,
+                                     SemIR::InstId /*inst_id*/,
+                                     SemIR::Inst inst) -> SemIR::ConstantId {
+  auto branch = inst.As<SemIR::Branch>();
+  eval_context.blocks().back() =
+      eval_context.context().inst_blocks().Get(branch.target_id);
+  return SemIR::ConstantId::None;
+}
+
+template <>
+auto TryExecTypedInst<SemIR::BranchIf>(FunctionExecContext& eval_context,
+                                       SemIR::InstId /*inst_id*/,
+                                       SemIR::Inst inst) -> SemIR::ConstantId {
+  auto branch_if = inst.As<SemIR::BranchIf>();
+  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<SemIR::BoolLiteral>(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<SemIR::Return>(FunctionExecContext& eval_context,
+                                     SemIR::InstId /*inst_id*/,
+                                     SemIR::Inst /*inst*/)
+    -> SemIR::ConstantId {
+  return MakeEmptyTupleResult(eval_context);
+}
+
+template <>
+auto TryExecTypedInst<SemIR::ReturnExpr>(FunctionExecContext& eval_context,
+                                         SemIR::InstId /*inst_id*/,
+                                         SemIR::Inst inst)
+    -> SemIR::ConstantId {
+  auto return_expr = inst.As<SemIR::ReturnExpr>();
+  return eval_context.GetConstantValue(return_expr.expr_id);
+}
+
+template <>
+auto TryExecTypedInst<SemIR::ReturnSlot>(FunctionExecContext& eval_context,
+                                         SemIR::InstId inst_id,
+                                         SemIR::Inst inst)
+    -> SemIR::ConstantId {
+  auto return_slot = inst.As<SemIR::ReturnSlot>();
+  // 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<SemIR::SpliceBlock>(FunctionExecContext& eval_context,
+                                          SemIR::InstId /*inst_id*/,
+                                          SemIR::Inst inst)
+    -> SemIR::ConstantId {
+  auto splice_block = inst.As<SemIR::SpliceBlock>();
+  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<SemIR::AnyParam>();
+  CARBON_CHECK(static_cast<size_t>(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<SemIR::OutParam>(FunctionExecContext& eval_context,
+                                       SemIR::InstId inst_id, SemIR::Inst inst)
+    -> SemIR::ConstantId {
+  auto param = inst.As<SemIR::OutParam>();
+  if (static_cast<size_t>(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<SemIR::RefParam>(FunctionExecContext& eval_context,
+                                       SemIR::InstId inst_id, SemIR::Inst inst)
+    -> SemIR::ConstantId {
+  return TryExecTypedParam(eval_context, inst_id, inst);
+}
+
+template <>
+auto TryExecTypedInst<SemIR::ValueParam>(FunctionExecContext& eval_context,
+                                         SemIR::InstId inst_id,
+                                         SemIR::Inst inst)
+    -> SemIR::ConstantId {
+  return TryExecTypedParam(eval_context, inst_id, inst);
+}
+
+template <>
+auto TryExecTypedInst<SemIR::ValueBinding>(FunctionExecContext& eval_context,
+                                           SemIR::InstId inst_id,
+                                           SemIR::Inst inst)
+    -> SemIR::ConstantId {
+  auto value_binding = inst.As<SemIR::ValueBinding>();
+  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<SemIR::Kind>,
+#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<SemIR::InstId, SemIR::ConstantId> 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<SemIR::FunctionDecl>(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

+ 53 - 2
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,

+ 17 - 4
toolchain/check/handle_function.cpp

@@ -90,10 +90,11 @@ static auto DiagnoseModifiers(Context& context,
                               std::optional<SemIR::Inst> 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<SemIR::Function::EvaluationMode>()
+      .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;

+ 9 - 3
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);

+ 2 - 1
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

+ 24 - 4
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)

+ 2 - 0
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;
   }
 }
 

+ 198 - 0
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: <witness> = 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> = 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> = 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> = 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: <witness> = 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> = bound_method %int_3.822, %Int.as.ImplicitAs.impl.Convert.dd4 [concrete]
+// CHECK:STDOUT:   %Int.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Int.as.ImplicitAs.impl.Convert.dd4, @Int.as.ImplicitAs.impl.Convert(%int_32) [concrete]
+// CHECK:STDOUT:   %bound_method.17a: <bound method> = 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> = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
+// CHECK:STDOUT:   %bound_method.38b: <bound method> = 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> = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
+// CHECK:STDOUT:   %bound_method.646: <bound method> = 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> = 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> = 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> = 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> = bound_method %F.call, %impl.elem0.loc11_22 [concrete = constants.%Int.as.ImplicitAs.impl.Convert.bound]
+// CHECK:STDOUT:     %specific_fn.loc11_22: <specific function> = 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> = 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> = 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> = 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> = 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> = 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> = 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> = 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> = 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> = 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> = 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:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 142 - 0
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:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 111 - 0
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 `<dependent 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 `<dependent 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 `<cannot stringify inst7800017A: {kind: Call, arg0: inst7800003F, arg1: inst_block78000081, type: type(TypeType)}>` 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);
+}

+ 28 - 0
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.

+ 52 - 0
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);
+}

+ 72 - 0
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);
+}

+ 172 - 0
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() {}

+ 232 - 0
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; }
+}

+ 1 - 0
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);

+ 17 - 4
toolchain/diagnostics/format_providers.cpp

@@ -69,17 +69,30 @@ auto llvm::format_provider<Carbon::Diagnostics::IntAsSelect>::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<std::optional<bool>>(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);
     }
   }
 

+ 31 - 0
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(),

+ 3 - 0
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)

+ 7 - 0
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 }

+ 2 - 0
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(

+ 5 - 1
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));
   }
 

+ 115 - 0
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)

+ 2 - 0
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)

+ 56 - 0
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:   ]

+ 7 - 0
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;

+ 4 - 0
toolchain/sem_ir/type_iterator.cpp

@@ -60,6 +60,8 @@ auto TypeIterator::Next() -> Step {
 auto TypeIterator::ProcessTypeId(TypeId type_id) -> std::optional<Step> {
   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<Step> {
     case SymbolicBindingPattern::Kind: {
       return Step::SymbolicType{.facet_type_id = type_id};
     }
+
+    case Call::Kind:
     case TypeOfInst::Kind: {
       return Step::TemplateType();
     }

+ 3 - 0
toolchain/sem_ir/typed_insts.h

@@ -373,6 +373,8 @@ struct Call {
   static constexpr auto Kind = InstKind::Call.Define<Parse::NodeId>(
       {.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<Parse::NodeId>(
       {.ir_name = "name_binding_decl",
+       .expr_category = ExprCategory::NotExpr,
        .constant_kind = InstConstantKind::Never});
 
   InstBlockId pattern_block_id;