Quellcode durchsuchen

Detect control flow in entities nested inside functions (#5336)

Right now, return_scope_stack is being used to determine whether logic
is in a function scope. However, we need to handle nested entities
inside function scopes. For example where this crashes right now:

```
base class C(B:! bool) {}

fn F() {
  class B {
    extend base: C(true or false);
  }
}
```

This is doing a few things to make this kind of code not crash:

- Split `scope_stack().Push` into `PushForDeclName`, `PushForEntity`,
`PushForExpr`, and `PushForFunction` so that better decisions can be
made about behaviors.
- Hide `return_scope_stack` in the API, instead using interfaces to get
at the underlying data.
- Also using `PushForFunction` to update it similar to the other stacks
that `ScopeStack` manages.
- Add `IsInFunctionScope` as the best way to determine presence in
function scope.
- Remove `PeekIsLexicalScope` since destruction really wants function
scope information anyways.
- Clean up `destroy_id_stack` handling to be for function scopes rather
than lexical scopes.
- Return after related `context.TODO`s in a couple more spots, so that
code doesn't proceed to add control flow in spite of the lack of
support.

---------

Co-authored-by: Geoff Romer <gromer@google.com>
Jon Ross-Perkins vor 1 Jahr
Ursprung
Commit
03e693873b

+ 4 - 2
toolchain/check/check_unit.cpp

@@ -229,7 +229,9 @@ auto CheckUnit::ImportCurrentPackage(SemIR::InstId package_inst_id,
       unit_and_imports_->package_imports_map.Lookup(PackageNameId::None);
   if (!import_map_lookup) {
     // Push the scope; there are no names to add.
-    context_.scope_stack().Push(package_inst_id, SemIR::NameScopeId::Package);
+    context_.scope_stack().PushForEntity(
+        package_inst_id, SemIR::NameScopeId::Package, SemIR::SpecificId::None,
+        /*lexical_lookup_has_load_error=*/false);
     return;
   }
   PackageImports& self_import =
@@ -244,7 +246,7 @@ auto CheckUnit::ImportCurrentPackage(SemIR::InstId package_inst_id,
       CollectTransitiveImports(self_import.import_decl_id, &self_import,
                                /*api_imports=*/nullptr));
 
-  context_.scope_stack().Push(
+  context_.scope_stack().PushForEntity(
       package_inst_id, SemIR::NameScopeId::Package, SemIR::SpecificId::None,
       context_.name_scopes().Get(SemIR::NameScopeId::Package).has_error());
 }

+ 1 - 4
toolchain/check/context.h

@@ -118,10 +118,7 @@ class Context {
 
   auto scope_stack() -> ScopeStack& { return scope_stack_; }
 
-  // Conveneicne functions for frequently-used `scope_stack` members.
-  auto return_scope_stack() -> llvm::SmallVector<ScopeStack::ReturnScope>& {
-    return scope_stack().return_scope_stack();
-  }
+  // Convenience functions for frequently-used `scope_stack` members.
   auto break_continue_stack()
       -> llvm::SmallVector<ScopeStack::BreakContinueScope>& {
     return scope_stack().break_continue_stack();

+ 2 - 2
toolchain/check/control_flow.cpp

@@ -139,8 +139,8 @@ auto IsCurrentPositionReachable(Context& context) -> bool {
 
 auto MaybeAddCleanupForInst(Context& context, SemIR::TypeId type_id,
                             SemIR::InstId inst_id) -> void {
-  if (!context.scope_stack().PeekIsLexicalScope()) {
-    // Cleanup can only occur in lexical scopes.
+  if (!context.scope_stack().IsInFunctionScope()) {
+    // Cleanup can only occur in function scopes.
     return;
   }
 

+ 4 - 4
toolchain/check/decl_name_stack.cpp

@@ -62,7 +62,7 @@ auto DeclNameStack::PushScopeAndStartName() -> void {
   decl_name_stack_.push_back(MakeEmptyNameContext());
 
   // Create a scope for any parameters introduced in this name.
-  context_->scope_stack().Push();
+  context_->scope_stack().PushForDeclName();
 }
 
 auto DeclNameStack::FinishName(const NameComponent& name) -> NameContext {
@@ -233,8 +233,8 @@ static auto PushNameQualifierScope(Context& context, SemIR::LocId loc_id,
   // providing the definition.
   StartGenericDecl(context);
 
-  context.scope_stack().Push(scope_inst_id, scope_id, self_specific_id,
-                             has_error);
+  context.scope_stack().PushForEntity(scope_inst_id, scope_id, self_specific_id,
+                                      has_error);
 
   // An interface also introduces its 'Self' parameter into scope, despite it
   // not being redeclared as part of the qualifier.
@@ -246,7 +246,7 @@ static auto PushNameQualifierScope(Context& context, SemIR::LocId loc_id,
   }
 
   // Enter a parameter scope in case the qualified name itself has parameters.
-  context.scope_stack().Push();
+  context.scope_stack().PushForSameRegion();
 }
 
 auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void {

+ 2 - 2
toolchain/check/handle_choice.cpp

@@ -108,8 +108,8 @@ auto HandleParseNode(Context& context, Parse::ChoiceDefinitionStartId node_id)
   mut_class.self_type_id = self_type_id;
 
   // Enter the choice scope.
-  context.scope_stack().Push(class_decl_id, class_info.scope_id,
-                             self_specific_id);
+  context.scope_stack().PushForEntity(class_decl_id, class_info.scope_id,
+                                      self_specific_id);
   // Checking the binding pattern for an alternative requires a non-empty stack.
   // We reuse the Choice token even though we're now checking an alternative
   // inside the Choice, since there's no better token to use.

+ 1 - 1
toolchain/check/handle_class.cpp

@@ -278,7 +278,7 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionStartId node_id)
   StartClassDefinition(context, class_info, class_decl_id);
 
   // Enter the class scope.
-  context.scope_stack().Push(
+  context.scope_stack().PushForEntity(
       class_decl_id, class_info.scope_id,
       context.generics().GetSelfSpecific(class_info.generic_id));
   StartGenericDefinition(context);

+ 1 - 1
toolchain/check/handle_codeblock.cpp

@@ -10,7 +10,7 @@ namespace Carbon::Check {
 auto HandleParseNode(Context& context, Parse::CodeBlockStartId node_id)
     -> bool {
   context.node_stack().Push(node_id);
-  context.scope_stack().Push();
+  context.scope_stack().PushForSameRegion();
   return true;
 }
 

+ 2 - 4
toolchain/check/handle_function.cpp

@@ -585,10 +585,9 @@ static auto HandleFunctionDefinitionAfterSignature(
   auto& function = context.functions().Get(function_id);
 
   // Create the function scope and the entry block.
-  context.return_scope_stack().push_back({.decl_id = decl_id});
+  context.scope_stack().PushForFunctionBody(decl_id);
   context.inst_block_stack().Push();
   context.region_stack().PushRegion(context.inst_block_stack().PeekOrAdd());
-  context.scope_stack().Push(decl_id);
   StartGenericDefinition(context);
 
   CheckFunctionDefinitionSignature(context, function);
@@ -645,9 +644,8 @@ auto HandleParseNode(Context& context, Parse::FunctionDefinitionId node_id)
     }
   }
 
-  context.scope_stack().Pop();
   context.inst_block_stack().Pop();
-  context.return_scope_stack().pop_back();
+  context.scope_stack().Pop();
   context.decl_name_stack().PopScope();
 
   auto& function = context.functions().Get(function_id);

+ 4 - 4
toolchain/check/handle_if_expr.cpp

@@ -64,10 +64,10 @@ auto HandleParseNode(Context& context, Parse::IfExprThenId node_id) -> bool {
 }
 
 auto HandleParseNode(Context& context, Parse::IfExprElseId node_id) -> bool {
-  if (context.return_scope_stack().empty()) {
-    context.TODO(node_id,
-                 "Control flow expressions are currently only supported inside "
-                 "functions.");
+  if (!context.scope_stack().IsInFunctionScope()) {
+    return context.TODO(node_id,
+                        "Control flow expressions are currently only supported "
+                        "inside functions.");
   }
   // Alias node_id for if/then/else consistency.
   auto& else_node = node_id;

+ 1 - 1
toolchain/check/handle_impl.cpp

@@ -537,7 +537,7 @@ auto HandleParseNode(Context& context, Parse::ImplDefinitionStartId node_id)
       context.name_scopes().Add(impl_decl_id, SemIR::NameId::None,
                                 context.decl_name_stack().PeekParentScopeId());
 
-  context.scope_stack().Push(
+  context.scope_stack().PushForEntity(
       impl_decl_id, impl_info.scope_id,
       context.generics().GetSelfSpecific(impl_info.generic_id));
   StartGenericDefinition(context);

+ 2 - 2
toolchain/check/handle_interface.cpp

@@ -193,8 +193,8 @@ auto HandleParseNode(Context& context,
                                         interface_info.self_param_id);
 
   // Enter the interface scope.
-  context.scope_stack().Push(interface_decl_id, interface_info.scope_id,
-                             self_specific_id);
+  context.scope_stack().PushForEntity(
+      interface_decl_id, interface_info.scope_id, self_specific_id);
 
   // TODO: Handle the case where there's control flow in the interface body. For
   // example:

+ 4 - 4
toolchain/check/handle_operator.cpp

@@ -398,10 +398,10 @@ auto HandleParseNode(Context& context, Parse::ShortCircuitOperandOrId node_id)
 // occurs during operand handling.
 static auto HandleShortCircuitOperator(Context& context, Parse::NodeId node_id)
     -> bool {
-  if (context.return_scope_stack().empty()) {
-    context.TODO(node_id,
-                 "Control flow expressions are currently only supported inside "
-                 "functions.");
+  if (!context.scope_stack().IsInFunctionScope()) {
+    return context.TODO(node_id,
+                        "Control flow expressions are currently only supported "
+                        "inside functions.");
   }
   auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
   auto short_circuit_result_id = context.node_stack().PopExpr();

+ 2 - 2
toolchain/check/handle_struct.cpp

@@ -14,7 +14,7 @@ namespace Carbon::Check {
 
 auto HandleParseNode(Context& context, Parse::StructLiteralStartId node_id)
     -> bool {
-  context.scope_stack().Push();
+  context.scope_stack().PushForSameRegion();
   context.node_stack().Push(node_id);
   context.struct_type_fields_stack().PushArray();
   context.param_and_arg_refs_stack().Push();
@@ -23,7 +23,7 @@ auto HandleParseNode(Context& context, Parse::StructLiteralStartId node_id)
 
 auto HandleParseNode(Context& context, Parse::StructTypeLiteralStartId node_id)
     -> bool {
-  context.scope_stack().Push();
+  context.scope_stack().PushForSameRegion();
   context.node_stack().Push(node_id);
   context.struct_type_fields_stack().PushArray();
   return true;

+ 1 - 1
toolchain/check/handle_where.cpp

@@ -27,7 +27,7 @@ auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {
 
   // Introduce a name scope so that we can remove the `.Self` entry we are
   // adding to name lookup at the end of the `where` expression.
-  context.scope_stack().Push();
+  context.scope_stack().PushForSameRegion();
   // Introduce `.Self` as a symbolic binding. Its type is the value of the
   // expression to the left of `where`, so `MyInterface` in the example above.
   auto entity_name_id = context.entity_names().Add(

+ 6 - 7
toolchain/check/return.cpp

@@ -12,12 +12,11 @@ namespace Carbon::Check {
 
 // Gets the function that lexically encloses the current location.
 auto GetCurrentFunctionForReturn(Context& context) -> SemIR::Function& {
-  CARBON_CHECK(!context.return_scope_stack().empty(),
+  CARBON_CHECK(context.scope_stack().IsInFunctionScope(),
                "Handling return but not in a function");
-  auto function_id = context.insts()
-                         .GetAs<SemIR::FunctionDecl>(
-                             context.return_scope_stack().back().decl_id)
-                         .function_id;
+  auto decl_id = context.scope_stack().GetReturnScopeDeclId();
+  auto function_id =
+      context.insts().GetAs<SemIR::FunctionDecl>(decl_id).function_id;
   return context.functions().Get(function_id);
 }
 
@@ -33,9 +32,9 @@ auto GetCurrentReturnSlot(Context& context) -> SemIR::InstId {
 // Gets the currently in scope `returned var`, if any, that would be returned
 // by a `return var;`.
 static auto GetCurrentReturnedVar(Context& context) -> SemIR::InstId {
-  CARBON_CHECK(!context.return_scope_stack().empty(),
+  CARBON_CHECK(context.scope_stack().IsInFunctionScope(),
                "Handling return but not in a function");
-  return context.return_scope_stack().back().returned_var;
+  return context.scope_stack().GetReturnedVar();
 }
 
 // Produces a note that the given function has no explicit return type.

+ 49 - 17
toolchain/check/scope_stack.cpp

@@ -63,8 +63,6 @@ auto ScopeStack::Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
     // specifics, we'll need to somehow track them in lookup.
     CARBON_CHECK(!specific_id.has_value(),
                  "Lexical scope should not have an associated specific.");
-
-    destroy_id_stack_.PushArray();
   } else {
     non_lexical_scope_stack_.push_back({.scope_index = next_scope_index_,
                                         .name_scope_id = scope_id,
@@ -79,6 +77,36 @@ auto ScopeStack::Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
   VerifyNextCompileTimeBindIndex("Push", scope_stack_.back());
 }
 
+auto ScopeStack::PushForDeclName() -> void {
+  Push(SemIR::InstId::None, SemIR::NameScopeId::None, SemIR::SpecificId::None,
+       /*lexical_lookup_has_load_error=*/false);
+  MarkNestingIfInReturnScope();
+}
+
+auto ScopeStack::PushForEntity(SemIR::InstId scope_inst_id,
+                               SemIR::NameScopeId scope_id,
+                               SemIR::SpecificId specific_id,
+                               bool lexical_lookup_has_load_error) -> void {
+  CARBON_CHECK(scope_inst_id.has_value());
+  CARBON_DCHECK(!sem_ir_->insts().Is<SemIR::FunctionDecl>(scope_inst_id));
+  Push(scope_inst_id, scope_id, specific_id, lexical_lookup_has_load_error);
+  MarkNestingIfInReturnScope();
+}
+
+auto ScopeStack::PushForSameRegion() -> void {
+  Push(SemIR::InstId::None, SemIR::NameScopeId::None, SemIR::SpecificId::None,
+       /*lexical_lookup_has_load_error=*/false);
+}
+
+auto ScopeStack::PushForFunctionBody(SemIR::InstId scope_inst_id) -> void {
+  CARBON_DCHECK(sem_ir_->insts().Is<SemIR::FunctionDecl>(scope_inst_id));
+  Push(scope_inst_id, SemIR::NameScopeId::None, SemIR::SpecificId::None,
+       /*lexical_lookup_has_load_error=*/false);
+
+  return_scope_stack_.push_back({.decl_id = scope_inst_id});
+  destroy_id_stack_.PushArray();
+}
+
 auto ScopeStack::Pop() -> void {
   auto scope = scope_stack_.pop_back_val();
 
@@ -89,17 +117,27 @@ auto ScopeStack::Pop() -> void {
     lexical_results.pop_back();
   });
 
-  if (scope.is_lexical_scope()) {
-    destroy_id_stack_.PopArray();
-  } else {
+  if (!scope.is_lexical_scope()) {
     CARBON_CHECK(non_lexical_scope_stack_.back().scope_index == scope.index);
     non_lexical_scope_stack_.pop_back();
   }
 
-  if (scope.has_returned_var) {
-    CARBON_CHECK(!return_scope_stack_.empty());
-    CARBON_CHECK(return_scope_stack_.back().returned_var.has_value());
-    return_scope_stack_.back().returned_var = SemIR::InstId::None;
+  if (!return_scope_stack_.empty()) {
+    if (scope.has_returned_var) {
+      CARBON_CHECK(return_scope_stack_.back().returned_var.has_value());
+      return_scope_stack_.back().returned_var = SemIR::InstId::None;
+    }
+
+    if (return_scope_stack_.back().decl_id == scope.scope_inst_id) {
+      // Leaving the function scope.
+      return_scope_stack_.pop_back();
+      destroy_id_stack_.PopArray();
+    } else if (return_scope_stack_.back().nested_scope_index == scope.index) {
+      // Returned to a function scope from a non-function nested entity scope.
+      return_scope_stack_.back().nested_scope_index = ScopeIndex::None;
+    }
+  } else {
+    CARBON_CHECK(!scope.has_returned_var);
   }
 
   VerifyNextCompileTimeBindIndex("Pop", scope);
@@ -218,11 +256,7 @@ auto ScopeStack::Suspend() -> SuspendedScope {
   CARBON_CHECK(!scope_stack_.empty(), "No scope to suspend");
   SuspendedScope result = {.entry = scope_stack_.pop_back_val(),
                            .suspended_items = {}};
-  if (result.entry.is_lexical_scope()) {
-    CARBON_CHECK(destroy_id_stack_.PeekArray().empty(),
-                 "Missing support to suspend scopes with destructed storage");
-    destroy_id_stack_.PopArray();
-  } else {
+  if (!result.entry.is_lexical_scope()) {
     non_lexical_scope_stack_.pop_back();
   }
   auto peek_compile_time_bindings = compile_time_binding_stack_.PeekArray();
@@ -265,9 +299,7 @@ auto ScopeStack::Restore(SuspendedScope scope) -> void {
 
   VerifyNextCompileTimeBindIndex("Restore", scope.entry);
 
-  if (scope.entry.is_lexical_scope()) {
-    destroy_id_stack_.PushArray();
-  } else {
+  if (!scope.entry.is_lexical_scope()) {
     non_lexical_scope_stack_.push_back(
         {.scope_index = scope.entry.index,
          .name_scope_id = scope.entry.scope_id,

+ 77 - 30
toolchain/check/scope_stack.h

@@ -31,17 +31,6 @@ class ScopeStack {
     SemIR::InstBlockId continue_target;
   };
 
-  // A scope in which `return` can be used.
-  struct ReturnScope {
-    // The declaration from which we can return. Inside a function, this will
-    // be a `FunctionDecl`.
-    SemIR::InstId decl_id;
-
-    // The value corresponding to the current `returned var`, if any. Will be
-    // set and unset as `returned var`s are declared and go out of scope.
-    SemIR::InstId returned_var = SemIR::InstId::None;
-  };
-
   // A non-lexical scope in which unqualified lookup may be required.
   struct NonLexicalScope {
     // The index of the scope in the scope stack.
@@ -57,17 +46,24 @@ class ScopeStack {
   // Information about a scope that has been temporarily removed from the stack.
   struct SuspendedScope;
 
-  // Pushes a scope onto scope_stack_. NameScopeId::None is used for new
-  // scopes. lexical_lookup_has_load_error is used to limit diagnostics when a
-  // given namespace may contain a mix of both successful and failed name
-  // imports.
-  auto Push(SemIR::InstId scope_inst_id = SemIR::InstId::None,
-            SemIR::NameScopeId scope_id = SemIR::NameScopeId::None,
-            SemIR::SpecificId specific_id = SemIR::SpecificId::None,
-            bool lexical_lookup_has_load_error = false) -> void;
-
-  // Pops the top scope from scope_stack_, cleaning up names from
-  // lexical_lookup_.
+  // Pushes a scope for a declaration name's parameters.
+  auto PushForDeclName() -> void;
+
+  // Pushes a non-function entity scope. Functions must use
+  // `PushForFunctionBody` instead.
+  auto PushForEntity(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
+                     SemIR::SpecificId specific_id,
+                     bool lexical_lookup_has_load_error = false) -> void;
+
+  // Pushes a scope which should be in the same region as the current scope.
+  // These can be in a function without breaking `return` scoping. For example,
+  // this is used by struct literals and code blocks.
+  auto PushForSameRegion() -> void;
+
+  // Pushes a function scope.
+  auto PushForFunctionBody(SemIR::InstId scope_inst_id) -> void;
+
+  // Pops the top scope from scope_stack_. Removes names from lexical_lookup_.
   auto Pop() -> void;
 
   // Pops the top scope from scope_stack_ if it contains no names.
@@ -98,8 +94,12 @@ class ScopeStack {
     return Peek().specific_id;
   }
 
-  // Returns true if current scope is lexical.
-  auto PeekIsLexicalScope() const -> bool { return Peek().is_lexical_scope(); }
+  // Returns true if the current scope is inside a function scope (either the
+  // scope itself, or a lexical scope), without an intervening entity scope.
+  auto IsInFunctionScope() const -> bool {
+    return !return_scope_stack_.empty() &&
+           !return_scope_stack_.back().nested_scope_index.has_value();
+  }
 
   // Returns the current scope, if it is of the specified kind. Otherwise,
   // returns nullopt.
@@ -117,6 +117,19 @@ class ScopeStack {
   // is already a `returned var`, returns it instead.
   auto SetReturnedVarOrGetExisting(SemIR::InstId inst_id) -> SemIR::InstId;
 
+  // Returns the `returned var` instruction that's currently in scope, or `None`
+  // if there isn't one.
+  auto GetReturnedVar() -> SemIR::InstId {
+    CARBON_CHECK(IsInFunctionScope(), "Handling return but not in a function");
+    return return_scope_stack_.back().returned_var;
+  }
+
+  // Returns the decl ID for the current return scope.
+  auto GetReturnScopeDeclId() -> SemIR::InstId {
+    CARBON_CHECK(IsInFunctionScope(), "Handling return but not in a function");
+    return return_scope_stack_.back().decl_id;
+  }
+
   // Looks up the name `name_id` in the current scope and enclosing scopes, but
   // do not look past `scope_index`. Returns the existing lookup result, if any.
   auto LookupInLexicalScopesWithin(SemIR::NameId name_id,
@@ -160,10 +173,6 @@ class ScopeStack {
   // Runs verification that the processing cleanly finished.
   auto VerifyOnFinish() const -> void;
 
-  auto return_scope_stack() -> llvm::SmallVector<ReturnScope>& {
-    return return_scope_stack_;
-  }
-
   auto break_continue_stack() -> llvm::SmallVector<BreakContinueScope>& {
     return break_continue_stack_;
   }
@@ -219,6 +228,35 @@ class ScopeStack {
     Set<SemIR::NameId> names = {};
   };
 
+  // A scope in which `return` can be used.
+  struct ReturnScope {
+    // The `FunctionDecl`.
+    SemIR::InstId decl_id;
+
+    // The value corresponding to the current `returned var`, if any. Will be
+    // set and unset as `returned var`s are declared and go out of scope.
+    SemIR::InstId returned_var = SemIR::InstId::None;
+
+    // When a nested scope interrupts a return scope, this is the index of the
+    // outermost interrupting scope (the one closest to the function scope).
+    // This can then be used to determine whether we're actually inside the most
+    // recent `ReturnScope`, or inside a different entity scope.
+    //
+    // This won't be set for functions directly inside functions, because they
+    // will have their own `ReturnScope`.
+
+    // For example, when a `class` is inside a `fn`, it interrupts the function
+    // body by setting this on `PushEntity`; `Pop` will set it back to `None`.
+    ScopeIndex nested_scope_index = ScopeIndex::None;
+  };
+
+  // Pushes a scope onto scope_stack_. NameScopeId::None is used for new scopes.
+  // lexical_lookup_has_load_error is used to limit diagnostics when a given
+  // namespace may contain a mix of both successful and failed name imports.
+  auto Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
+            SemIR::SpecificId specific_id, bool lexical_lookup_has_load_error)
+      -> void;
+
   auto Peek() const -> const ScopeStackEntry& { return scope_stack_.back(); }
 
   // Returns whether lexical lookup currently has any load errors.
@@ -227,6 +265,15 @@ class ScopeStack {
            scope_stack_.back().lexical_lookup_has_load_error;
   }
 
+  // If inside a return scope, marks a nested scope (see `nested_scope_index`).
+  // Called after pushing the new scope.
+  auto MarkNestingIfInReturnScope() -> void {
+    if (!return_scope_stack_.empty() &&
+        !return_scope_stack_.back().nested_scope_index.has_value()) {
+      return_scope_stack_.back().nested_scope_index = scope_stack_.back().index;
+    }
+  }
+
   // Checks that the provided scope's `next_compile_time_bind_index` matches the
   // full size of the current `compile_time_binding_stack_`. The values should
   // always match, and this is used to validate the correspondence during
@@ -246,8 +293,8 @@ class ScopeStack {
   // A stack for scope context.
   llvm::SmallVector<ScopeStackEntry> scope_stack_;
 
-  // A stack of `destroy` functions to call. This only has entries for lexical
-  // scopes, because non-lexical scopes don't have destruction on scope exit.
+  // A stack of `destroy` functions to call. This only has entries inside of
+  // function bodies, where destruction on scope exit is required.
   ArrayStack<SemIR::InstId> destroy_id_stack_;
 
   // Information about non-lexical scopes. This is a subset of the entries and

+ 73 - 15
toolchain/check/testdata/alias/fail_control_flow.carbon

@@ -8,35 +8,93 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/alias/fail_control_flow.carbon
 
-// CHECK:STDERR: fail_control_flow.carbon:[[@LINE+16]]:11: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_simple.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_simple.carbon:[[@LINE+8]]:11: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: alias a = true or false;
 // CHECK:STDERR:           ^~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_control_flow.carbon:[[@LINE+12]]:11: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: alias a = true or false;
-// CHECK:STDERR:           ^~~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_control_flow.carbon:[[@LINE+8]]:11: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: alias a = true or false;
-// CHECK:STDERR:           ^~~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_control_flow.carbon:[[@LINE+4]]:11: error: alias initializer must be a name reference [AliasRequiresNameRef]
+// CHECK:STDERR: fail_simple.carbon:[[@LINE+4]]:11: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: alias a = true or false;
 // CHECK:STDERR:           ^~~~~~~~~~~~~
 // CHECK:STDERR:
 alias a = true or false;
 
-// CHECK:STDOUT: --- fail_control_flow.carbon
+// --- fail_nested.carbon
+
+library "[[@TEST_NAME]]";
+
+base class C(B:! bool) {}
+
+fn F() {
+  class B {
+    // CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:20: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+    // CHECK:STDERR:     extend base: C(true or false);
+    // CHECK:STDERR:                    ^~~~~~~~~~~~~
+    // CHECK:STDERR:
+    extend base: C(true or false);
+  }
+}
+
+// CHECK:STDOUT: --- fail_simple.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_nested.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %B.7dd: bool = bind_symbolic_name B, 0 [symbolic]
+// CHECK:STDOUT:   %B.patt: bool = symbolic_binding_pattern B, 0 [symbolic]
+// CHECK:STDOUT:   %C.type: type = generic_class_type @C [concrete]
+// CHECK:STDOUT:   %C.generic: %C.type = struct_value () [concrete]
+// CHECK:STDOUT:   %C: type = class_type @C, @C(%B.7dd) [symbolic]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %B.d97: type = class_type @B [concrete]
 // CHECK:STDOUT:   %true: bool = bool_literal true [concrete]
+// CHECK:STDOUT:   %false: bool = bool_literal false [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @C(<unexpected>.inst27.loc4_14: bool) {
+// CHECK:STDOUT:   %B: bool = bind_symbolic_name B, 0 [symbolic = %B (constants.%B.7dd)]
+// CHECK:STDOUT:   %B.patt.loc4_14.2: bool = symbolic_binding_pattern B, 0 [symbolic = %B.patt.loc4_14.2 (constants.%B.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
+// CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type]
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%C
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: imports {
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %C.ref: %C.type = name_ref C, <unexpected>.inst32.loc4_24 [concrete = constants.%C.generic]
+// CHECK:STDOUT:   %true.loc12_20: bool = bool_literal true [concrete = constants.%true]
+// CHECK:STDOUT:   %.loc12: bool = not %true.loc12_20 [concrete = constants.%false]
+// CHECK:STDOUT:   %true.loc12_25: bool = bool_literal true [concrete = constants.%true]
+// CHECK:STDOUT:   if %.loc12 br !or.rhs else br !or.result(%true.loc12_25)
+// CHECK:STDOUT:   complete_type_witness = invalid
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%B.d97
+// CHECK:STDOUT:   .C = <poisoned>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   %.loc27: bool = block_arg <unexpected instblockref inst_block5> [concrete = constants.%true]
-// CHECK:STDOUT:   %a: <error> = bind_alias a, <error> [concrete = <error>]
+// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%B.7dd) {
+// CHECK:STDOUT:   %B => constants.%B.7dd
+// CHECK:STDOUT:   %B.patt.loc4_14.2 => constants.%B.patt
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 7 - 6
toolchain/check/testdata/class/no_prelude/destroy_calls.carbon

@@ -48,7 +48,6 @@ fn F() {
   var explicit_return: ExplicitReturn;
   var with_addr: WithAddr;
   if (true) {
-    // TODO: Destroy in nested scope.
     var in_scope: NoAddr;
   }
 }
@@ -476,22 +475,24 @@ fn G() { F({}); }
 // CHECK:STDOUT: !if.then:
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %in_scope.patt: %NoAddr = binding_pattern in_scope
-// CHECK:STDOUT:     %.loc11: %NoAddr = var_pattern %in_scope.patt
+// CHECK:STDOUT:     %.loc10_5.1: %NoAddr = var_pattern %in_scope.patt
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %in_scope.var: ref %NoAddr = var in_scope
 // CHECK:STDOUT:   %destroy.ref.4: %destroy.type.bc5 = name_ref destroy, imports.%Main.import_ref.7fe [concrete = constants.%destroy.60f]
 // CHECK:STDOUT:   %destroy.bound.4: <bound method> = bound_method %in_scope.var, %destroy.ref.4
-// CHECK:STDOUT:   %NoAddr.ref.loc11: type = name_ref NoAddr, imports.%Main.NoAddr [concrete = constants.%NoAddr]
+// CHECK:STDOUT:   %NoAddr.ref.loc10: type = name_ref NoAddr, imports.%Main.NoAddr [concrete = constants.%NoAddr]
 // CHECK:STDOUT:   %in_scope: ref %NoAddr = bind_name in_scope, %in_scope.var
 // CHECK:STDOUT:   br !if.else
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.else:
+// CHECK:STDOUT:   %.loc10_5.2: %NoAddr = bind_value %in_scope.var
+// CHECK:STDOUT:   %destroy.call.1: init %empty_tuple.type = call %destroy.bound.4(%.loc10_5.2)
 // CHECK:STDOUT:   %addr: %ptr.b4e = addr_of %with_addr.var
-// CHECK:STDOUT:   %destroy.call.1: init %empty_tuple.type = call %destroy.bound.3(%addr)
+// CHECK:STDOUT:   %destroy.call.2: init %empty_tuple.type = call %destroy.bound.3(%addr)
 // CHECK:STDOUT:   %.loc7_3.2: %ExplicitReturn = bind_value %explicit_return.var
-// CHECK:STDOUT:   %destroy.call.2: init %empty_tuple.type = call %destroy.bound.2(%.loc7_3.2)
+// CHECK:STDOUT:   %destroy.call.3: init %empty_tuple.type = call %destroy.bound.2(%.loc7_3.2)
 // CHECK:STDOUT:   %.loc6_3.2: %NoAddr = bind_value %no_addr.var
-// CHECK:STDOUT:   %destroy.call.3: init %empty_tuple.type = call %destroy.bound.1(%.loc6_3.2)
+// CHECK:STDOUT:   %destroy.call.4: init %empty_tuple.type = call %destroy.bound.1(%.loc6_3.2)
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 108 - 75
toolchain/check/testdata/if_expr/fail_not_in_function.carbon

@@ -8,133 +8,166 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/if_expr/fail_not_in_function.carbon
 
-// CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+16]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_basic.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_basic.carbon:[[@LINE+12]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: let x: i32 = if true then 1 else 0;
 // CHECK:STDERR:              ^~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+12]]:22: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_basic.carbon:[[@LINE+8]]:22: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: let x: i32 = if true then 1 else 0;
 // CHECK:STDERR:                      ^~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+8]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_basic.carbon:[[@LINE+4]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: let x: i32 = if true then 1 else 0;
 // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+4]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: let x: i32 = if true then 1 else 0;
-// CHECK:STDERR:              ^~~~~~~
-// CHECK:STDERR:
 let x: i32 = if true then 1 else 0;
 
-// CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+8]]:8: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_types.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_types.carbon:[[@LINE+4]]:8: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var y: if true then i32 else f64;
 // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+4]]:8: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: var y: if true then i32 else f64;
-// CHECK:STDERR:        ^~~~~~~
-// CHECK:STDERR:
 var y: if true then i32 else f64;
 
-// CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+8]]:9: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_in_param.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_in_param.carbon:[[@LINE+4]]:9: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: fn F(a: if true then i32 else f64);
 // CHECK:STDERR:         ^~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+4]]:9: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: fn F(a: if true then i32 else f64);
-// CHECK:STDERR:         ^~~~~~~
-// CHECK:STDERR:
 fn F(a: if true then i32 else f64);
 
+// --- fail_class.carbon
+
+library "[[@TEST_NAME]]";
+
 class C {
-  // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+16]]:10: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+  // CHECK:STDERR: fail_class.carbon:[[@LINE+12]]:10: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
   // CHECK:STDERR:   var n: if true then i32 else f64;
   // CHECK:STDERR:          ^~~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+12]]:18: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+  // CHECK:STDERR: fail_class.carbon:[[@LINE+8]]:18: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
   // CHECK:STDERR:   var n: if true then i32 else f64;
   // CHECK:STDERR:                  ^~~~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+8]]:10: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+  // CHECK:STDERR: fail_class.carbon:[[@LINE+4]]:10: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
   // CHECK:STDERR:   var n: if true then i32 else f64;
   // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+4]]:10: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-  // CHECK:STDERR:   var n: if true then i32 else f64;
-  // CHECK:STDERR:          ^~~~~~~
-  // CHECK:STDERR:
   var n: if true then i32 else f64;
 }
 
-// CHECK:STDOUT: --- fail_not_in_function.carbon
+// --- fail_nested_class.carbon
+
+library "[[@TEST_NAME]]";
+
+base class C(T:! type) {}
+
+fn F() {
+  class B {
+    // CHECK:STDERR: fail_nested_class.carbon:[[@LINE+4]]:20: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+    // CHECK:STDERR:     extend base: C(if true then i32 else i32);
+    // CHECK:STDERR:                    ^~~~~~~~~~~~~~~~~~~~~~~~~
+    // CHECK:STDERR:
+    extend base: C(if true then i32 else i32);
+  }
+}
+
+// CHECK:STDOUT: --- fail_basic.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:   %true: bool = bool_literal true [concrete]
-// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
-// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
-// CHECK:STDOUT:     .Int = %Core.Int
-// CHECK:STDOUT:     .ImplicitAs = %Core.ImplicitAs
-// CHECK:STDOUT:     .Float = %Core.Float
-// CHECK:STDOUT:     import Core//prelude
-// CHECK:STDOUT:     import Core//prelude/...
-// CHECK:STDOUT:   }
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .x = %x
-// CHECK:STDOUT:     .y = %y
-// CHECK:STDOUT:     .F = %F.decl
-// CHECK:STDOUT:     .C = %C.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %x.patt: %i32 = binding_pattern x
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.loc27: type = splice_block %i32 [concrete = constants.%i32] {
-// 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:   }
-// CHECK:STDOUT:   %x: %i32 = bind_name x, <unexpected>.inst1317.loc27_14
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %y.patt: %i32 = binding_pattern y
-// CHECK:STDOUT:     %.loc37: %i32 = var_pattern %y.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %y.var: ref %i32 = var y
-// CHECK:STDOUT:   %y: ref %i32 = bind_name y, %y.var
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
-// CHECK:STDOUT:     %a.patt: %i32 = binding_pattern a
-// CHECK:STDOUT:     %a.param_patt: %i32 = value_param_pattern %a.patt, call_param0
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %a.param: %i32 = value_param call_param0
-// CHECK:STDOUT:     %a: %i32 = bind_name a, %a.param
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_in_param.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %true: bool = bool_literal true [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT:   %true: bool = bool_literal true [concrete = constants.%true]
 // CHECK:STDOUT:   if %true br !if.expr.then else br !if.expr.else
-// CHECK:STDOUT:   complete_type_witness = <unexpected>.inst1384.loc67_1
+// CHECK:STDOUT:   complete_type_witness = invalid
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
-// CHECK:STDOUT:   .n = <unexpected>.inst1381.loc66_8
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%a.param: %i32);
+// CHECK:STDOUT: --- fail_nested_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %C.type: type = generic_class_type @C [concrete]
+// CHECK:STDOUT:   %C.generic: %C.type = struct_value () [concrete]
+// CHECK:STDOUT:   %C: type = class_type @C, @C(%T) [symbolic]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %B: type = class_type @B [concrete]
+// CHECK:STDOUT:   %true: bool = bool_literal true [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @__global_init() {
-// CHECK:STDOUT: !entry:
+// CHECK:STDOUT: generic class @C(<unexpected>.inst17.loc4_14: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc4_14.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc4_14.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
+// CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%C
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B {
+// CHECK:STDOUT:   %C.ref: %C.type = name_ref C, <unexpected>.inst21.loc4_24 [concrete = constants.%C.generic]
 // CHECK:STDOUT:   %true: bool = bool_literal true [concrete = constants.%true]
 // CHECK:STDOUT:   if %true br !if.expr.then else br !if.expr.else
+// CHECK:STDOUT:   complete_type_witness = invalid
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%B
+// CHECK:STDOUT:   .C = <poisoned>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_14.2 => constants.%T.patt
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 78 - 112
toolchain/check/testdata/operators/builtin/fail_and_or_not_in_function.carbon

@@ -8,155 +8,121 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/operators/builtin/fail_and_or_not_in_function.carbon
 
+// --- fail_and.carbon
+
+library "[[@TEST_NAME]]";
+
 // TODO: Make this a compile-time function.
 fn F(b: bool) -> type {
   return if b then i32 else f64;
 }
 
 // TODO: Short-circuit operators should be permitted outside functions.
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+10]]:13: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_and.carbon:[[@LINE+4]]:13: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var and_: F(true and true);
 // CHECK:STDERR:             ^~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+6]]:11: error: cannot evaluate type expression [TypeExprEvaluationFailure]
-// CHECK:STDERR: var and_: F(true and true);
-// CHECK:STDERR:           ^~~~~~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_and_or_not_in_function.carbon: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR:
 var and_: F(true and true);
 
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+12]]:21: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_and_val.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_and_val.carbon:[[@LINE+8]]:21: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var and_val: bool = true and true;
 // CHECK:STDERR:                     ^~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+8]]:21: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: var and_val: bool = true and true;
-// CHECK:STDERR:                     ^~~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+4]]:21: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_and_val.carbon:[[@LINE+4]]:21: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var and_val: bool = true and true;
 // CHECK:STDERR:                     ^~~~~~~~~~~~~
 // CHECK:STDERR:
 var and_val: bool = true and true;
 
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+10]]:12: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_or.carbon
+
+library "[[@TEST_NAME]]";
+
+// TODO: Make this a compile-time function.
+fn F(b: bool) -> type {
+  return if b then i32 else f64;
+}
+
+// CHECK:STDERR: fail_or.carbon:[[@LINE+4]]:12: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var or_: F(true or true);
 // CHECK:STDERR:            ^~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+6]]:10: error: cannot evaluate type expression [TypeExprEvaluationFailure]
-// CHECK:STDERR: var or_: F(true or true);
-// CHECK:STDERR:          ^~~~~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_and_or_not_in_function.carbon: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR:
 var or_: F(true or true);
 
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+12]]:20: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_or_val.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_or_val.carbon:[[@LINE+8]]:20: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var or_val: bool = true or true;
 // CHECK:STDERR:                    ^~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+8]]:20: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: var or_val: bool = true or true;
-// CHECK:STDERR:                    ^~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_and_or_not_in_function.carbon:[[@LINE+4]]:20: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_or_val.carbon:[[@LINE+4]]:20: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var or_val: bool = true or true;
 // CHECK:STDERR:                    ^~~~~~~~~~~~
 // CHECK:STDERR:
 var or_val: bool = true or true;
 
-// CHECK:STDOUT: --- fail_and_or_not_in_function.carbon
+// CHECK:STDOUT: --- fail_and.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %Bool.type: type = fn_type @Bool [concrete]
-// CHECK:STDOUT:   %Bool: %Bool.type = struct_value () [concrete]
-// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
-// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
 // CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
 // CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
 // CHECK:STDOUT:   %int_64: Core.IntLiteral = int_value 64 [concrete]
 // CHECK:STDOUT:   %Float.type: type = fn_type @Float [concrete]
 // CHECK:STDOUT:   %Float: %Float.type = struct_value () [concrete]
-// CHECK:STDOUT:   %true: bool = bool_literal true [concrete]
-// CHECK:STDOUT:   %false: bool = bool_literal false [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
-// CHECK:STDOUT:     .Bool = %Core.Bool
-// CHECK:STDOUT:     .Int = %Core.Int
-// CHECK:STDOUT:     .Float = %Core.Float
-// CHECK:STDOUT:     import Core//prelude
-// CHECK:STDOUT:     import Core//prelude/...
-// CHECK:STDOUT:   }
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%b.param: bool) -> type {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %b.ref: bool = name_ref b, <unexpected>.inst27.loc5_6
+// CHECK:STDOUT:   if %b.ref br !if.expr.then else br !if.expr.else
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then:
+// 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:   br !if.expr.result(%i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else:
+// CHECK:STDOUT:   %int_64: Core.IntLiteral = int_value 64 [concrete = constants.%int_64]
+// CHECK:STDOUT:   %float.make_type: init type = call constants.%Float(%int_64) [concrete = f64]
+// CHECK:STDOUT:   %.loc6_24.1: type = value_of_initializer %float.make_type [concrete = f64]
+// CHECK:STDOUT:   %.loc6_24.2: type = converted %float.make_type, %.loc6_24.1 [concrete = f64]
+// CHECK:STDOUT:   br !if.expr.result(%.loc6_24.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result:
+// CHECK:STDOUT:   %.loc6_10: type = block_arg !if.expr.result
+// CHECK:STDOUT:   return %.loc6_10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_and_val.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .F = %F.decl
-// CHECK:STDOUT:     .and_ = %and_
-// CHECK:STDOUT:     .and_val = %and_val
-// CHECK:STDOUT:     .or_ = %or_
-// CHECK:STDOUT:     .or_val = %or_val
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
-// CHECK:STDOUT:     %b.patt: bool = binding_pattern b
-// CHECK:STDOUT:     %b.param_patt: bool = value_param_pattern %b.patt, call_param0
-// CHECK:STDOUT:     %return.patt: type = return_slot_pattern
-// CHECK:STDOUT:     %return.param_patt: type = out_param_pattern %return.patt, call_param1
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %b.param: bool = value_param call_param0
-// CHECK:STDOUT:     %.loc12_9.1: type = splice_block %.loc12_9.3 [concrete = bool] {
-// CHECK:STDOUT:       %bool.make_type: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:       %.loc12_9.2: type = value_of_initializer %bool.make_type [concrete = bool]
-// CHECK:STDOUT:       %.loc12_9.3: type = converted %bool.make_type, %.loc12_9.2 [concrete = bool]
-// CHECK:STDOUT:     }
-// CHECK:STDOUT:     %b: bool = bind_name b, %b.param
-// CHECK:STDOUT:     %return.param: ref type = out_param call_param1
-// CHECK:STDOUT:     %return: ref type = return_slot %return.param
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %and_.patt: <error> = binding_pattern and_
-// CHECK:STDOUT:     %.loc27: <error> = var_pattern %and_.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %and_.var: ref <error> = var and_
-// CHECK:STDOUT:   %and_: <error> = bind_name and_, <error>
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %and_val.patt: bool = binding_pattern and_val
-// CHECK:STDOUT:     %.loc41_1: bool = var_pattern %and_val.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %and_val.var: ref bool = var and_val
-// CHECK:STDOUT:   %.loc41_14.1: type = splice_block %.loc41_14.3 [concrete = bool] {
-// CHECK:STDOUT:     %bool.make_type.loc41: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:     %.loc41_14.2: type = value_of_initializer %bool.make_type.loc41 [concrete = bool]
-// CHECK:STDOUT:     %.loc41_14.3: type = converted %bool.make_type.loc41, %.loc41_14.2 [concrete = bool]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %and_val: ref bool = bind_name and_val, %and_val.var
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %or_.patt: <error> = binding_pattern or_
-// CHECK:STDOUT:     %.loc53: <error> = var_pattern %or_.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %or_.var: ref <error> = var or_
-// CHECK:STDOUT:   %or_: <error> = bind_name or_, <error>
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %or_val.patt: bool = binding_pattern or_val
-// CHECK:STDOUT:     %.loc67_1: bool = var_pattern %or_val.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %or_val.var: ref bool = var or_val
-// CHECK:STDOUT:   %.loc67_13.1: type = splice_block %.loc67_13.3 [concrete = bool] {
-// CHECK:STDOUT:     %bool.make_type.loc67: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:     %.loc67_13.2: type = value_of_initializer %bool.make_type.loc67 [concrete = bool]
-// CHECK:STDOUT:     %.loc67_13.3: type = converted %bool.make_type.loc67, %.loc67_13.2 [concrete = bool]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %or_val: ref bool = bind_name or_val, %or_val.var
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_or.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:   %int_64: Core.IntLiteral = int_value 64 [concrete]
+// CHECK:STDOUT:   %Float.type: type = fn_type @Float [concrete]
+// CHECK:STDOUT:   %Float: %Float.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
 // CHECK:STDOUT: fn @F(%b.param: bool) -> type {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %b.ref: bool = name_ref b, %b
+// CHECK:STDOUT:   %b.ref: bool = name_ref b, <unexpected>.inst27.loc5_6
 // CHECK:STDOUT:   if %b.ref br !if.expr.then else br !if.expr.else
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.then:
@@ -167,19 +133,19 @@ var or_val: bool = true or true;
 // CHECK:STDOUT: !if.expr.else:
 // CHECK:STDOUT:   %int_64: Core.IntLiteral = int_value 64 [concrete = constants.%int_64]
 // CHECK:STDOUT:   %float.make_type: init type = call constants.%Float(%int_64) [concrete = f64]
-// CHECK:STDOUT:   %.loc13_24.1: type = value_of_initializer %float.make_type [concrete = f64]
-// CHECK:STDOUT:   %.loc13_24.2: type = converted %float.make_type, %.loc13_24.1 [concrete = f64]
-// CHECK:STDOUT:   br !if.expr.result(%.loc13_24.2)
+// CHECK:STDOUT:   %.loc6_24.1: type = value_of_initializer %float.make_type [concrete = f64]
+// CHECK:STDOUT:   %.loc6_24.2: type = converted %float.make_type, %.loc6_24.1 [concrete = f64]
+// CHECK:STDOUT:   br !if.expr.result(%.loc6_24.2)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.result:
-// CHECK:STDOUT:   %.loc13_10: type = block_arg !if.expr.result
-// CHECK:STDOUT:   return %.loc13_10
+// CHECK:STDOUT:   %.loc6_10: type = block_arg !if.expr.result
+// CHECK:STDOUT:   return %.loc6_10
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @__global_init() {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %true: bool = bool_literal true [concrete = constants.%true]
-// CHECK:STDOUT:   %false: bool = bool_literal false [concrete = constants.%false]
-// CHECK:STDOUT:   if %true br !and.rhs else br !and.result(%false)
+// CHECK:STDOUT: --- fail_or_val.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:

+ 44 - 92
toolchain/check/testdata/var/fail_todo_control_flow_init.carbon

@@ -8,143 +8,95 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/var/fail_todo_control_flow_init.carbon
 
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+16]]:13: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_if_true.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_if_true.carbon:[[@LINE+12]]:13: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var x: () = if true then () else ();
 // CHECK:STDERR:             ^~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+12]]:21: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_if_true.carbon:[[@LINE+8]]:21: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var x: () = if true then () else ();
 // CHECK:STDERR:                     ^~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+8]]:13: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_if_true.carbon:[[@LINE+4]]:13: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var x: () = if true then () else ();
 // CHECK:STDERR:             ^~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+4]]:13: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: var x: () = if true then () else ();
-// CHECK:STDERR:             ^~~~~~~
-// CHECK:STDERR:
 var x: () = if true then () else ();
 
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+16]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_if_false.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_if_false.carbon:[[@LINE+12]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var x2: () = if false then () else ();
 // CHECK:STDERR:              ^~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+12]]:23: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_if_false.carbon:[[@LINE+8]]:23: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var x2: () = if false then () else ();
 // CHECK:STDERR:                       ^~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+8]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_if_false.carbon:[[@LINE+4]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var x2: () = if false then () else ();
 // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+4]]:14: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: var x2: () = if false then () else ();
-// CHECK:STDERR:              ^~~~~~~~
-// CHECK:STDERR:
 var x2: () = if false then () else ();
 
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+12]]:15: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_true_or_false.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_true_or_false.carbon:[[@LINE+8]]:15: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var y: bool = true or false;
 // CHECK:STDERR:               ^~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+8]]:15: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: var y: bool = true or false;
-// CHECK:STDERR:               ^~~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+4]]:15: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_true_or_false.carbon:[[@LINE+4]]:15: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var y: bool = true or false;
 // CHECK:STDERR:               ^~~~~~~~~~~~~
 // CHECK:STDERR:
 var y: bool = true or false;
 
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+12]]:16: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// --- fail_false_or_true.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_false_or_true.carbon:[[@LINE+8]]:16: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var y2: bool = false or true;
 // CHECK:STDERR:                ^~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+8]]:16: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
-// CHECK:STDERR: var y2: bool = false or true;
-// CHECK:STDERR:                ^~~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_todo_control_flow_init.carbon:[[@LINE+4]]:16: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
+// CHECK:STDERR: fail_false_or_true.carbon:[[@LINE+4]]:16: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo]
 // CHECK:STDERR: var y2: bool = false or true;
 // CHECK:STDERR:                ^~~~~~~~~~~~~
 // CHECK:STDERR:
 var y2: bool = false or true;
 
-// CHECK:STDOUT: --- fail_todo_control_flow_init.carbon
+// CHECK:STDOUT: --- fail_if_true.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
-// CHECK:STDOUT:   %true: bool = bool_literal true [concrete]
-// CHECK:STDOUT:   %Bool.type: type = fn_type @Bool [concrete]
-// CHECK:STDOUT:   %Bool: %Bool.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
-// CHECK:STDOUT:     .Bool = %Core.Bool
-// CHECK:STDOUT:     import Core//prelude
-// CHECK:STDOUT:     import Core//prelude/...
-// CHECK:STDOUT:   }
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_if_false.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .x = %x
-// CHECK:STDOUT:     .x2 = %x2
-// CHECK:STDOUT:     .y = %y
-// CHECK:STDOUT:     .y2 = %y2
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %x.patt: %empty_tuple.type = binding_pattern x
-// CHECK:STDOUT:     %.loc27_1: %empty_tuple.type = var_pattern %x.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %x.var: ref %empty_tuple.type = var x
-// CHECK:STDOUT:   %.loc27_9.1: type = splice_block %.loc27_9.3 [concrete = constants.%empty_tuple.type] {
-// CHECK:STDOUT:     %.loc27_9.2: %empty_tuple.type = tuple_literal ()
-// CHECK:STDOUT:     %.loc27_9.3: type = converted %.loc27_9.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %x: ref %empty_tuple.type = bind_name x, %x.var
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %x2.patt: %empty_tuple.type = binding_pattern x2
-// CHECK:STDOUT:     %.loc45_1: %empty_tuple.type = var_pattern %x2.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %x2.var: ref %empty_tuple.type = var x2
-// CHECK:STDOUT:   %.loc45_10.1: type = splice_block %.loc45_10.3 [concrete = constants.%empty_tuple.type] {
-// CHECK:STDOUT:     %.loc45_10.2: %empty_tuple.type = tuple_literal ()
-// CHECK:STDOUT:     %.loc45_10.3: type = converted %.loc45_10.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %x2: ref %empty_tuple.type = bind_name x2, %x2.var
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %y.patt: bool = binding_pattern y
-// CHECK:STDOUT:     %.loc59_1: bool = var_pattern %y.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %y.var: ref bool = var y
-// CHECK:STDOUT:   %.loc59_8.1: type = splice_block %.loc59_8.3 [concrete = bool] {
-// CHECK:STDOUT:     %bool.make_type.loc59: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:     %.loc59_8.2: type = value_of_initializer %bool.make_type.loc59 [concrete = bool]
-// CHECK:STDOUT:     %.loc59_8.3: type = converted %bool.make_type.loc59, %.loc59_8.2 [concrete = bool]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %y: ref bool = bind_name y, %y.var
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %y2.patt: bool = binding_pattern y2
-// CHECK:STDOUT:     %.loc73_1: bool = var_pattern %y2.patt
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %y2.var: ref bool = var y2
-// CHECK:STDOUT:   %.loc73_9.1: type = splice_block %.loc73_9.3 [concrete = bool] {
-// CHECK:STDOUT:     %bool.make_type.loc73: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:     %.loc73_9.2: type = value_of_initializer %bool.make_type.loc73 [concrete = bool]
-// CHECK:STDOUT:     %.loc73_9.3: type = converted %bool.make_type.loc73, %.loc73_9.2 [concrete = bool]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %y2: ref bool = bind_name y2, %y2.var
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_true_or_false.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @__global_init() {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %true: bool = bool_literal true [concrete = constants.%true]
-// CHECK:STDOUT:   if %true br !if.expr.then else br !if.expr.else
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_false_or_true.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT: