Bladeren bron

Constant evaluation support for `if` expressions, `and`, and `or`. (#3840)

These can't be supported by `TryEvalInst`, because we don't track
sufficient information about predecessors and branch conditions in SemIR
to efficiently compute the constant result. Even if we could do so, we
may not want to treat all `BlockArg`s for which we can infer a constant
value as being constants. Instead, set the constant value explicitly
after creating the corresponding `BlockArg` instruction.
Richard Smith 2 jaren geleden
bovenliggende
commit
583f5aa508

+ 29 - 0
toolchain/check/context.cpp

@@ -479,6 +479,35 @@ auto Context::AddConvergenceBlockWithArgAndPush(
   return AddInst({node_id, SemIR::BlockArg{result_type_id, new_block_id}});
 }
 
+auto Context::SetBlockArgResultBeforeConstantUse(SemIR::InstId select_id,
+                                                 SemIR::InstId cond_id,
+                                                 SemIR::InstId if_true,
+                                                 SemIR::InstId if_false)
+    -> void {
+  CARBON_CHECK(insts().Is<SemIR::BlockArg>(select_id));
+
+  // Determine the constant result based on the condition value.
+  SemIR::ConstantId const_id = SemIR::ConstantId::NotConstant;
+  auto cond_const_id = constant_values().Get(cond_id);
+  if (!cond_const_id.is_template()) {
+    // Symbolic or non-constant condition means a non-constant result.
+  } else if (auto literal = insts().TryGetAs<SemIR::BoolLiteral>(
+                 cond_const_id.inst_id())) {
+    const_id = constant_values().Get(literal.value().value.ToBool() ? if_true
+                                                                    : if_false);
+  } else {
+    CARBON_CHECK(cond_const_id == SemIR::ConstantId::Error)
+        << "Unexpected constant branch condition.";
+    const_id = SemIR::ConstantId::Error;
+  }
+
+  if (const_id.is_constant()) {
+    CARBON_VLOG() << "Constant: " << insts().Get(select_id) << " -> "
+                  << const_id.inst_id() << "\n";
+    constant_values().Set(select_id, const_id);
+  }
+}
+
 // Add the current code block to the enclosing function.
 auto Context::AddCurrentCodeBlockToFunction(Parse::NodeId node_id) -> void {
   CARBON_CHECK(!inst_block_stack().empty()) << "no current code block";

+ 11 - 0
toolchain/check/context.h

@@ -177,6 +177,17 @@ class Context {
       Parse::NodeId node_id, std::initializer_list<SemIR::InstId> block_args)
       -> SemIR::InstId;
 
+  // Sets the constant value of a block argument created as the result of a
+  // branch.  `select_id` should be a `BlockArg` that selects between two
+  // values. `cond_id` is the condition, `if_false` is the value to use if the
+  // condition is false, and `if_true` is the value to use if the condition is
+  // true.  We don't track enough information in the `BlockArg` inst for
+  // `TryEvalInst` to do this itself.
+  auto SetBlockArgResultBeforeConstantUse(SemIR::InstId select_id,
+                                          SemIR::InstId cond_id,
+                                          SemIR::InstId if_true,
+                                          SemIR::InstId if_false) -> void;
+
   // Add the current code block to the enclosing function.
   // TODO: The node_id is taken for expressions, which can occur in
   // non-function contexts. This should be refactored to support non-function

+ 5 - 1
toolchain/check/handle_if_expr.cpp

@@ -11,10 +11,11 @@ auto HandleIfExprIf(Context& context, Parse::IfExprIfId node_id) -> bool {
   // Alias node_id for if/then/else consistency.
   auto& if_node = node_id;
 
-  auto cond_value_id = context.node_stack().PopExpr();
+  auto [cond_node, cond_value_id] = context.node_stack().PopExprWithNodeId();
 
   // Convert the condition to `bool`, and branch on it.
   cond_value_id = ConvertToBoolValue(context, if_node, cond_value_id);
+  context.node_stack().Push(cond_node, cond_value_id);
   auto then_block_id =
       context.AddDominatedBlockAndBranchIf(if_node, cond_value_id);
   auto else_block_id = context.AddDominatedBlockAndBranch(if_node);
@@ -51,6 +52,7 @@ auto HandleIfExprElse(Context& context, Parse::IfExprElseId node_id) -> bool {
   auto then_value_id = context.node_stack().Pop<Parse::NodeKind::IfExprThen>();
   auto [if_node, _] =
       context.node_stack().PopWithNodeId<Parse::NodeKind::IfExprIf>();
+  auto cond_value_id = context.node_stack().PopExpr();
 
   // Convert the `else` value to the `then` value's type, and finish the `else`
   // block.
@@ -62,6 +64,8 @@ auto HandleIfExprElse(Context& context, Parse::IfExprElseId node_id) -> bool {
   // Create a resumption block and branches to it.
   auto chosen_value_id = context.AddConvergenceBlockWithArgAndPush(
       if_node, {else_value_id, then_value_id});
+  context.SetBlockArgResultBeforeConstantUse(chosen_value_id, cond_value_id,
+                                             then_value_id, else_value_id);
   context.AddCurrentCodeBlockToFunction(node_id);
 
   // Push the result value.

+ 12 - 2
toolchain/check/handle_operator.cpp

@@ -343,7 +343,7 @@ auto HandlePrefixOperatorStar(Context& context,
 static auto HandleShortCircuitOperand(Context& context, Parse::NodeId node_id,
                                       bool is_or) -> bool {
   // Convert the condition to `bool`.
-  auto cond_value_id = context.node_stack().PopExpr();
+  auto [cond_node, cond_value_id] = context.node_stack().PopExprWithNodeId();
   cond_value_id = ConvertToBoolValue(context, node_id, cond_value_id);
   auto bool_type_id = context.insts().Get(cond_value_id).type_id();
 
@@ -362,6 +362,11 @@ static auto HandleShortCircuitOperand(Context& context, Parse::NodeId node_id,
   auto end_block_id = context.AddDominatedBlockAndBranchWithArg(
       node_id, short_circuit_result_id);
 
+  // Push the branch condition and result for use when handling the complete
+  // expression.
+  context.node_stack().Push(cond_node, branch_value_id);
+  context.node_stack().Push(cond_node, short_circuit_result_id);
+
   // Push the resumption and the right-hand side blocks, and start emitting the
   // right-hand operand.
   context.inst_block_stack().Pop();
@@ -391,6 +396,8 @@ auto HandleShortCircuitOperandOr(Context& context,
 static auto HandleShortCircuitOperator(Context& context, Parse::NodeId node_id)
     -> bool {
   auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
+  auto short_circuit_result_id = context.node_stack().PopExpr();
+  auto branch_value_id = context.node_stack().PopExpr();
 
   // The first operand is wrapped in a ShortCircuitOperand, which we
   // already handled by creating a RHS block and a resumption block, which
@@ -405,9 +412,12 @@ static auto HandleShortCircuitOperator(Context& context, Parse::NodeId node_id)
   context.AddCurrentCodeBlockToFunction(node_id);
 
   // Collect the result from either the first or second operand.
-  context.AddInstAndPush(
+  auto result_id = context.AddInst(
       {node_id, SemIR::BlockArg{context.insts().Get(rhs_id).type_id(),
                                 resume_block_id}});
+  context.SetBlockArgResultBeforeConstantUse(result_id, branch_value_id, rhs_id,
+                                             short_circuit_result_id);
+  context.node_stack().Push(node_id, result_id);
   return true;
 }
 

+ 1 - 1
toolchain/check/testdata/alias/fail_control_flow.carbon

@@ -25,7 +25,7 @@ alias a = true or false;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   %.loc18: bool = block_arg <unexpected instblockref block4>
+// CHECK:STDOUT:   %.loc18: bool = block_arg <unexpected instblockref block4> [template = constants.%.1]
 // CHECK:STDOUT:   %a: <error> = bind_alias a, <error> [template = <error>]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 108 - 0
toolchain/check/testdata/if_expr/constant_condition.carbon

@@ -15,6 +15,18 @@ fn G() -> i32 {
   return if false then A() else B();
 }
 
+fn Constant() -> i32 {
+  var v: if true then i32 else i32* = 1;
+  var w: if false then i32 else i32* = &v;
+  return *w;
+}
+
+fn PartiallyConstant(t: type) -> i32 {
+  var v: if true then i32 else t = 1;
+  var w: if false then t else i32* = &v;
+  return *w;
+}
+
 // CHECK:STDOUT: --- constant_condition.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -22,6 +34,7 @@ fn G() -> i32 {
 // CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.3: bool = bool_literal true [template]
 // CHECK:STDOUT:   %.4: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.5: type = ptr_type i32 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -30,6 +43,8 @@ fn G() -> i32 {
 // CHECK:STDOUT:     .B = %B
 // CHECK:STDOUT:     .F = %F
 // CHECK:STDOUT:     .G = %G
+// CHECK:STDOUT:     .Constant = %Constant
+// CHECK:STDOUT:     .PartiallyConstant = %PartiallyConstant
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %A: <function> = fn_decl @A [template] {
 // CHECK:STDOUT:     %return.var.loc7: ref i32 = var <return slot>
@@ -43,6 +58,14 @@ fn G() -> i32 {
 // CHECK:STDOUT:   %G: <function> = fn_decl @G [template] {
 // CHECK:STDOUT:     %return.var.loc14: ref i32 = var <return slot>
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Constant: <function> = fn_decl @Constant [template] {
+// CHECK:STDOUT:     %return.var.loc18: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %PartiallyConstant: <function> = fn_decl @PartiallyConstant [template] {
+// CHECK:STDOUT:     %t.loc24_22.1: type = param t
+// CHECK:STDOUT:     @PartiallyConstant.%t: type = bind_name t, %t.loc24_22.1
+// CHECK:STDOUT:     %return.var.loc24: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @A() -> i32 {
@@ -105,3 +128,88 @@ fn G() -> i32 {
 // CHECK:STDOUT:   return %.loc15_10
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @Constant() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc19_13: bool = bool_literal true [template = constants.%.3]
+// CHECK:STDOUT:   if %.loc19_13 br !if.expr.then.loc19 else br !if.expr.else.loc19
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc19:
+// CHECK:STDOUT:   br !if.expr.result.loc19(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc19:
+// CHECK:STDOUT:   %.loc19_35: type = ptr_type i32 [template = constants.%.5]
+// CHECK:STDOUT:   br !if.expr.result.loc19(%.loc19_35)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc19:
+// CHECK:STDOUT:   %.loc19_10: type = block_arg !if.expr.result.loc19 [template = i32]
+// CHECK:STDOUT:   %v.var: ref i32 = var v
+// CHECK:STDOUT:   %v: ref i32 = bind_name v, %v.var
+// CHECK:STDOUT:   %.loc19_39: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   assign %v.var, %.loc19_39
+// CHECK:STDOUT:   %.loc20_13: bool = bool_literal false [template = constants.%.4]
+// CHECK:STDOUT:   if %.loc20_13 br !if.expr.then.loc20 else br !if.expr.else.loc20
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc20:
+// CHECK:STDOUT:   br !if.expr.result.loc20(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc20:
+// CHECK:STDOUT:   %.loc20_36: type = ptr_type i32 [template = constants.%.5]
+// CHECK:STDOUT:   br !if.expr.result.loc20(%.loc20_36)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc20:
+// CHECK:STDOUT:   %.loc20_10: type = block_arg !if.expr.result.loc20 [template = constants.%.5]
+// CHECK:STDOUT:   %w.var: ref i32* = var w
+// CHECK:STDOUT:   %w: ref i32* = bind_name w, %w.var
+// CHECK:STDOUT:   %v.ref: ref i32 = name_ref v, %v
+// CHECK:STDOUT:   %.loc20_40: i32* = addr_of %v.ref
+// CHECK:STDOUT:   assign %w.var, %.loc20_40
+// CHECK:STDOUT:   %w.ref: ref i32* = name_ref w, %w
+// CHECK:STDOUT:   %.loc21_11: i32* = bind_value %w.ref
+// CHECK:STDOUT:   %.loc21_10.1: ref i32 = deref %.loc21_11
+// CHECK:STDOUT:   %.loc21_10.2: i32 = bind_value %.loc21_10.1
+// CHECK:STDOUT:   return %.loc21_10.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @PartiallyConstant(%t: type) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc25_13: bool = bool_literal true [template = constants.%.3]
+// CHECK:STDOUT:   if %.loc25_13 br !if.expr.then.loc25 else br !if.expr.else.loc25
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc25:
+// CHECK:STDOUT:   br !if.expr.result.loc25(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc25:
+// CHECK:STDOUT:   %t.ref.loc25: type = name_ref t, %t
+// CHECK:STDOUT:   br !if.expr.result.loc25(%t.ref.loc25)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc25:
+// CHECK:STDOUT:   %.loc25_10: type = block_arg !if.expr.result.loc25 [template = i32]
+// CHECK:STDOUT:   %v.var: ref i32 = var v
+// CHECK:STDOUT:   %v: ref i32 = bind_name v, %v.var
+// CHECK:STDOUT:   %.loc25_36: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   assign %v.var, %.loc25_36
+// CHECK:STDOUT:   %.loc26_13: bool = bool_literal false [template = constants.%.4]
+// CHECK:STDOUT:   if %.loc26_13 br !if.expr.then.loc26 else br !if.expr.else.loc26
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc26:
+// CHECK:STDOUT:   %t.ref.loc26: type = name_ref t, %t
+// CHECK:STDOUT:   br !if.expr.result.loc26(%t.ref.loc26)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc26:
+// CHECK:STDOUT:   %.loc26_34: type = ptr_type i32 [template = constants.%.5]
+// CHECK:STDOUT:   br !if.expr.result.loc26(%.loc26_34)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc26:
+// CHECK:STDOUT:   %.loc26_10: type = block_arg !if.expr.result.loc26 [template = constants.%.5]
+// CHECK:STDOUT:   %w.var: ref i32* = var w
+// CHECK:STDOUT:   %w: ref i32* = bind_name w, %w.var
+// CHECK:STDOUT:   %v.ref: ref i32 = name_ref v, %v
+// CHECK:STDOUT:   %.loc26_38: i32* = addr_of %v.ref
+// CHECK:STDOUT:   assign %w.var, %.loc26_38
+// CHECK:STDOUT:   %w.ref: ref i32* = name_ref w, %w
+// CHECK:STDOUT:   %.loc27_11: i32* = bind_value %w.ref
+// CHECK:STDOUT:   %.loc27_10.1: ref i32 = deref %.loc27_11
+// CHECK:STDOUT:   %.loc27_10.2: i32 = bind_value %.loc27_10.1
+// CHECK:STDOUT:   return %.loc27_10.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 13
toolchain/check/testdata/if_expr/fail_not_in_function.carbon

@@ -4,7 +4,6 @@
 //
 // AUTOUPDATE
 
-// TODO: Should work with compile-time evaluation.
 // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+12]]:14: ERROR: Semantics TODO: `Control flow expressions are currently only supported inside functions.`.
 // CHECK:STDERR: let x: i32 = if true then 1 else 0;
 // CHECK:STDERR:              ^~~~~~~
@@ -20,16 +19,11 @@
 let x: i32 = if true then 1 else 0;
 
 class C {
-  // TODO: Should work with compile-time evaluation.
-  // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+15]]:10: ERROR: Semantics TODO: `Control flow expressions are currently only supported inside functions.`.
-  // CHECK:STDERR:   var n: if true then i32 else f64;
-  // CHECK:STDERR:          ^~~~~~~
-  // CHECK:STDERR:
   // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+11]]:10: ERROR: Semantics TODO: `Control flow expressions are currently only supported inside functions.`.
   // CHECK:STDERR:   var n: if true then i32 else f64;
-  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:          ^~~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+7]]:10: ERROR: Cannot evaluate type expression.
+  // CHECK:STDERR: fail_not_in_function.carbon:[[@LINE+7]]:10: ERROR: Semantics TODO: `Control flow expressions are currently only supported inside functions.`.
   // CHECK:STDERR:   var n: if true then i32 else f64;
   // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
@@ -46,20 +40,22 @@ class C {
 // CHECK:STDOUT:   %.2: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.3: i32 = int_literal 0 [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.4: type = unbound_element_type C, i32 [template]
+// CHECK:STDOUT:   %.5: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   %.loc20: i32 = block_arg <unexpected instblockref block6>
-// CHECK:STDOUT:   %x: i32 = bind_name x, %.loc20
+// CHECK:STDOUT:   %.loc19: i32 = block_arg <unexpected instblockref block6> [template = constants.%.2]
+// CHECK:STDOUT:   %x: i32 = bind_name x, %.loc19
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   %.loc39: bool = bool_literal true [template = constants.%.1]
-// CHECK:STDOUT:   if %.loc39 br !if.expr.then else br !if.expr.else
+// CHECK:STDOUT:   %.loc33: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc33 br !if.expr.then else br !if.expr.else
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
-// CHECK:STDOUT:   .n = <unexpected instref inst+21>
+// CHECK:STDOUT:   .n = <unexpected instref inst+22>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 128 - 0
toolchain/check/testdata/if_expr/fail_partial_constant.carbon

@@ -0,0 +1,128 @@
+// 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
+
+// --- fail_non_constant_condition.carbon
+
+package NonConstantCondition api;
+
+fn ConditionIsNonConstant(b: bool) {
+  // We choose to not accept this even if both arms evaluate to the same
+  // constant value, because it notionally involves evaluating a non-constant
+  // condition.
+  // CHECK:STDERR: fail_non_constant_condition.carbon:[[@LINE+4]]:10: ERROR: Cannot evaluate type expression.
+  // CHECK:STDERR:   var v: if b then i32 else i32 = 1;
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var v: if b then i32 else i32 = 1;
+}
+
+// --- fail_non_constant_result.carbon
+
+package NonConstantResult api;
+
+fn ChosenBranchIsNonConstant(t: type) {
+  // CHECK:STDERR: fail_non_constant_result.carbon:[[@LINE+4]]:10: ERROR: Cannot evaluate type expression.
+  // CHECK:STDERR:   var v: if true then t else i32 = 1;
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var v: if true then t else i32 = 1;
+  // CHECK:STDERR: fail_non_constant_result.carbon:[[@LINE+3]]:10: ERROR: Cannot evaluate type expression.
+  // CHECK:STDERR:   var w: if false then i32 else t = 1;
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~
+  var w: if false then i32 else t = 1;
+}
+
+// CHECK:STDOUT: --- fail_non_constant_condition.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .ConditionIsNonConstant = %ConditionIsNonConstant
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ConditionIsNonConstant: <function> = fn_decl @ConditionIsNonConstant [template] {
+// CHECK:STDOUT:     %b.loc4_27.1: bool = param b
+// CHECK:STDOUT:     @ConditionIsNonConstant.%b: bool = bind_name b, %b.loc4_27.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @ConditionIsNonConstant(%b: bool) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %b.ref: bool = name_ref b, %b
+// CHECK:STDOUT:   if %b.ref br !if.expr.then else br !if.expr.else
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then:
+// CHECK:STDOUT:   br !if.expr.result(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else:
+// CHECK:STDOUT:   br !if.expr.result(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result:
+// CHECK:STDOUT:   %.loc12_10: type = block_arg !if.expr.result
+// CHECK:STDOUT:   %v.var: ref <error> = var v
+// CHECK:STDOUT:   %v: ref <error> = bind_name v, %v.var
+// CHECK:STDOUT:   %.loc12_35: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   assign %v.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_non_constant_result.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.3: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .ChosenBranchIsNonConstant = %ChosenBranchIsNonConstant
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ChosenBranchIsNonConstant: <function> = fn_decl @ChosenBranchIsNonConstant [template] {
+// CHECK:STDOUT:     %t.loc4_30.1: type = param t
+// CHECK:STDOUT:     @ChosenBranchIsNonConstant.%t: type = bind_name t, %t.loc4_30.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @ChosenBranchIsNonConstant(%t: type) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc9_13: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc9_13 br !if.expr.then.loc9 else br !if.expr.else.loc9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc9:
+// CHECK:STDOUT:   %t.ref.loc9: type = name_ref t, %t
+// CHECK:STDOUT:   br !if.expr.result.loc9(%t.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc9:
+// CHECK:STDOUT:   br !if.expr.result.loc9(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc9:
+// CHECK:STDOUT:   %.loc9_10: type = block_arg !if.expr.result.loc9
+// CHECK:STDOUT:   %v.var: ref <error> = var v
+// CHECK:STDOUT:   %v: ref <error> = bind_name v, %v.var
+// CHECK:STDOUT:   %.loc9_36: i32 = int_literal 1 [template = constants.%.2]
+// CHECK:STDOUT:   assign %v.var, <error>
+// CHECK:STDOUT:   %.loc13_13: bool = bool_literal false [template = constants.%.3]
+// CHECK:STDOUT:   if %.loc13_13 br !if.expr.then.loc13 else br !if.expr.else.loc13
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc13:
+// CHECK:STDOUT:   br !if.expr.result.loc13(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc13:
+// CHECK:STDOUT:   %t.ref.loc13: type = name_ref t, %t
+// CHECK:STDOUT:   br !if.expr.result.loc13(%t.ref.loc13)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc13:
+// CHECK:STDOUT:   %.loc13_10: type = block_arg !if.expr.result.loc13
+// CHECK:STDOUT:   %w.var: ref <error> = var w
+// CHECK:STDOUT:   %w: ref <error> = bind_name w, %w.var
+// CHECK:STDOUT:   %.loc13_37: i32 = int_literal 1 [template = constants.%.2]
+// CHECK:STDOUT:   assign %w.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 168 - 0
toolchain/check/testdata/operators/builtin/and.carbon

@@ -11,11 +11,24 @@ fn And() -> bool {
   return F() and G();
 }
 
+fn Constant() {
+  var a: if true and true then bool else () = true;
+  var b: if true and false then bool else () = ();
+  var c: if false and true then bool else () = ();
+  var d: if false and false then bool else () = ();
+}
+
+fn PartialConstant(x: bool) {
+  var a: if false and x then bool else () = ();
+}
+
 // CHECK:STDOUT: --- and.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: bool = bool_literal true [template]
 // CHECK:STDOUT:   %.2: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %.4: () = tuple_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -23,6 +36,8 @@ fn And() -> bool {
 // CHECK:STDOUT:     .F = %F
 // CHECK:STDOUT:     .G = %G
 // CHECK:STDOUT:     .And = %And
+// CHECK:STDOUT:     .Constant = %Constant
+// CHECK:STDOUT:     .PartialConstant = %PartialConstant
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
 // CHECK:STDOUT:     %return.var.loc7: ref bool = var <return slot>
@@ -33,6 +48,11 @@ fn And() -> bool {
 // CHECK:STDOUT:   %And: <function> = fn_decl @And [template] {
 // CHECK:STDOUT:     %return.var.loc10: ref bool = var <return slot>
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Constant: <function> = fn_decl @Constant [template] {}
+// CHECK:STDOUT:   %PartialConstant: <function> = fn_decl @PartialConstant [template] {
+// CHECK:STDOUT:     %x.loc21_20.1: bool = param x
+// CHECK:STDOUT:     @PartialConstant.%x: bool = bind_name x, %x.loc21_20.1
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> bool {
@@ -68,3 +88,151 @@ fn And() -> bool {
 // CHECK:STDOUT:   return %.loc11_14.4
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @Constant() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc15_13: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   %.loc15_18.1: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc15_13 br !and.rhs.loc15 else br !and.result.loc15(%.loc15_18.1)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.rhs.loc15:
+// CHECK:STDOUT:   %.loc15_22: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   br !and.result.loc15(%.loc15_22)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.result.loc15:
+// CHECK:STDOUT:   %.loc15_18.2: bool = block_arg !and.result.loc15 [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc15_18.2 br !if.expr.then.loc15 else br !if.expr.else.loc15
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc15:
+// CHECK:STDOUT:   br !if.expr.result.loc15(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc15:
+// CHECK:STDOUT:   %.loc15_43.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc15_43.2: type = converted %.loc15_43.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc15(%.loc15_43.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc15:
+// CHECK:STDOUT:   %.loc15_10: type = block_arg !if.expr.result.loc15 [template = bool]
+// CHECK:STDOUT:   %a.var: ref bool = var a
+// CHECK:STDOUT:   %a: ref bool = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc15_47: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   assign %a.var, %.loc15_47
+// CHECK:STDOUT:   %.loc16_13: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   %.loc16_18.1: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc16_13 br !and.rhs.loc16 else br !and.result.loc16(%.loc16_18.1)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.rhs.loc16:
+// CHECK:STDOUT:   %.loc16_22: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   br !and.result.loc16(%.loc16_22)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.result.loc16:
+// CHECK:STDOUT:   %.loc16_18.2: bool = block_arg !and.result.loc16 [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc16_18.2 br !if.expr.then.loc16 else br !if.expr.else.loc16
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc16:
+// CHECK:STDOUT:   br !if.expr.result.loc16(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc16:
+// CHECK:STDOUT:   %.loc16_44.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc16_44.2: type = converted %.loc16_44.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc16(%.loc16_44.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc16:
+// CHECK:STDOUT:   %.loc16_10: type = block_arg !if.expr.result.loc16 [template = constants.%.3]
+// CHECK:STDOUT:   %b.var: ref () = var b
+// CHECK:STDOUT:   %b: ref () = bind_name b, %b.var
+// CHECK:STDOUT:   %.loc16_49.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc16_49.2: init () = tuple_init () to %b.var [template = constants.%.4]
+// CHECK:STDOUT:   %.loc16_49.3: init () = converted %.loc16_49.1, %.loc16_49.2 [template = constants.%.4]
+// CHECK:STDOUT:   assign %b.var, %.loc16_49.3
+// CHECK:STDOUT:   %.loc17_13: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   %.loc17_19.1: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc17_13 br !and.rhs.loc17 else br !and.result.loc17(%.loc17_19.1)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.rhs.loc17:
+// CHECK:STDOUT:   %.loc17_23: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   br !and.result.loc17(%.loc17_23)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.result.loc17:
+// CHECK:STDOUT:   %.loc17_19.2: bool = block_arg !and.result.loc17 [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc17_19.2 br !if.expr.then.loc17 else br !if.expr.else.loc17
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc17:
+// CHECK:STDOUT:   br !if.expr.result.loc17(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc17:
+// CHECK:STDOUT:   %.loc17_44.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc17_44.2: type = converted %.loc17_44.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc17(%.loc17_44.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc17:
+// CHECK:STDOUT:   %.loc17_10: type = block_arg !if.expr.result.loc17 [template = constants.%.3]
+// CHECK:STDOUT:   %c.var: ref () = var c
+// CHECK:STDOUT:   %c: ref () = bind_name c, %c.var
+// CHECK:STDOUT:   %.loc17_49.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc17_49.2: init () = tuple_init () to %c.var [template = constants.%.4]
+// CHECK:STDOUT:   %.loc17_49.3: init () = converted %.loc17_49.1, %.loc17_49.2 [template = constants.%.4]
+// CHECK:STDOUT:   assign %c.var, %.loc17_49.3
+// CHECK:STDOUT:   %.loc18_13: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   %.loc18_19.1: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc18_13 br !and.rhs.loc18 else br !and.result.loc18(%.loc18_19.1)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.rhs.loc18:
+// CHECK:STDOUT:   %.loc18_23: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   br !and.result.loc18(%.loc18_23)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.result.loc18:
+// CHECK:STDOUT:   %.loc18_19.2: bool = block_arg !and.result.loc18 [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc18_19.2 br !if.expr.then.loc18 else br !if.expr.else.loc18
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc18:
+// CHECK:STDOUT:   br !if.expr.result.loc18(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc18:
+// CHECK:STDOUT:   %.loc18_45.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc18_45.2: type = converted %.loc18_45.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc18(%.loc18_45.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc18:
+// CHECK:STDOUT:   %.loc18_10: type = block_arg !if.expr.result.loc18 [template = constants.%.3]
+// CHECK:STDOUT:   %d.var: ref () = var d
+// CHECK:STDOUT:   %d: ref () = bind_name d, %d.var
+// CHECK:STDOUT:   %.loc18_50.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc18_50.2: init () = tuple_init () to %d.var [template = constants.%.4]
+// CHECK:STDOUT:   %.loc18_50.3: init () = converted %.loc18_50.1, %.loc18_50.2 [template = constants.%.4]
+// CHECK:STDOUT:   assign %d.var, %.loc18_50.3
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @PartialConstant(%x: bool) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc22_13: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   %.loc22_19.1: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc22_13 br !and.rhs else br !and.result(%.loc22_19.1)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.rhs:
+// CHECK:STDOUT:   %x.ref: bool = name_ref x, %x
+// CHECK:STDOUT:   br !and.result(%x.ref)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.result:
+// CHECK:STDOUT:   %.loc22_19.2: bool = block_arg !and.result [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc22_19.2 br !if.expr.then else br !if.expr.else
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then:
+// CHECK:STDOUT:   br !if.expr.result(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else:
+// CHECK:STDOUT:   %.loc22_41.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc22_41.2: type = converted %.loc22_41.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result(%.loc22_41.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result:
+// CHECK:STDOUT:   %.loc22_10: type = block_arg !if.expr.result [template = constants.%.3]
+// CHECK:STDOUT:   %a.var: ref () = var a
+// CHECK:STDOUT:   %a: ref () = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc22_46.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc22_46.2: init () = tuple_init () to %a.var [template = constants.%.4]
+// CHECK:STDOUT:   %.loc22_46.3: init () = converted %.loc22_46.1, %.loc22_46.2 [template = constants.%.4]
+// CHECK:STDOUT:   assign %a.var, %.loc22_46.3
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/operators/builtin/fail_and_or_not_in_function.carbon

@@ -45,7 +45,7 @@ var or_: F(true or true);
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   %.loc38_17: bool = block_arg <unexpected instblockref block14>
+// CHECK:STDOUT:   %.loc38_17: bool = block_arg <unexpected instblockref block14> [template = constants.%.1]
 // CHECK:STDOUT:   %.loc38_11.1: init type = call <unexpected instref inst+27>(%.loc38_17)
 // CHECK:STDOUT:   %.loc38_24: type = value_of_initializer %.loc38_11.1
 // CHECK:STDOUT:   %.loc38_11.2: type = converted %.loc38_11.1, %.loc38_24

+ 185 - 0
toolchain/check/testdata/operators/builtin/fail_and_or_partial_constant.carbon

@@ -0,0 +1,185 @@
+// 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
+
+// --- fail_non_constant_result.carbon
+
+package FailNonConstantResult api;
+
+fn PartialConstant(x: bool) {
+  // CHECK:STDERR: fail_non_constant_result.carbon:[[@LINE+4]]:10: ERROR: Cannot evaluate type expression.
+  // CHECK:STDERR:   var a: if true and x then bool else ();
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var a: if true and x then bool else ();
+  // CHECK:STDERR: fail_non_constant_result.carbon:[[@LINE+4]]:10: ERROR: Cannot evaluate type expression.
+  // CHECK:STDERR:   var b: if false or x then bool else ();
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var b: if false or x then bool else ();
+}
+
+// --- fail_despite_known_result.carbon
+
+package FailDespiteKnownResult api;
+
+fn KnownValueButNonConstantCondition(x: bool) {
+  // We choose not to give these cases constant values, even though we could,
+  // because they notionally involve evaluating a non-constant condition.
+  // CHECK:STDERR: fail_despite_known_result.carbon:[[@LINE+4]]:10: ERROR: Cannot evaluate type expression.
+  // CHECK:STDERR:   var c: if x and false then bool else ();
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var c: if x and false then bool else ();
+  // CHECK:STDERR: fail_despite_known_result.carbon:[[@LINE+3]]:10: ERROR: Cannot evaluate type expression.
+  // CHECK:STDERR:   var d: if x or true then bool else ();
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  var d: if x or true then bool else ();
+}
+
+// CHECK:STDOUT: --- fail_non_constant_result.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.2: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .PartialConstant = %PartialConstant
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %PartialConstant: <function> = fn_decl @PartialConstant [template] {
+// CHECK:STDOUT:     %x.loc4_20.1: bool = param x
+// CHECK:STDOUT:     @PartialConstant.%x: bool = bind_name x, %x.loc4_20.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @PartialConstant(%x: bool) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc9_13: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   %.loc9_18.1: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc9_13 br !and.rhs else br !and.result(%.loc9_18.1)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.rhs:
+// CHECK:STDOUT:   %x.ref.loc9: bool = name_ref x, %x
+// CHECK:STDOUT:   br !and.result(%x.ref.loc9)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.result:
+// CHECK:STDOUT:   %.loc9_18.2: bool = block_arg !and.result
+// CHECK:STDOUT:   if %.loc9_18.2 br !if.expr.then.loc9 else br !if.expr.else.loc9
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc9:
+// CHECK:STDOUT:   br !if.expr.result.loc9(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc9:
+// CHECK:STDOUT:   %.loc9_40.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc9_40.2: type = converted %.loc9_40.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc9(%.loc9_40.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc9:
+// CHECK:STDOUT:   %.loc9_10: type = block_arg !if.expr.result.loc9
+// CHECK:STDOUT:   %a.var: ref <error> = var a
+// CHECK:STDOUT:   %a: ref <error> = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc14_13: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   %.loc14_19.1: bool = not %.loc14_13 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc14_19.2: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc14_19.1 br !or.rhs else br !or.result(%.loc14_19.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.rhs:
+// CHECK:STDOUT:   %x.ref.loc14: bool = name_ref x, %x
+// CHECK:STDOUT:   br !or.result(%x.ref.loc14)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.result:
+// CHECK:STDOUT:   %.loc14_19.3: bool = block_arg !or.result
+// CHECK:STDOUT:   if %.loc14_19.3 br !if.expr.then.loc14 else br !if.expr.else.loc14
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc14:
+// CHECK:STDOUT:   br !if.expr.result.loc14(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc14:
+// CHECK:STDOUT:   %.loc14_40.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc14_40.2: type = converted %.loc14_40.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc14(%.loc14_40.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc14:
+// CHECK:STDOUT:   %.loc14_10: type = block_arg !if.expr.result.loc14
+// CHECK:STDOUT:   %b.var: ref <error> = var b
+// CHECK:STDOUT:   %b: ref <error> = bind_name b, %b.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_despite_known_result.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %.3: bool = bool_literal true [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .KnownValueButNonConstantCondition = %KnownValueButNonConstantCondition
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %KnownValueButNonConstantCondition: <function> = fn_decl @KnownValueButNonConstantCondition [template] {
+// CHECK:STDOUT:     %x.loc4_38.1: bool = param x
+// CHECK:STDOUT:     @KnownValueButNonConstantCondition.%x: bool = bind_name x, %x.loc4_38.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @KnownValueButNonConstantCondition(%x: bool) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %x.ref.loc11: bool = name_ref x, %x
+// CHECK:STDOUT:   %.loc11_15.1: bool = bool_literal false [template = constants.%.1]
+// CHECK:STDOUT:   if %x.ref.loc11 br !and.rhs else br !and.result(%.loc11_15.1)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.rhs:
+// CHECK:STDOUT:   %.loc11_19: bool = bool_literal false [template = constants.%.1]
+// CHECK:STDOUT:   br !and.result(%.loc11_19)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !and.result:
+// CHECK:STDOUT:   %.loc11_15.2: bool = block_arg !and.result
+// CHECK:STDOUT:   if %.loc11_15.2 br !if.expr.then.loc11 else br !if.expr.else.loc11
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc11:
+// CHECK:STDOUT:   br !if.expr.result.loc11(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc11:
+// CHECK:STDOUT:   %.loc11_41.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc11_41.2: type = converted %.loc11_41.1, constants.%.2 [template = constants.%.2]
+// CHECK:STDOUT:   br !if.expr.result.loc11(%.loc11_41.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc11:
+// CHECK:STDOUT:   %.loc11_10: type = block_arg !if.expr.result.loc11
+// CHECK:STDOUT:   %c.var: ref <error> = var c
+// CHECK:STDOUT:   %c: ref <error> = bind_name c, %c.var
+// CHECK:STDOUT:   %x.ref.loc15: bool = name_ref x, %x
+// CHECK:STDOUT:   %.loc15_15.1: bool = not %x.ref.loc15
+// CHECK:STDOUT:   %.loc15_15.2: bool = bool_literal true [template = constants.%.3]
+// CHECK:STDOUT:   if %.loc15_15.1 br !or.rhs else br !or.result(%.loc15_15.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.rhs:
+// CHECK:STDOUT:   %.loc15_18: bool = bool_literal true [template = constants.%.3]
+// CHECK:STDOUT:   br !or.result(%.loc15_18)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.result:
+// CHECK:STDOUT:   %.loc15_15.3: bool = block_arg !or.result
+// CHECK:STDOUT:   if %.loc15_15.3 br !if.expr.then.loc15 else br !if.expr.else.loc15
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc15:
+// CHECK:STDOUT:   br !if.expr.result.loc15(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc15:
+// CHECK:STDOUT:   %.loc15_39.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc15_39.2: type = converted %.loc15_39.1, constants.%.2 [template = constants.%.2]
+// CHECK:STDOUT:   br !if.expr.result.loc15(%.loc15_39.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc15:
+// CHECK:STDOUT:   %.loc15_10: type = block_arg !if.expr.result.loc15
+// CHECK:STDOUT:   %d.var: ref <error> = var d
+// CHECK:STDOUT:   %d: ref <error> = bind_name d, %d.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/operators/builtin/fail_assignment_to_non_assignable.carbon

@@ -160,7 +160,7 @@ fn Main() {
 // CHECK:STDOUT:   br !if.expr.result.loc45(%.loc45_24)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !if.expr.result.loc45:
-// CHECK:STDOUT:   %.loc45_4: i32 = block_arg !if.expr.result.loc45
+// CHECK:STDOUT:   %.loc45_4: i32 = block_arg !if.expr.result.loc45 [template = constants.%.1]
 // CHECK:STDOUT:   %.loc45_29: i32 = int_literal 3 [template = constants.%.4]
 // CHECK:STDOUT:   assign %.loc45_4, %.loc45_29
 // CHECK:STDOUT:   %a.var: ref i32 = var a

+ 168 - 0
toolchain/check/testdata/operators/builtin/or.carbon

@@ -11,10 +11,24 @@ fn Or() -> bool {
   return F() or G();
 }
 
+fn Constant() {
+  var a: if true or true then bool else () = true;
+  var b: if true or false then bool else () = true;
+  var c: if false or true then bool else () = true;
+  var d: if false or false then bool else () = ();
+}
+
+fn PartialConstant(x: bool) {
+  var a: if true or x then bool else () = true;
+}
+
 // CHECK:STDOUT: --- or.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.2: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %.4: () = tuple_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -22,6 +36,8 @@ fn Or() -> bool {
 // CHECK:STDOUT:     .F = %F
 // CHECK:STDOUT:     .G = %G
 // CHECK:STDOUT:     .Or = %Or
+// CHECK:STDOUT:     .Constant = %Constant
+// CHECK:STDOUT:     .PartialConstant = %PartialConstant
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template] {
 // CHECK:STDOUT:     %return.var.loc7: ref bool = var <return slot>
@@ -32,6 +48,11 @@ fn Or() -> bool {
 // CHECK:STDOUT:   %Or: <function> = fn_decl @Or [template] {
 // CHECK:STDOUT:     %return.var.loc10: ref bool = var <return slot>
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Constant: <function> = fn_decl @Constant [template] {}
+// CHECK:STDOUT:   %PartialConstant: <function> = fn_decl @PartialConstant [template] {
+// CHECK:STDOUT:     %x.loc21_20.1: bool = param x
+// CHECK:STDOUT:     @PartialConstant.%x: bool = bind_name x, %x.loc21_20.1
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> bool {
@@ -68,3 +89,150 @@ fn Or() -> bool {
 // CHECK:STDOUT:   return %.loc11_14.5
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @Constant() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc15_13: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   %.loc15_18.1: bool = not %.loc15_13 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc15_18.2: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc15_18.1 br !or.rhs.loc15 else br !or.result.loc15(%.loc15_18.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.rhs.loc15:
+// CHECK:STDOUT:   %.loc15_21: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   br !or.result.loc15(%.loc15_21)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.result.loc15:
+// CHECK:STDOUT:   %.loc15_18.3: bool = block_arg !or.result.loc15 [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc15_18.3 br !if.expr.then.loc15 else br !if.expr.else.loc15
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc15:
+// CHECK:STDOUT:   br !if.expr.result.loc15(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc15:
+// CHECK:STDOUT:   %.loc15_42.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc15_42.2: type = converted %.loc15_42.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc15(%.loc15_42.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc15:
+// CHECK:STDOUT:   %.loc15_10: type = block_arg !if.expr.result.loc15 [template = bool]
+// CHECK:STDOUT:   %a.var: ref bool = var a
+// CHECK:STDOUT:   %a: ref bool = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc15_46: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   assign %a.var, %.loc15_46
+// CHECK:STDOUT:   %.loc16_13: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   %.loc16_18.1: bool = not %.loc16_13 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc16_18.2: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc16_18.1 br !or.rhs.loc16 else br !or.result.loc16(%.loc16_18.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.rhs.loc16:
+// CHECK:STDOUT:   %.loc16_21: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   br !or.result.loc16(%.loc16_21)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.result.loc16:
+// CHECK:STDOUT:   %.loc16_18.3: bool = block_arg !or.result.loc16 [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc16_18.3 br !if.expr.then.loc16 else br !if.expr.else.loc16
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc16:
+// CHECK:STDOUT:   br !if.expr.result.loc16(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc16:
+// CHECK:STDOUT:   %.loc16_43.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc16_43.2: type = converted %.loc16_43.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc16(%.loc16_43.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc16:
+// CHECK:STDOUT:   %.loc16_10: type = block_arg !if.expr.result.loc16 [template = bool]
+// CHECK:STDOUT:   %b.var: ref bool = var b
+// CHECK:STDOUT:   %b: ref bool = bind_name b, %b.var
+// CHECK:STDOUT:   %.loc16_47: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   assign %b.var, %.loc16_47
+// CHECK:STDOUT:   %.loc17_13: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   %.loc17_19.1: bool = not %.loc17_13 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc17_19.2: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc17_19.1 br !or.rhs.loc17 else br !or.result.loc17(%.loc17_19.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.rhs.loc17:
+// CHECK:STDOUT:   %.loc17_22: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   br !or.result.loc17(%.loc17_22)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.result.loc17:
+// CHECK:STDOUT:   %.loc17_19.3: bool = block_arg !or.result.loc17 [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc17_19.3 br !if.expr.then.loc17 else br !if.expr.else.loc17
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc17:
+// CHECK:STDOUT:   br !if.expr.result.loc17(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc17:
+// CHECK:STDOUT:   %.loc17_43.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc17_43.2: type = converted %.loc17_43.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc17(%.loc17_43.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc17:
+// CHECK:STDOUT:   %.loc17_10: type = block_arg !if.expr.result.loc17 [template = bool]
+// CHECK:STDOUT:   %c.var: ref bool = var c
+// CHECK:STDOUT:   %c: ref bool = bind_name c, %c.var
+// CHECK:STDOUT:   %.loc17_47: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   assign %c.var, %.loc17_47
+// CHECK:STDOUT:   %.loc18_13: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   %.loc18_19.1: bool = not %.loc18_13 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc18_19.2: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc18_19.1 br !or.rhs.loc18 else br !or.result.loc18(%.loc18_19.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.rhs.loc18:
+// CHECK:STDOUT:   %.loc18_22: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   br !or.result.loc18(%.loc18_22)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.result.loc18:
+// CHECK:STDOUT:   %.loc18_19.3: bool = block_arg !or.result.loc18 [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc18_19.3 br !if.expr.then.loc18 else br !if.expr.else.loc18
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc18:
+// CHECK:STDOUT:   br !if.expr.result.loc18(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc18:
+// CHECK:STDOUT:   %.loc18_44.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc18_44.2: type = converted %.loc18_44.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc18(%.loc18_44.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc18:
+// CHECK:STDOUT:   %.loc18_10: type = block_arg !if.expr.result.loc18 [template = constants.%.3]
+// CHECK:STDOUT:   %d.var: ref () = var d
+// CHECK:STDOUT:   %d: ref () = bind_name d, %d.var
+// CHECK:STDOUT:   %.loc18_49.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc18_49.2: init () = tuple_init () to %d.var [template = constants.%.4]
+// CHECK:STDOUT:   %.loc18_49.3: init () = converted %.loc18_49.1, %.loc18_49.2 [template = constants.%.4]
+// CHECK:STDOUT:   assign %d.var, %.loc18_49.3
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @PartialConstant(%x: bool) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc22_13: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   %.loc22_18.1: bool = not %.loc22_13 [template = constants.%.2]
+// CHECK:STDOUT:   %.loc22_18.2: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc22_18.1 br !or.rhs else br !or.result(%.loc22_18.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.rhs:
+// CHECK:STDOUT:   %x.ref: bool = name_ref x, %x
+// CHECK:STDOUT:   br !or.result(%x.ref)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !or.result:
+// CHECK:STDOUT:   %.loc22_18.3: bool = block_arg !or.result [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc22_18.3 br !if.expr.then else br !if.expr.else
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then:
+// CHECK:STDOUT:   br !if.expr.result(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else:
+// CHECK:STDOUT:   %.loc22_39.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc22_39.2: type = converted %.loc22_39.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result(%.loc22_39.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result:
+// CHECK:STDOUT:   %.loc22_10: type = block_arg !if.expr.result [template = bool]
+// CHECK:STDOUT:   %a.var: ref bool = var a
+// CHECK:STDOUT:   %a: ref bool = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc22_43: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   assign %a.var, %.loc22_43
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 52 - 0
toolchain/check/testdata/operators/builtin/unary_op.carbon

@@ -11,16 +11,24 @@ fn Not(b: bool) -> bool {
 let not_true: bool = not true;
 let not_false: bool = not false;
 
+fn Constant() {
+  var a: if not true then bool else () = ();
+  var b: if not false then bool else () = true;
+}
+
 // CHECK:STDOUT: --- unary_op.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: bool = bool_literal true [template]
 // CHECK:STDOUT:   %.2: bool = bool_literal false [template]
+// CHECK:STDOUT:   %.3: type = tuple_type () [template]
+// CHECK:STDOUT:   %.4: () = tuple_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .Not = %Not
+// CHECK:STDOUT:     .Constant = %Constant
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Not: <function> = fn_decl @Not [template] {
 // CHECK:STDOUT:     %b.loc7_8.1: bool = param b
@@ -33,6 +41,7 @@ let not_false: bool = not false;
 // CHECK:STDOUT:   %.loc12_27: bool = bool_literal false [template = constants.%.2]
 // CHECK:STDOUT:   %.loc12_23: bool = not %.loc12_27 [template = constants.%.1]
 // CHECK:STDOUT:   %not_false: bool = bind_name not_false, %.loc12_23
+// CHECK:STDOUT:   %Constant: <function> = fn_decl @Constant [template] {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Not(%b: bool) -> bool {
@@ -42,3 +51,46 @@ let not_false: bool = not false;
 // CHECK:STDOUT:   return %.loc8
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @Constant() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc15_17: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   %.loc15_13: bool = not %.loc15_17 [template = constants.%.2]
+// CHECK:STDOUT:   if %.loc15_13 br !if.expr.then.loc15 else br !if.expr.else.loc15
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc15:
+// CHECK:STDOUT:   br !if.expr.result.loc15(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc15:
+// CHECK:STDOUT:   %.loc15_38.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc15_38.2: type = converted %.loc15_38.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc15(%.loc15_38.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc15:
+// CHECK:STDOUT:   %.loc15_10: type = block_arg !if.expr.result.loc15 [template = constants.%.3]
+// CHECK:STDOUT:   %a.var: ref () = var a
+// CHECK:STDOUT:   %a: ref () = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc15_43.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc15_43.2: init () = tuple_init () to %a.var [template = constants.%.4]
+// CHECK:STDOUT:   %.loc15_43.3: init () = converted %.loc15_43.1, %.loc15_43.2 [template = constants.%.4]
+// CHECK:STDOUT:   assign %a.var, %.loc15_43.3
+// CHECK:STDOUT:   %.loc16_17: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   %.loc16_13: bool = not %.loc16_17 [template = constants.%.1]
+// CHECK:STDOUT:   if %.loc16_13 br !if.expr.then.loc16 else br !if.expr.else.loc16
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.then.loc16:
+// CHECK:STDOUT:   br !if.expr.result.loc16(bool)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.else.loc16:
+// CHECK:STDOUT:   %.loc16_39.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc16_39.2: type = converted %.loc16_39.1, constants.%.3 [template = constants.%.3]
+// CHECK:STDOUT:   br !if.expr.result.loc16(%.loc16_39.2)
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.expr.result.loc16:
+// CHECK:STDOUT:   %.loc16_10: type = block_arg !if.expr.result.loc16 [template = bool]
+// CHECK:STDOUT:   %b.var: ref bool = var b
+// CHECK:STDOUT:   %b: ref bool = bind_name b, %b.var
+// CHECK:STDOUT:   %.loc16_43: bool = bool_literal true [template = constants.%.1]
+// CHECK:STDOUT:   assign %b.var, %.loc16_43
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/pointer/fail_address_of_value.carbon

@@ -184,7 +184,7 @@ fn AddressOfParam(param: i32) {
 // CHECK:STDOUT:   br !and.result(%.loc49_14)
 // CHECK:STDOUT:
 // CHECK:STDOUT: !and.result:
-// CHECK:STDOUT:   %.loc49_10.2: bool = block_arg !and.result
+// CHECK:STDOUT:   %.loc49_10.2: bool = block_arg !and.result [template = constants.%.16]
 // CHECK:STDOUT:   %.loc49_3: bool* = addr_of <error> [template = <error>]
 // CHECK:STDOUT:   %H.ref: <function> = name_ref H, file.%H [template = file.%H]
 // CHECK:STDOUT:   %.loc54_5.1: init {.a: i32} = call %H.ref()