Kaynağa Gözat

Lex/parse support for ->?, :?, and form literals (#6695)

See #5389 (pending) for the language design.
Geoff Romer 2 ay önce
ebeveyn
işleme
7938d9a8d0
31 değiştirilmiş dosya ile 758 ekleme ve 195 silme
  1. 1 0
      toolchain/check/BUILD
  2. 5 0
      toolchain/check/handle_binding_pattern.cpp
  3. 40 0
      toolchain/check/handle_form_literal.cpp
  4. 4 0
      toolchain/check/handle_function.cpp
  5. 3 0
      toolchain/check/node_stack.h
  6. 8 8
      toolchain/check/testdata/array/import.carbon
  7. 56 56
      toolchain/check/testdata/class/import_indirect.carbon
  8. 1 1
      toolchain/check/testdata/impl/error_recovery.carbon
  9. 1 1
      toolchain/check/testdata/interop/cpp/function/arithmetic_types_direct.carbon
  10. 72 72
      toolchain/check/testdata/primitives/import_symbolic.carbon
  11. 11 11
      toolchain/check/testdata/struct/import.carbon
  12. 11 11
      toolchain/check/testdata/tuple/import.carbon
  13. 1 0
      toolchain/diagnostics/kind.def
  14. 4 0
      toolchain/lex/token_kind.def
  15. 4 0
      toolchain/parse/context.h
  16. 41 16
      toolchain/parse/handle_binding_pattern.cpp
  17. 5 0
      toolchain/parse/handle_expr.cpp
  18. 78 0
      toolchain/parse/handle_form_literal.cpp
  19. 10 0
      toolchain/parse/handle_function.cpp
  20. 2 0
      toolchain/parse/node_ids.h
  21. 9 0
      toolchain/parse/node_kind.def
  22. 0 1
      toolchain/parse/precedence.cpp
  23. 77 5
      toolchain/parse/state.def
  24. 173 0
      toolchain/parse/testdata/basics/form_literals.carbon
  25. 61 1
      toolchain/parse/testdata/function/declaration.carbon
  26. 1 1
      toolchain/parse/testdata/generics/impl/fail_impl.carbon
  27. 1 1
      toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon
  28. 1 1
      toolchain/parse/testdata/let/fail_missing_type.carbon
  29. 6 1
      toolchain/parse/tree.h
  30. 9 7
      toolchain/parse/tree_and_subtrees.cpp
  31. 62 1
      toolchain/parse/typed_nodes.h

+ 1 - 0
toolchain/check/BUILD

@@ -244,6 +244,7 @@ cc_library(
         "//toolchain/lex:token_index",
         "//toolchain/lex:token_kind",
         "//toolchain/lex:tokenized_buffer",
+        "//toolchain/parse:node_category",
         "//toolchain/parse:node_kind",
         "//toolchain/parse:tree",
         "//toolchain/sem_ir:absolute_node_id",

+ 5 - 0
toolchain/check/handle_binding_pattern.cpp

@@ -301,6 +301,11 @@ auto HandleParseNode(Context& context, Parse::VarBindingPatternId node_id)
                                  Parse::NodeKind::VarBindingPattern);
 }
 
+auto HandleParseNode(Context& context, Parse::FormBindingPatternId node_id)
+    -> bool {
+  return context.TODO(node_id, "Implement :? support");
+}
+
 auto HandleParseNode(Context& context,
                      Parse::CompileTimeBindingPatternStartId /*node_id*/)
     -> bool {

+ 40 - 0
toolchain/check/handle_form_literal.cpp

@@ -0,0 +1,40 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/check/context.h"
+#include "toolchain/check/handle.h"
+#include "toolchain/parse/node_category.h"
+
+namespace Carbon::Check {
+
+auto HandleParseNode(Context& context, Parse::RefPrimitiveFormId node_id)
+    -> bool {
+  return context.TODO(node_id, "Implement form literals");
+}
+
+auto HandleParseNode(Context& context, Parse::ValPrimitiveFormId node_id)
+    -> bool {
+  return context.TODO(node_id, "Implement form literals");
+}
+
+auto HandleParseNode(Context& context, Parse::VarPrimitiveFormId node_id)
+    -> bool {
+  return context.TODO(node_id, "Implement form literals");
+}
+
+auto HandleParseNode(Context& context, Parse::FormLiteralKeywordId node_id)
+    -> bool {
+  return context.TODO(node_id, "Implement form literals");
+}
+
+auto HandleParseNode(Context& context, Parse::FormLiteralOpenParenId node_id)
+    -> bool {
+  return context.TODO(node_id, "Implement form literals");
+}
+
+auto HandleParseNode(Context& context, Parse::FormLiteralId node_id) -> bool {
+  return context.TODO(node_id, "Implement form literals");
+}
+
+}  // namespace Carbon::Check

+ 4 - 0
toolchain/check/handle_function.cpp

@@ -76,6 +76,10 @@ auto HandleParseNode(Context& context, Parse::ReturnTypeId node_id) -> bool {
   return true;
 }
 
+auto HandleParseNode(Context& context, Parse::ReturnFormId node_id) -> bool {
+  return context.TODO(node_id, "Support ->?");
+}
+
 // Diagnoses issues with the modifiers, removing modifiers that shouldn't be
 // present.
 static auto DiagnoseModifiers(Context& context,

+ 3 - 0
toolchain/check/node_stack.h

@@ -427,6 +427,7 @@ class NodeStack {
       case Parse::NodeKind::ImplicitParamList:
       case Parse::NodeKind::WhileConditionStart:
       case Parse::NodeKind::ReturnType:
+      case Parse::NodeKind::ReturnForm:
         return Id::KindFor<SemIR::InstBlockId>();
       case Parse::NodeKind::FunctionDefinitionStart:
       case Parse::NodeKind::BuiltinFunctionDefinitionStart:
@@ -497,6 +498,8 @@ class NodeStack {
       case Parse::NodeKind::FileStart:
       case Parse::NodeKind::ForHeader:
       case Parse::NodeKind::Forall:
+      case Parse::NodeKind::FormLiteralKeyword:
+      case Parse::NodeKind::FormLiteralOpenParen:
       case Parse::NodeKind::IdentifierNameQualifierWithParams:
       case Parse::NodeKind::IdentifierNameQualifierWithoutParams:
       case Parse::NodeKind::IdentifierPackageName:

+ 8 - 8
toolchain/check/testdata/array/import.carbon

@@ -31,16 +31,16 @@ fn G(n: i32) -> i32 {
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! array(i32, 1);
+  let value:! array(i32, 1);
 }
 
 class C {}
 
-// CHECK:STDERR: fail_todo_symbolic_decl.carbon:[[@LINE+4]]:26: error: expression is runtime; expected constant [EvalRequiresConstantValue]
-// CHECK:STDERR: impl C as I where .val = (1,) {}
-// CHECK:STDERR:                          ^~~~
+// CHECK:STDERR: fail_todo_symbolic_decl.carbon:[[@LINE+4]]:28: error: expression is runtime; expected constant [EvalRequiresConstantValue]
+// CHECK:STDERR: impl C as I where .value = (1,) {}
+// CHECK:STDERR:                            ^~~~
 // CHECK:STDERR:
-impl C as I where .val = (1,) {}
+impl C as I where .value = (1,) {}
 
 // --- fail_todo_import_symbolic_decl.carbon
 
@@ -50,10 +50,10 @@ import library "symbolic_decl";
 fn F() -> array(i32, 1) {
   //@dump-sem-ir-begin
   // CHECK:STDERR: fail_todo_import_symbolic_decl.carbon:[[@LINE+4]]:11: error: cannot convert type `C` into type implementing `I` [ConversionFailureTypeToFacet]
-  // CHECK:STDERR:   return (C as I).val;
+  // CHECK:STDERR:   return (C as I).value;
   // CHECK:STDERR:           ^~~~~~
   // CHECK:STDERR:
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -131,7 +131,7 @@ fn F() -> array(i32, 1) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %C.ref: type = name_ref C, imports.%Main.C [concrete = constants.%C]
 // CHECK:STDOUT:   %I.ref: type = name_ref I, imports.%Main.I [concrete = constants.%I.type]
-// CHECK:STDOUT:   %val.ref: <error> = name_ref val, <error> [concrete = <error>]
+// CHECK:STDOUT:   %value.ref: <error> = name_ref value, <error> [concrete = <error>]
 // CHECK:STDOUT:   return <error> to %return.param
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 56 - 56
toolchain/check/testdata/class/import_indirect.carbon

@@ -71,8 +71,8 @@ library "[[@TEST_NAME]]";
 import library "a";
 import library "b";
 
-var val: C = {};
-var ptr: D* = &val;
+var value: C = {};
+var ptr: D* = &value;
 
 // --- triangle_reverse.carbon
 
@@ -81,8 +81,8 @@ library "[[@TEST_NAME]]";
 import library "b";
 import library "a";
 
-var val: C = {};
-var ptr: D* = &val;
+var value: C = {};
+var ptr: D* = &value;
 
 // --- diamond.carbon
 
@@ -91,8 +91,8 @@ library "[[@TEST_NAME]]";
 import library "b";
 import library "c";
 
-var val: D = {};
-var ptr: E* = &val;
+var value: D = {};
+var ptr: E* = &value;
 
 // --- diamond_reverse.carbon
 
@@ -101,8 +101,8 @@ library "[[@TEST_NAME]]";
 import library "c";
 import library "b";
 
-var val: D = {};
-var ptr: E* = &val;
+var value: D = {};
+var ptr: E* = &value;
 
 // CHECK:STDOUT: --- a.carbon
 // CHECK:STDOUT:
@@ -339,7 +339,7 @@ var ptr: E* = &val;
 // CHECK:STDOUT:   %C.val: %C = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.31e: type = ptr_type %C [concrete]
 // CHECK:STDOUT:   %pattern_type.506: type = pattern_type %ptr.31e [concrete]
-// CHECK:STDOUT:   %addr: %ptr.31e = addr_of file.%val.var [concrete]
+// CHECK:STDOUT:   %addr: %ptr.31e = addr_of file.%value.var [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %T.67d: type = symbolic_binding T, 0 [symbolic]
@@ -379,18 +379,18 @@ var ptr: E* = &val;
 // CHECK:STDOUT:     .b_val = imports.%Main.b_val
 // CHECK:STDOUT:     .b_ptr = imports.%Main.b_ptr
 // CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .val = %val
+// CHECK:STDOUT:     .value = %value
 // CHECK:STDOUT:     .ptr = %ptr.loc8_5
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %default.import = import <none>
 // CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %val.patt: %pattern_type.7c7 = ref_binding_pattern val [concrete]
-// CHECK:STDOUT:     %val.var_patt: %pattern_type.7c7 = var_pattern %val.patt [concrete]
+// CHECK:STDOUT:     %value.patt: %pattern_type.7c7 = ref_binding_pattern value [concrete]
+// CHECK:STDOUT:     %value.var_patt: %pattern_type.7c7 = var_pattern %value.patt [concrete]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %val.var: ref %C = var %val.var_patt [concrete]
+// CHECK:STDOUT:   %value.var: ref %C = var %value.var_patt [concrete]
 // CHECK:STDOUT:   %C.ref: type = name_ref C, imports.%Main.C [concrete = constants.%C]
-// CHECK:STDOUT:   %val: ref %C = ref_binding val, %val.var [concrete = %val.var]
+// CHECK:STDOUT:   %value: ref %C = ref_binding value, %value.var [concrete = %value.var]
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %ptr.patt: %pattern_type.506 = ref_binding_pattern ptr [concrete]
 // CHECK:STDOUT:     %ptr.var_patt: %pattern_type.506 = var_pattern %ptr.patt [concrete]
@@ -412,12 +412,12 @@ var ptr: E* = &val;
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc7_15.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %.loc7_15.2: init %C to file.%val.var = class_init () [concrete = constants.%C.val]
-// CHECK:STDOUT:   %.loc7_1: init %C = converted %.loc7_15.1, %.loc7_15.2 [concrete = constants.%C.val]
-// CHECK:STDOUT:   assign file.%val.var, %.loc7_1
-// CHECK:STDOUT:   %val.ref: ref %C = name_ref val, file.%val [concrete = file.%val.var]
-// CHECK:STDOUT:   %addr: %ptr.31e = addr_of %val.ref [concrete = constants.%addr]
+// CHECK:STDOUT:   %.loc7_17.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.loc7_17.2: init %C to file.%value.var = class_init () [concrete = constants.%C.val]
+// CHECK:STDOUT:   %.loc7_1: init %C = converted %.loc7_17.1, %.loc7_17.2 [concrete = constants.%C.val]
+// CHECK:STDOUT:   assign file.%value.var, %.loc7_1
+// CHECK:STDOUT:   %value.ref: ref %C = name_ref value, file.%value [concrete = file.%value.var]
+// CHECK:STDOUT:   %addr: %ptr.31e = addr_of %value.ref [concrete = constants.%addr]
 // CHECK:STDOUT:   %impl.elem0: %.cda = impl_witness_access constants.%Copy.impl_witness.2c7, element0 [concrete = constants.%ptr.as.Copy.impl.Op.ed9]
 // CHECK:STDOUT:   %bound_method.loc8_15.1: <bound method> = bound_method %addr, %impl.elem0 [concrete = constants.%ptr.as.Copy.impl.Op.bound]
 // CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%C) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn]
@@ -438,7 +438,7 @@ var ptr: E* = &val;
 // CHECK:STDOUT:   %C.val: %C = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.31e: type = ptr_type %C [concrete]
 // CHECK:STDOUT:   %pattern_type.506: type = pattern_type %ptr.31e [concrete]
-// CHECK:STDOUT:   %addr: %ptr.31e = addr_of file.%val.var [concrete]
+// CHECK:STDOUT:   %addr: %ptr.31e = addr_of file.%value.var [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %T.67d: type = symbolic_binding T, 0 [symbolic]
@@ -478,18 +478,18 @@ var ptr: E* = &val;
 // CHECK:STDOUT:     .b_ptr = imports.%Main.b_ptr
 // CHECK:STDOUT:     .C = imports.%Main.C
 // CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .val = %val
+// CHECK:STDOUT:     .value = %value
 // CHECK:STDOUT:     .ptr = %ptr.loc8_5
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %default.import = import <none>
 // CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %val.patt: %pattern_type.7c7 = ref_binding_pattern val [concrete]
-// CHECK:STDOUT:     %val.var_patt: %pattern_type.7c7 = var_pattern %val.patt [concrete]
+// CHECK:STDOUT:     %value.patt: %pattern_type.7c7 = ref_binding_pattern value [concrete]
+// CHECK:STDOUT:     %value.var_patt: %pattern_type.7c7 = var_pattern %value.patt [concrete]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %val.var: ref %C = var %val.var_patt [concrete]
+// CHECK:STDOUT:   %value.var: ref %C = var %value.var_patt [concrete]
 // CHECK:STDOUT:   %C.ref: type = name_ref C, imports.%Main.C [concrete = constants.%C]
-// CHECK:STDOUT:   %val: ref %C = ref_binding val, %val.var [concrete = %val.var]
+// CHECK:STDOUT:   %value: ref %C = ref_binding value, %value.var [concrete = %value.var]
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %ptr.patt: %pattern_type.506 = ref_binding_pattern ptr [concrete]
 // CHECK:STDOUT:     %ptr.var_patt: %pattern_type.506 = var_pattern %ptr.patt [concrete]
@@ -511,12 +511,12 @@ var ptr: E* = &val;
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc7_15.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %.loc7_15.2: init %C to file.%val.var = class_init () [concrete = constants.%C.val]
-// CHECK:STDOUT:   %.loc7_1: init %C = converted %.loc7_15.1, %.loc7_15.2 [concrete = constants.%C.val]
-// CHECK:STDOUT:   assign file.%val.var, %.loc7_1
-// CHECK:STDOUT:   %val.ref: ref %C = name_ref val, file.%val [concrete = file.%val.var]
-// CHECK:STDOUT:   %addr: %ptr.31e = addr_of %val.ref [concrete = constants.%addr]
+// CHECK:STDOUT:   %.loc7_17.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.loc7_17.2: init %C to file.%value.var = class_init () [concrete = constants.%C.val]
+// CHECK:STDOUT:   %.loc7_1: init %C = converted %.loc7_17.1, %.loc7_17.2 [concrete = constants.%C.val]
+// CHECK:STDOUT:   assign file.%value.var, %.loc7_1
+// CHECK:STDOUT:   %value.ref: ref %C = name_ref value, file.%value [concrete = file.%value.var]
+// CHECK:STDOUT:   %addr: %ptr.31e = addr_of %value.ref [concrete = constants.%addr]
 // CHECK:STDOUT:   %impl.elem0: %.cda = impl_witness_access constants.%Copy.impl_witness.2c7, element0 [concrete = constants.%ptr.as.Copy.impl.Op.ed9]
 // CHECK:STDOUT:   %bound_method.loc8_15.1: <bound method> = bound_method %addr, %impl.elem0 [concrete = constants.%ptr.as.Copy.impl.Op.bound]
 // CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%C) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn]
@@ -537,7 +537,7 @@ var ptr: E* = &val;
 // CHECK:STDOUT:   %C.val: %C = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.31e: type = ptr_type %C [concrete]
 // CHECK:STDOUT:   %pattern_type.506: type = pattern_type %ptr.31e [concrete]
-// CHECK:STDOUT:   %addr: %ptr.31e = addr_of file.%val.var [concrete]
+// CHECK:STDOUT:   %addr: %ptr.31e = addr_of file.%value.var [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %T.67d: type = symbolic_binding T, 0 [symbolic]
@@ -581,18 +581,18 @@ var ptr: E* = &val;
 // CHECK:STDOUT:     .c_val = imports.%Main.c_val
 // CHECK:STDOUT:     .c_ptr = imports.%Main.c_ptr
 // CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .val = %val
+// CHECK:STDOUT:     .value = %value
 // CHECK:STDOUT:     .ptr = %ptr.loc8_5
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %default.import = import <none>
 // CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %val.patt: %pattern_type.7c7 = ref_binding_pattern val [concrete]
-// CHECK:STDOUT:     %val.var_patt: %pattern_type.7c7 = var_pattern %val.patt [concrete]
+// CHECK:STDOUT:     %value.patt: %pattern_type.7c7 = ref_binding_pattern value [concrete]
+// CHECK:STDOUT:     %value.var_patt: %pattern_type.7c7 = var_pattern %value.patt [concrete]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %val.var: ref %C = var %val.var_patt [concrete]
+// CHECK:STDOUT:   %value.var: ref %C = var %value.var_patt [concrete]
 // CHECK:STDOUT:   %D.ref: type = name_ref D, imports.%Main.D [concrete = constants.%C]
-// CHECK:STDOUT:   %val: ref %C = ref_binding val, %val.var [concrete = %val.var]
+// CHECK:STDOUT:   %value: ref %C = ref_binding value, %value.var [concrete = %value.var]
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %ptr.patt: %pattern_type.506 = ref_binding_pattern ptr [concrete]
 // CHECK:STDOUT:     %ptr.var_patt: %pattern_type.506 = var_pattern %ptr.patt [concrete]
@@ -614,12 +614,12 @@ var ptr: E* = &val;
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc7_15.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %.loc7_15.2: init %C to file.%val.var = class_init () [concrete = constants.%C.val]
-// CHECK:STDOUT:   %.loc7_1: init %C = converted %.loc7_15.1, %.loc7_15.2 [concrete = constants.%C.val]
-// CHECK:STDOUT:   assign file.%val.var, %.loc7_1
-// CHECK:STDOUT:   %val.ref: ref %C = name_ref val, file.%val [concrete = file.%val.var]
-// CHECK:STDOUT:   %addr: %ptr.31e = addr_of %val.ref [concrete = constants.%addr]
+// CHECK:STDOUT:   %.loc7_17.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.loc7_17.2: init %C to file.%value.var = class_init () [concrete = constants.%C.val]
+// CHECK:STDOUT:   %.loc7_1: init %C = converted %.loc7_17.1, %.loc7_17.2 [concrete = constants.%C.val]
+// CHECK:STDOUT:   assign file.%value.var, %.loc7_1
+// CHECK:STDOUT:   %value.ref: ref %C = name_ref value, file.%value [concrete = file.%value.var]
+// CHECK:STDOUT:   %addr: %ptr.31e = addr_of %value.ref [concrete = constants.%addr]
 // CHECK:STDOUT:   %impl.elem0: %.cda = impl_witness_access constants.%Copy.impl_witness.2c7, element0 [concrete = constants.%ptr.as.Copy.impl.Op.ed9]
 // CHECK:STDOUT:   %bound_method.loc8_15.1: <bound method> = bound_method %addr, %impl.elem0 [concrete = constants.%ptr.as.Copy.impl.Op.bound]
 // CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%C) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn]
@@ -640,7 +640,7 @@ var ptr: E* = &val;
 // CHECK:STDOUT:   %C.val: %C = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.31e: type = ptr_type %C [concrete]
 // CHECK:STDOUT:   %pattern_type.506: type = pattern_type %ptr.31e [concrete]
-// CHECK:STDOUT:   %addr: %ptr.31e = addr_of file.%val.var [concrete]
+// CHECK:STDOUT:   %addr: %ptr.31e = addr_of file.%value.var [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %T.67d: type = symbolic_binding T, 0 [symbolic]
@@ -684,18 +684,18 @@ var ptr: E* = &val;
 // CHECK:STDOUT:     .b_val = imports.%Main.b_val
 // CHECK:STDOUT:     .b_ptr = imports.%Main.b_ptr
 // CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .val = %val
+// CHECK:STDOUT:     .value = %value
 // CHECK:STDOUT:     .ptr = %ptr.loc8_5
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %default.import = import <none>
 // CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %val.patt: %pattern_type.7c7 = ref_binding_pattern val [concrete]
-// CHECK:STDOUT:     %val.var_patt: %pattern_type.7c7 = var_pattern %val.patt [concrete]
+// CHECK:STDOUT:     %value.patt: %pattern_type.7c7 = ref_binding_pattern value [concrete]
+// CHECK:STDOUT:     %value.var_patt: %pattern_type.7c7 = var_pattern %value.patt [concrete]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %val.var: ref %C = var %val.var_patt [concrete]
+// CHECK:STDOUT:   %value.var: ref %C = var %value.var_patt [concrete]
 // CHECK:STDOUT:   %D.ref: type = name_ref D, imports.%Main.D [concrete = constants.%C]
-// CHECK:STDOUT:   %val: ref %C = ref_binding val, %val.var [concrete = %val.var]
+// CHECK:STDOUT:   %value: ref %C = ref_binding value, %value.var [concrete = %value.var]
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %ptr.patt: %pattern_type.506 = ref_binding_pattern ptr [concrete]
 // CHECK:STDOUT:     %ptr.var_patt: %pattern_type.506 = var_pattern %ptr.patt [concrete]
@@ -717,12 +717,12 @@ var ptr: E* = &val;
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc7_15.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %.loc7_15.2: init %C to file.%val.var = class_init () [concrete = constants.%C.val]
-// CHECK:STDOUT:   %.loc7_1: init %C = converted %.loc7_15.1, %.loc7_15.2 [concrete = constants.%C.val]
-// CHECK:STDOUT:   assign file.%val.var, %.loc7_1
-// CHECK:STDOUT:   %val.ref: ref %C = name_ref val, file.%val [concrete = file.%val.var]
-// CHECK:STDOUT:   %addr: %ptr.31e = addr_of %val.ref [concrete = constants.%addr]
+// CHECK:STDOUT:   %.loc7_17.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.loc7_17.2: init %C to file.%value.var = class_init () [concrete = constants.%C.val]
+// CHECK:STDOUT:   %.loc7_1: init %C = converted %.loc7_17.1, %.loc7_17.2 [concrete = constants.%C.val]
+// CHECK:STDOUT:   assign file.%value.var, %.loc7_1
+// CHECK:STDOUT:   %value.ref: ref %C = name_ref value, file.%value [concrete = file.%value.var]
+// CHECK:STDOUT:   %addr: %ptr.31e = addr_of %value.ref [concrete = constants.%addr]
 // CHECK:STDOUT:   %impl.elem0: %.cda = impl_witness_access constants.%Copy.impl_witness.2c7, element0 [concrete = constants.%ptr.as.Copy.impl.Op.ed9]
 // CHECK:STDOUT:   %bound_method.loc8_15.1: <bound method> = bound_method %addr, %impl.elem0 [concrete = constants.%ptr.as.Copy.impl.Op.bound]
 // CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @ptr.as.Copy.impl.Op(constants.%C) [concrete = constants.%ptr.as.Copy.impl.Op.specific_fn]

+ 1 - 1
toolchain/check/testdata/impl/error_recovery.carbon

@@ -118,7 +118,7 @@ class C {};
 //@dump-sem-ir-begin
 impl C as I {
   // This leaves the impl with a placeholder instruction in the witness table.
-  // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+8]]:11: error: expected `:` or `:!` in binding pattern [ExpectedBindingPattern]
+  // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+8]]:11: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern]
   // CHECK:STDERR:   fn Op[x self: C]() {}
   // CHECK:STDERR:           ^~~~
   // CHECK:STDERR:

+ 1 - 1
toolchain/check/testdata/interop/cpp/function/arithmetic_types_direct.carbon

@@ -288,7 +288,7 @@ library "[[@TEST_NAME]]";
 
 import Cpp library "int_return.h";
 
-fn Carbon_foo(val: i32);
+fn Carbon_foo(value: i32);
 
 fn F() {
   //@dump-sem-ir-begin

+ 72 - 72
toolchain/check/testdata/primitives/import_symbolic.carbon

@@ -15,11 +15,11 @@
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! bool;
+  let value:! bool;
 }
 
 class C {
-  impl as I where .val = false {}
+  impl as I where .value = false {}
 }
 
 // --- import_bool.carbon
@@ -29,7 +29,7 @@ import library "bool";
 
 fn F() -> bool {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -38,12 +38,12 @@ fn F() -> bool {
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! Core.Char;
+  let value:! Core.Char;
 }
 
 class C {}
 
-impl C as I where .val = 'a' {}
+impl C as I where .value = 'a' {}
 
 // --- import_char.carbon
 
@@ -52,7 +52,7 @@ import library "char";
 
 fn F() -> Core.Char {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -61,12 +61,12 @@ fn F() -> Core.Char {
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! Core.CharLiteral();
+  let value:! Core.CharLiteral();
 }
 
 class C {}
 
-impl C as I where .val = 'a' {}
+impl C as I where .value = 'a' {}
 
 // --- import_char_literal.carbon
 
@@ -75,7 +75,7 @@ import library "char_literal";
 
 fn F() -> Core.CharLiteral() {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -84,12 +84,12 @@ fn F() -> Core.CharLiteral() {
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! f64;
+  let value:! f64;
 }
 
 class C {}
 
-impl C as I where .val = 0.1 {}
+impl C as I where .value = 0.1 {}
 
 // --- import_float.carbon
 
@@ -98,7 +98,7 @@ import library "float";
 
 fn F() -> f64 {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -107,12 +107,12 @@ fn F() -> f64 {
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! Core.FloatLiteral();
+  let value:! Core.FloatLiteral();
 }
 
 class C {}
 
-impl C as I where .val = 0.1 {}
+impl C as I where .value = 0.1 {}
 
 // --- import_float_literal.carbon
 
@@ -121,7 +121,7 @@ import library "float_literal";
 
 fn F() -> Core.FloatLiteral() {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -130,12 +130,12 @@ fn F() -> Core.FloatLiteral() {
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! i32;
+  let value:! i32;
 }
 
 class C {}
 
-impl C as I where .val = 8 {}
+impl C as I where .value = 8 {}
 
 // --- import_int.carbon
 
@@ -144,7 +144,7 @@ import library "int";
 
 fn F() -> i32 {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -153,12 +153,12 @@ fn F() -> i32 {
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! Core.IntLiteral();
+  let value:! Core.IntLiteral();
 }
 
 class C {}
 
-impl C as I where .val = 7 {}
+impl C as I where .value = 7 {}
 
 // --- import_int_literal.carbon
 
@@ -167,7 +167,7 @@ import library "int_literal";
 
 fn F() -> Core.IntLiteral() {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -176,12 +176,12 @@ fn F() -> Core.IntLiteral() {
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! u32;
+  let value:! u32;
 }
 
 class C {}
 
-impl C as I where .val = 8 {}
+impl C as I where .value = 8 {}
 
 // --- unsigned_uint.carbon
 
@@ -190,7 +190,7 @@ import library "uint";
 
 fn F() -> u32 {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -203,7 +203,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.aa0: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.dd8 [concrete]
+// CHECK:STDOUT:   %assoc0.af3: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.4da [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %Copy.impl_witness.348: <witness> = impl_witness imports.%Copy.impl_witness_table.3cc [concrete]
@@ -217,11 +217,11 @@ fn F() -> u32 {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//bool, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//bool, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.be4: %I.assoc_type = import_ref Main//bool, loc5_10, loaded [concrete = constants.%assoc0.aa0]
-// CHECK:STDOUT:   %Main.import_ref.d18: bool = import_ref Main//bool, loc9_32, loaded [concrete = constants.%false]
+// CHECK:STDOUT:   %Main.import_ref.1c8: %I.assoc_type = import_ref Main//bool, loc5_12, loaded [concrete = constants.%assoc0.af3]
+// CHECK:STDOUT:   %Main.import_ref.d18: bool = import_ref Main//bool, loc9_34, loaded [concrete = constants.%false]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.d18), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.dd8: bool = import_ref Main//bool, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: bool = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.4da: bool = import_ref Main//bool, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: bool = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.595: %bool.as.Copy.impl.Op.type = import_ref Core//prelude/copy, loc{{\d+_\d+}}, loaded [concrete = constants.%bool.as.Copy.impl.Op]
 // CHECK:STDOUT:   %Copy.impl_witness_table.3cc = impl_witness_table (%Core.import_ref.595), @bool.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -234,7 +234,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.be4 [concrete = constants.%assoc0.aa0]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.1c8 [concrete = constants.%assoc0.af3]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: bool = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%false]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.56b = impl_witness_access constants.%Copy.impl_witness.348, element0 [concrete = constants.%bool.as.Copy.impl.Op]
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %impl.elem0.loc7_18.1, %impl.elem0.loc7_18.2 [concrete = constants.%bool.as.Copy.impl.Op.bound]
@@ -252,7 +252,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.2ac: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.55e [concrete]
+// CHECK:STDOUT:   %assoc0.840: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.90f [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %Copy.impl_witness.bd9: <witness> = impl_witness imports.%Copy.impl_witness_table.5ef [concrete]
@@ -266,11 +266,11 @@ fn F() -> u32 {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//char, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//char, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.7e5: %I.assoc_type = import_ref Main//char, loc5_10, loaded [concrete = constants.%assoc0.2ac]
-// CHECK:STDOUT:   %Main.import_ref.d0c: %char = import_ref Main//char, loc10_30, loaded [concrete = constants.%int_97]
+// CHECK:STDOUT:   %Main.import_ref.d69: %I.assoc_type = import_ref Main//char, loc5_12, loaded [concrete = constants.%assoc0.840]
+// CHECK:STDOUT:   %Main.import_ref.d0c: %char = import_ref Main//char, loc10_32, loaded [concrete = constants.%int_97]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.d0c), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.55e: %char = import_ref Main//char, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: %char = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.90f: %char = import_ref Main//char, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: %char = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.35d: %char.as.Copy.impl.Op.type = import_ref Core//prelude/types/char, loc{{\d+_\d+}}, loaded [concrete = constants.%char.as.Copy.impl.Op]
 // CHECK:STDOUT:   %Copy.impl_witness_table.5ef = impl_witness_table (%Core.import_ref.35d), @char.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -283,7 +283,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.7e5 [concrete = constants.%assoc0.2ac]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.d69 [concrete = constants.%assoc0.840]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: %char = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%int_97]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.11f = impl_witness_access constants.%Copy.impl_witness.bd9, element0 [concrete = constants.%char.as.Copy.impl.Op]
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %impl.elem0.loc7_18.1, %impl.elem0.loc7_18.2 [concrete = constants.%char.as.Copy.impl.Op.bound]
@@ -300,7 +300,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.35b: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.2ad [concrete]
+// CHECK:STDOUT:   %assoc0.c6f: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.8d8 [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %Copy.impl_witness.100: <witness> = impl_witness imports.%Copy.impl_witness_table.853 [concrete]
@@ -314,11 +314,11 @@ fn F() -> u32 {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//char_literal, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//char_literal, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.297: %I.assoc_type = import_ref Main//char_literal, loc5_10, loaded [concrete = constants.%assoc0.35b]
-// CHECK:STDOUT:   %Main.import_ref.43e: Core.CharLiteral = import_ref Main//char_literal, loc10_30, loaded [concrete = constants.%.54f]
+// CHECK:STDOUT:   %Main.import_ref.6f9: %I.assoc_type = import_ref Main//char_literal, loc5_12, loaded [concrete = constants.%assoc0.c6f]
+// CHECK:STDOUT:   %Main.import_ref.43e: Core.CharLiteral = import_ref Main//char_literal, loc10_32, loaded [concrete = constants.%.54f]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.43e), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.2ad: Core.CharLiteral = import_ref Main//char_literal, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: Core.CharLiteral = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.8d8: Core.CharLiteral = import_ref Main//char_literal, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: Core.CharLiteral = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.d09: %Core.CharLiteral.as.Copy.impl.Op.type = import_ref Core//prelude/copy, loc{{\d+_\d+}}, loaded [concrete = constants.%Core.CharLiteral.as.Copy.impl.Op]
 // CHECK:STDOUT:   %Copy.impl_witness_table.853 = impl_witness_table (%Core.import_ref.d09), @Core.CharLiteral.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -331,7 +331,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.297 [concrete = constants.%assoc0.35b]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.6f9 [concrete = constants.%assoc0.c6f]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: Core.CharLiteral = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%.54f]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.0d7 = impl_witness_access constants.%Copy.impl_witness.100, element0 [concrete = constants.%Core.CharLiteral.as.Copy.impl.Op]
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %impl.elem0.loc7_18.1, %impl.elem0.loc7_18.2 [concrete = constants.%Core.CharLiteral.as.Copy.impl.Op.bound]
@@ -351,7 +351,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.2c4: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.26f [concrete]
+// CHECK:STDOUT:   %assoc0.f67: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.893 [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %Float.as.Copy.impl.Op.type.2fe: type = fn_type @Float.as.Copy.impl.Op, @Float.as.Copy.impl(%N) [symbolic]
@@ -369,11 +369,11 @@ fn F() -> u32 {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//float, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//float, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.0eb: %I.assoc_type = import_ref Main//float, loc5_10, loaded [concrete = constants.%assoc0.2c4]
-// CHECK:STDOUT:   %Main.import_ref.0d9: %f64.d77 = import_ref Main//float, loc10_30, loaded [concrete = constants.%float]
+// CHECK:STDOUT:   %Main.import_ref.e98: %I.assoc_type = import_ref Main//float, loc5_12, loaded [concrete = constants.%assoc0.f67]
+// CHECK:STDOUT:   %Main.import_ref.0d9: %f64.d77 = import_ref Main//float, loc10_32, loaded [concrete = constants.%float]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.0d9), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.26f: %f64.d77 = import_ref Main//float, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: %f64.d77 = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.893: %f64.d77 = import_ref Main//float, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: %f64.d77 = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.47c: @Float.as.Copy.impl.%Float.as.Copy.impl.Op.type (%Float.as.Copy.impl.Op.type.2fe) = import_ref Core//prelude/types/float, loc{{\d+_\d+}}, loaded [symbolic = @Float.as.Copy.impl.%Float.as.Copy.impl.Op (constants.%Float.as.Copy.impl.Op.240)]
 // CHECK:STDOUT:   %Copy.impl_witness_table.119 = impl_witness_table (%Core.import_ref.47c), @Float.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -386,7 +386,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.0eb [concrete = constants.%assoc0.2c4]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.e98 [concrete = constants.%assoc0.f67]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: %f64.d77 = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%float]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.c41 = impl_witness_access constants.%Copy.impl_witness.1e3, element0 [concrete = constants.%Float.as.Copy.impl.Op.f05]
 // CHECK:STDOUT:   %bound_method.loc7_18.1: <bound method> = bound_method %impl.elem0.loc7_18.1, %impl.elem0.loc7_18.2 [concrete = constants.%Float.as.Copy.impl.Op.bound]
@@ -404,7 +404,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.7ab: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.a44 [concrete]
+// CHECK:STDOUT:   %assoc0.8a0: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.2e3 [concrete]
 // CHECK:STDOUT:   %float.3fedf4.2: Core.FloatLiteral = float_literal_value 1e-1 [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
@@ -419,11 +419,11 @@ fn F() -> u32 {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//float_literal, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//float_literal, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.cf4: %I.assoc_type = import_ref Main//float_literal, loc5_10, loaded [concrete = constants.%assoc0.7ab]
-// CHECK:STDOUT:   %Main.import_ref.dec: Core.FloatLiteral = import_ref Main//float_literal, loc10_30, loaded [concrete = constants.%float.3fedf4.2]
+// CHECK:STDOUT:   %Main.import_ref.10a: %I.assoc_type = import_ref Main//float_literal, loc5_12, loaded [concrete = constants.%assoc0.8a0]
+// CHECK:STDOUT:   %Main.import_ref.dec: Core.FloatLiteral = import_ref Main//float_literal, loc10_32, loaded [concrete = constants.%float.3fedf4.2]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.dec), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.a44: Core.FloatLiteral = import_ref Main//float_literal, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: Core.FloatLiteral = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.2e3: Core.FloatLiteral = import_ref Main//float_literal, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: Core.FloatLiteral = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.3f0: %Core.FloatLiteral.as.Copy.impl.Op.type = import_ref Core//prelude/copy, loc{{\d+_\d+}}, loaded [concrete = constants.%Core.FloatLiteral.as.Copy.impl.Op]
 // CHECK:STDOUT:   %Copy.impl_witness_table.65a = impl_witness_table (%Core.import_ref.3f0), @Core.FloatLiteral.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -436,7 +436,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.cf4 [concrete = constants.%assoc0.7ab]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.10a [concrete = constants.%assoc0.8a0]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: Core.FloatLiteral = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%float.3fedf4.2]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.f37 = impl_witness_access constants.%Copy.impl_witness.ba9, element0 [concrete = constants.%Core.FloatLiteral.as.Copy.impl.Op]
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %impl.elem0.loc7_18.1, %impl.elem0.loc7_18.2 [concrete = constants.%Core.FloatLiteral.as.Copy.impl.Op.bound]
@@ -456,7 +456,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.096: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.52f [concrete]
+// CHECK:STDOUT:   %assoc0.2ce: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.e84 [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %Int.as.Copy.impl.Op.type.824: type = fn_type @Int.as.Copy.impl.Op, @Int.as.Copy.impl(%N) [symbolic]
@@ -474,11 +474,11 @@ fn F() -> u32 {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//int, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//int, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.641: %I.assoc_type = import_ref Main//int, loc5_10, loaded [concrete = constants.%assoc0.096]
-// CHECK:STDOUT:   %Main.import_ref.330: %i32 = import_ref Main//int, loc10_28, loaded [concrete = constants.%int_8]
+// CHECK:STDOUT:   %Main.import_ref.76f: %I.assoc_type = import_ref Main//int, loc5_12, loaded [concrete = constants.%assoc0.2ce]
+// CHECK:STDOUT:   %Main.import_ref.330: %i32 = import_ref Main//int, loc10_30, loaded [concrete = constants.%int_8]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.330), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.52f: %i32 = import_ref Main//int, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: %i32 = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.e84: %i32 = import_ref Main//int, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: %i32 = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.18d: @Int.as.Copy.impl.%Int.as.Copy.impl.Op.type (%Int.as.Copy.impl.Op.type.824) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.Copy.impl.%Int.as.Copy.impl.Op (constants.%Int.as.Copy.impl.Op.9b9)]
 // CHECK:STDOUT:   %Copy.impl_witness_table.e76 = impl_witness_table (%Core.import_ref.18d), @Int.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -491,7 +491,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.641 [concrete = constants.%assoc0.096]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.76f [concrete = constants.%assoc0.2ce]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: %i32 = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%int_8]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.f79 = impl_witness_access constants.%Copy.impl_witness.f17, element0 [concrete = constants.%Int.as.Copy.impl.Op.664]
 // CHECK:STDOUT:   %bound_method.loc7_18.1: <bound method> = bound_method %impl.elem0.loc7_18.1, %impl.elem0.loc7_18.2 [concrete = constants.%Int.as.Copy.impl.Op.bound]
@@ -510,7 +510,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.b62: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.763 [concrete]
+// CHECK:STDOUT:   %assoc0.415: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.334 [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %Copy.impl_witness.98e: <witness> = impl_witness imports.%Copy.impl_witness_table.d8f [concrete]
@@ -524,11 +524,11 @@ fn F() -> u32 {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//int_literal, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//int_literal, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.f39: %I.assoc_type = import_ref Main//int_literal, loc5_10, loaded [concrete = constants.%assoc0.b62]
-// CHECK:STDOUT:   %Main.import_ref.bc3: Core.IntLiteral = import_ref Main//int_literal, loc10_28, loaded [concrete = constants.%int_7]
+// CHECK:STDOUT:   %Main.import_ref.398: %I.assoc_type = import_ref Main//int_literal, loc5_12, loaded [concrete = constants.%assoc0.415]
+// CHECK:STDOUT:   %Main.import_ref.bc3: Core.IntLiteral = import_ref Main//int_literal, loc10_30, loaded [concrete = constants.%int_7]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.bc3), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.763: Core.IntLiteral = import_ref Main//int_literal, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: Core.IntLiteral = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.334: Core.IntLiteral = import_ref Main//int_literal, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: Core.IntLiteral = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.66a: %Core.IntLiteral.as.Copy.impl.Op.type = import_ref Core//prelude/copy, loc{{\d+_\d+}}, loaded [concrete = constants.%Core.IntLiteral.as.Copy.impl.Op]
 // CHECK:STDOUT:   %Copy.impl_witness_table.d8f = impl_witness_table (%Core.import_ref.66a), @Core.IntLiteral.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -541,7 +541,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.f39 [concrete = constants.%assoc0.b62]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.398 [concrete = constants.%assoc0.415]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: Core.IntLiteral = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%int_7]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.6b5 = impl_witness_access constants.%Copy.impl_witness.98e, element0 [concrete = constants.%Core.IntLiteral.as.Copy.impl.Op]
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %impl.elem0.loc7_18.1, %impl.elem0.loc7_18.2 [concrete = constants.%Core.IntLiteral.as.Copy.impl.Op.bound]
@@ -561,7 +561,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.ea3: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.f37 [concrete]
+// CHECK:STDOUT:   %assoc0.aa0: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.ee9 [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %UInt.as.Copy.impl.Op.type.68f: type = fn_type @UInt.as.Copy.impl.Op, @UInt.as.Copy.impl(%N) [symbolic]
@@ -579,11 +579,11 @@ fn F() -> u32 {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//uint, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//uint, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.fa7: %I.assoc_type = import_ref Main//uint, loc5_10, loaded [concrete = constants.%assoc0.ea3]
-// CHECK:STDOUT:   %Main.import_ref.647: %u32 = import_ref Main//uint, loc10_28, loaded [concrete = constants.%int_8]
+// CHECK:STDOUT:   %Main.import_ref.4d2: %I.assoc_type = import_ref Main//uint, loc5_12, loaded [concrete = constants.%assoc0.aa0]
+// CHECK:STDOUT:   %Main.import_ref.647: %u32 = import_ref Main//uint, loc10_30, loaded [concrete = constants.%int_8]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.647), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.f37: %u32 = import_ref Main//uint, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: %u32 = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.ee9: %u32 = import_ref Main//uint, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: %u32 = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.c3c: @UInt.as.Copy.impl.%UInt.as.Copy.impl.Op.type (%UInt.as.Copy.impl.Op.type.68f) = import_ref Core//prelude/types/uint, loc{{\d+_\d+}}, loaded [symbolic = @UInt.as.Copy.impl.%UInt.as.Copy.impl.Op (constants.%UInt.as.Copy.impl.Op.576)]
 // CHECK:STDOUT:   %Copy.impl_witness_table.bd0 = impl_witness_table (%Core.import_ref.c3c), @UInt.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -596,7 +596,7 @@ fn F() -> u32 {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.fa7 [concrete = constants.%assoc0.ea3]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.4d2 [concrete = constants.%assoc0.aa0]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: %u32 = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%int_8]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.fcc = impl_witness_access constants.%Copy.impl_witness.514, element0 [concrete = constants.%UInt.as.Copy.impl.Op.c10]
 // CHECK:STDOUT:   %bound_method.loc7_18.1: <bound method> = bound_method %impl.elem0.loc7_18.1, %impl.elem0.loc7_18.2 [concrete = constants.%UInt.as.Copy.impl.Op.bound]

+ 11 - 11
toolchain/check/testdata/struct/import.carbon

@@ -61,12 +61,12 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! {.x: i32};
+  let value:! {.x: i32};
 }
 
 class C {}
 
-impl C as I where .val = {.x = 1} {}
+impl C as I where .value = {.x = 1} {}
 
 // --- import_symbolic_decl.carbon
 
@@ -75,7 +75,7 @@ import library "symbolic_decl";
 
 fn F() -> {.x: i32} {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -294,7 +294,7 @@ fn F() -> {.x: i32} {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.696: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.b51 [concrete]
+// CHECK:STDOUT:   %assoc0.7f5: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.8d8 [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %Int.as.Copy.impl.Op.type.824: type = fn_type @Int.as.Copy.impl.Op, @Int.as.Copy.impl(%N) [symbolic]
@@ -312,11 +312,11 @@ fn F() -> {.x: i32} {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//symbolic_decl, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//symbolic_decl, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.0f2: %I.assoc_type = import_ref Main//symbolic_decl, loc5_10, loaded [concrete = constants.%assoc0.696]
-// CHECK:STDOUT:   %Main.import_ref.1ec: %struct_type.x = import_ref Main//symbolic_decl, loc10_35, loaded [concrete = constants.%struct]
+// CHECK:STDOUT:   %Main.import_ref.dfb: %I.assoc_type = import_ref Main//symbolic_decl, loc5_12, loaded [concrete = constants.%assoc0.7f5]
+// CHECK:STDOUT:   %Main.import_ref.1ec: %struct_type.x = import_ref Main//symbolic_decl, loc10_37, loaded [concrete = constants.%struct]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.1ec), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.b51: %struct_type.x = import_ref Main//symbolic_decl, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: %struct_type.x = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.8d8: %struct_type.x = import_ref Main//symbolic_decl, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: %struct_type.x = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.18d: @Int.as.Copy.impl.%Int.as.Copy.impl.Op.type (%Int.as.Copy.impl.Op.type.824) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.Copy.impl.%Int.as.Copy.impl.Op (constants.%Int.as.Copy.impl.Op.9b9)]
 // CHECK:STDOUT:   %Copy.impl_witness_table.e76 = impl_witness_table (%Core.import_ref.18d), @Int.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -329,7 +329,7 @@ fn F() -> {.x: i32} {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18.1: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.0f2 [concrete = constants.%assoc0.696]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.dfb [concrete = constants.%assoc0.7f5]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: %struct_type.x = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%struct]
 // CHECK:STDOUT:   %.loc7_18.2: %i32 = struct_access %impl.elem0.loc7_18.1, element0 [concrete = constants.%int_1]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.f79 = impl_witness_access constants.%Copy.impl_witness.f17, element0 [concrete = constants.%Int.as.Copy.impl.Op.664]
@@ -338,7 +338,7 @@ fn F() -> {.x: i32} {
 // CHECK:STDOUT:   %bound_method.loc7_18.2: <bound method> = bound_method %.loc7_18.2, %specific_fn [concrete = constants.%bound_method]
 // CHECK:STDOUT:   %Int.as.Copy.impl.Op.call: init %i32 = call %bound_method.loc7_18.2(%.loc7_18.2) [concrete = constants.%int_1]
 // CHECK:STDOUT:   %.loc7_18.3: init %struct_type.x = struct_init (%Int.as.Copy.impl.Op.call) [concrete = constants.%struct]
-// CHECK:STDOUT:   %.loc7_22: init %struct_type.x = converted %impl.elem0.loc7_18.1, %.loc7_18.3 [concrete = constants.%struct]
-// CHECK:STDOUT:   return %.loc7_22
+// CHECK:STDOUT:   %.loc7_24: init %struct_type.x = converted %impl.elem0.loc7_18.1, %.loc7_18.3 [concrete = constants.%struct]
+// CHECK:STDOUT:   return %.loc7_24
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 11 - 11
toolchain/check/testdata/tuple/import.carbon

@@ -63,12 +63,12 @@ var c_bad: C((3, 4)) = F();
 library "[[@TEST_NAME]]";
 
 interface I {
-  let val:! (i32,);
+  let value:! (i32,);
 }
 
 class C {}
 
-impl C as I where .val = (1,) {}
+impl C as I where .value = (1,) {}
 
 // --- import_symbolic_decl.carbon
 
@@ -77,7 +77,7 @@ import library "symbolic_decl";
 
 fn F() -> (i32,) {
   //@dump-sem-ir-begin
-  return (C as I).val;
+  return (C as I).value;
   //@dump-sem-ir-end
 }
 
@@ -325,7 +325,7 @@ fn F() -> (i32,) {
 // CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness imports.%I.impl_witness_table [concrete]
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %C, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I [concrete]
-// CHECK:STDOUT:   %assoc0.8b4: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.ce8 [concrete]
+// CHECK:STDOUT:   %assoc0.5ae: %I.assoc_type = assoc_entity element0, imports.%Main.import_ref.ca6 [concrete]
 // CHECK:STDOUT:   %Copy.type: type = facet_type <@Copy> [concrete]
 // CHECK:STDOUT:   %Copy.Op.type: type = fn_type @Copy.Op [concrete]
 // CHECK:STDOUT:   %Int.as.Copy.impl.Op.type.824: type = fn_type @Int.as.Copy.impl.Op, @Int.as.Copy.impl(%N) [symbolic]
@@ -343,11 +343,11 @@ fn F() -> (i32,) {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Main.I: type = import_ref Main//symbolic_decl, I, loaded [concrete = constants.%I.type]
 // CHECK:STDOUT:   %Main.C: type = import_ref Main//symbolic_decl, C, loaded [concrete = constants.%C]
-// CHECK:STDOUT:   %Main.import_ref.bf0: %I.assoc_type = import_ref Main//symbolic_decl, loc5_10, loaded [concrete = constants.%assoc0.8b4]
-// CHECK:STDOUT:   %Main.import_ref.fd7: %tuple.type.a1c = import_ref Main//symbolic_decl, loc10_31, loaded [concrete = constants.%tuple.246]
+// CHECK:STDOUT:   %Main.import_ref.a8c: %I.assoc_type = import_ref Main//symbolic_decl, loc5_12, loaded [concrete = constants.%assoc0.5ae]
+// CHECK:STDOUT:   %Main.import_ref.fd7: %tuple.type.a1c = import_ref Main//symbolic_decl, loc10_33, loaded [concrete = constants.%tuple.246]
 // CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (%Main.import_ref.fd7), @C.as.I.impl [concrete]
-// CHECK:STDOUT:   %Main.import_ref.ce8: %tuple.type.a1c = import_ref Main//symbolic_decl, loc5_10, loaded [concrete = %val]
-// CHECK:STDOUT:   %val: %tuple.type.a1c = assoc_const_decl @val [concrete] {}
+// CHECK:STDOUT:   %Main.import_ref.ca6: %tuple.type.a1c = import_ref Main//symbolic_decl, loc5_12, loaded [concrete = %value]
+// CHECK:STDOUT:   %value: %tuple.type.a1c = assoc_const_decl @value [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.18d: @Int.as.Copy.impl.%Int.as.Copy.impl.Op.type (%Int.as.Copy.impl.Op.type.824) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.Copy.impl.%Int.as.Copy.impl.Op (constants.%Int.as.Copy.impl.Op.9b9)]
 // CHECK:STDOUT:   %Copy.impl_witness_table.e76 = impl_witness_table (%Core.import_ref.18d), @Int.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
@@ -360,7 +360,7 @@ fn F() -> (i32,) {
 // CHECK:STDOUT:   %.loc7_13: %I.type = converted %C.ref, %I.facet [concrete = constants.%I.facet]
 // CHECK:STDOUT:   %as_type: type = facet_access_type %.loc7_13 [concrete = constants.%C]
 // CHECK:STDOUT:   %.loc7_18.1: type = converted %.loc7_13, %as_type [concrete = constants.%C]
-// CHECK:STDOUT:   %val.ref: %I.assoc_type = name_ref val, imports.%Main.import_ref.bf0 [concrete = constants.%assoc0.8b4]
+// CHECK:STDOUT:   %value.ref: %I.assoc_type = name_ref value, imports.%Main.import_ref.a8c [concrete = constants.%assoc0.5ae]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.1: %tuple.type.a1c = impl_witness_access constants.%I.impl_witness, element0 [concrete = constants.%tuple.246]
 // CHECK:STDOUT:   %tuple.elem0: %i32 = tuple_access %impl.elem0.loc7_18.1, element0 [concrete = constants.%int_1]
 // CHECK:STDOUT:   %impl.elem0.loc7_18.2: %.f79 = impl_witness_access constants.%Copy.impl_witness.f17, element0 [concrete = constants.%Int.as.Copy.impl.Op.664]
@@ -369,7 +369,7 @@ fn F() -> (i32,) {
 // CHECK:STDOUT:   %bound_method.loc7_18.2: <bound method> = bound_method %tuple.elem0, %specific_fn [concrete = constants.%bound_method]
 // CHECK:STDOUT:   %Int.as.Copy.impl.Op.call: init %i32 = call %bound_method.loc7_18.2(%tuple.elem0) [concrete = constants.%int_1]
 // CHECK:STDOUT:   %.loc7_18.2: init %tuple.type.a1c = tuple_init (%Int.as.Copy.impl.Op.call) [concrete = constants.%tuple.246]
-// CHECK:STDOUT:   %.loc7_22: init %tuple.type.a1c = converted %impl.elem0.loc7_18.1, %.loc7_18.2 [concrete = constants.%tuple.246]
-// CHECK:STDOUT:   return %.loc7_22
+// CHECK:STDOUT:   %.loc7_24: init %tuple.type.a1c = converted %impl.elem0.loc7_18.1, %.loc7_18.2 [concrete = constants.%tuple.246]
+// CHECK:STDOUT:   return %.loc7_24
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/diagnostics/kind.def

@@ -95,6 +95,7 @@ CARBON_DIAGNOSTIC_KIND(CharLiteralUnderflow)
 
 CARBON_DIAGNOSTIC_KIND(BinaryOperatorRequiresWhitespace)
 CARBON_DIAGNOSTIC_KIND(ExpectedArrayComma)
+CARBON_DIAGNOSTIC_KIND(ExpectedCategoryModifier)
 CARBON_DIAGNOSTIC_KIND(ExpectedCloseSymbol)
 CARBON_DIAGNOSTIC_KIND(ExpectedCodeBlock)
 CARBON_DIAGNOSTIC_KIND(ExpectedLambdaBody)

+ 4 - 0
toolchain/lex/token_kind.def

@@ -59,12 +59,14 @@ CARBON_TOKEN(Error)
 CARBON_SYMBOL_TOKEN(GreaterGreaterEqual, ">>=")
 CARBON_SYMBOL_TOKEN(LessEqualGreater,    "<=>")
 CARBON_SYMBOL_TOKEN(LessLessEqual,       "<<=")
+CARBON_SYMBOL_TOKEN(MinusGreaterQuestion,"->?")
 
 CARBON_SYMBOL_TOKEN(AmpEqual,            "&=")
 CARBON_SYMBOL_TOKEN(CaretEqual,          "^=")
 CARBON_SYMBOL_TOKEN(ColonEqual,          ":=")
 CARBON_TOKEN_WITH_VIRTUAL_NODE(
   CARBON_SYMBOL_TOKEN(ColonExclaim,        ":!"))
+CARBON_SYMBOL_TOKEN(ColonQuestion,       ":?")
 CARBON_SYMBOL_TOKEN(EqualEqual,          "==")
 CARBON_SYMBOL_TOKEN(EqualGreater,        "=>")
 CARBON_SYMBOL_TOKEN(ExclaimEqual,        "!=")
@@ -189,6 +191,7 @@ CARBON_KEYWORD_TOKEN(Extern,              "extern")
 CARBON_KEYWORD_TOKEN(False,               "false")
 CARBON_KEYWORD_TOKEN(Final,               "final")
 CARBON_KEYWORD_TOKEN(For,                 "for")
+CARBON_KEYWORD_TOKEN(Form,                "form")
 CARBON_KEYWORD_TOKEN(Forall,              "forall")
 CARBON_KEYWORD_TOKEN(Friend,              "friend")
 CARBON_KEYWORD_TOKEN(If,                  "if")
@@ -221,6 +224,7 @@ CARBON_KEYWORD_TOKEN(Underscore,          "_")
 CARBON_KEYWORD_TOKEN(Unsafe,              "unsafe")
 CARBON_KEYWORD_TOKEN(Unused,              "unused")
 CARBON_KEYWORD_TOKEN(Virtual,             "virtual")
+CARBON_KEYWORD_TOKEN(Val,                 "val")
 CARBON_TOKEN_WITH_VIRTUAL_NODE(
   CARBON_KEYWORD_TOKEN(Where,             "where"))
 CARBON_KEYWORD_TOKEN(While,               "while")

+ 4 - 0
toolchain/parse/context.h

@@ -145,6 +145,10 @@ class Context {
                                kind != NodeKind::InvalidParseSubtree),
                  "{0} nodes must always have an error", kind);
     tree_->node_impls_.push_back(Tree::NodeImpl(kind, has_error, token));
+    CARBON_VLOG("Add #node{0}: {1}",
+                llvm::format_hex_no_prefix(tree_->node_impls_.size() - 1, 0,
+                                           /*Upper=*/true),
+                tree_->node_impls_.back());
   }
 
   // Adds a node and returns its typed NodeId.

+ 41 - 16
toolchain/parse/handle_binding_pattern.cpp

@@ -14,9 +14,10 @@ auto HandleBindingPattern(Context& context) -> void {
   // Handle an invalid pattern introducer for parameters and variables.
   auto on_error = [&](bool expected_name) {
     if (!state.has_error) {
-      CARBON_DIAGNOSTIC(ExpectedBindingPattern, Error,
-                        "expected {0:name|`:` or `:!`} in binding pattern",
-                        Diagnostics::BoolAsSelect);
+      CARBON_DIAGNOSTIC(
+          ExpectedBindingPattern, Error,
+          "expected {0:name|`:`, `:!`, or `:?`} in binding pattern",
+          Diagnostics::BoolAsSelect);
       context.emitter().Emit(*context.position(), ExpectedBindingPattern,
                              expected_name);
       state.has_error = true;
@@ -51,7 +52,8 @@ auto HandleBindingPattern(Context& context) -> void {
 
   if (auto token_kind = context.PositionKind();
       token_kind == Lex::TokenKind::Colon ||
-      token_kind == Lex::TokenKind::ColonExclaim) {
+      token_kind == Lex::TokenKind::ColonExclaim ||
+      token_kind == Lex::TokenKind::ColonQuestion) {
     // Add the wrapper node for the `template` keyword if present.
     if (template_token) {
       if (token_kind != Lex::TokenKind::ColonExclaim && !state.has_error) {
@@ -75,10 +77,21 @@ auto HandleBindingPattern(Context& context) -> void {
       context.AddNode(NodeKind::RefBindingName, *ref_token, state.has_error);
     }
 
-    state.kind = token_kind == Lex::TokenKind::Colon
-                     ? StateKind::BindingPatternFinishAsRegular
-                     : StateKind::BindingPatternFinishAsGeneric;
-    // Use the `:` or `:!` for the root node.
+    switch (token_kind) {
+      case Lex::TokenKind::Colon:
+        state.kind = StateKind::BindingPatternFinishAsRegular;
+        break;
+      case Lex::TokenKind::ColonExclaim:
+        state.kind = StateKind::BindingPatternFinishAsGeneric;
+        break;
+      case Lex::TokenKind::ColonQuestion:
+        state.kind = StateKind::BindingPatternFinishAsForm;
+        break;
+      default:
+        CARBON_FATAL("Unexpected token kind");
+    }
+
+    // Use the `:`, `:!`, or `:?` for the root node.
     state.token = context.Consume();
 
     if (token_kind == Lex::TokenKind::ColonExclaim) {
@@ -99,24 +112,32 @@ auto HandleBindingPattern(Context& context) -> void {
 }
 
 // Handles BindingPatternFinishAs(Generic|Regular).
-static auto HandleBindingPatternFinish(Context& context, bool is_compile_time)
+static auto HandleBindingPatternFinish(Context& context, StateKind finish_kind)
     -> void {
   auto state = context.PopState();
 
   auto node_kind = NodeKind::InvalidParse;
   if (state.in_var_pattern) {
     node_kind = NodeKind::VarBindingPattern;
-    if (is_compile_time) {
+    if (finish_kind == StateKind::BindingPatternFinishAsGeneric) {
       CARBON_DIAGNOSTIC(CompileTimeBindingInVarDecl, Error,
                         "`var` pattern cannot declare a compile-time binding");
       context.emitter().Emit(*context.position(), CompileTimeBindingInVarDecl);
       state.has_error = true;
     }
   } else {
-    if (is_compile_time) {
-      node_kind = NodeKind::CompileTimeBindingPattern;
-    } else {
-      node_kind = NodeKind::LetBindingPattern;
+    switch (finish_kind) {
+      case StateKind::BindingPatternFinishAsGeneric:
+        node_kind = NodeKind::CompileTimeBindingPattern;
+        break;
+      case StateKind::BindingPatternFinishAsRegular:
+        node_kind = NodeKind::LetBindingPattern;
+        break;
+      case StateKind::BindingPatternFinishAsForm:
+        node_kind = NodeKind::FormBindingPattern;
+        break;
+      default:
+        CARBON_FATAL("Unexpected StateKind {0}", finish_kind);
     }
   }
   context.AddNode(node_kind, state.token, state.has_error);
@@ -129,11 +150,15 @@ static auto HandleBindingPatternFinish(Context& context, bool is_compile_time)
 }
 
 auto HandleBindingPatternFinishAsGeneric(Context& context) -> void {
-  HandleBindingPatternFinish(context, /*is_compile_time=*/true);
+  HandleBindingPatternFinish(context, StateKind::BindingPatternFinishAsGeneric);
 }
 
 auto HandleBindingPatternFinishAsRegular(Context& context) -> void {
-  HandleBindingPatternFinish(context, /*is_compile_time=*/false);
+  HandleBindingPatternFinish(context, StateKind::BindingPatternFinishAsRegular);
+}
+
+auto HandleBindingPatternFinishAsForm(Context& context) -> void {
+  HandleBindingPatternFinish(context, StateKind::BindingPatternFinishAsForm);
 }
 
 }  // namespace Carbon::Parse

+ 5 - 0
toolchain/parse/handle_expr.cpp

@@ -169,6 +169,11 @@ auto HandleExprInPostfix(Context& context) -> void {
       context.PushState(StateKind::ArrayExpr);
       break;
     }
+    case Lex::TokenKind::Form: {
+      context.PushState(state);
+      context.PushState(StateKind::FormLiteral);
+      break;
+    }
     case Lex::TokenKind::Package: {
       context.AddLeafNode(NodeKind::PackageExpr, context.Consume());
       if (context.PositionKind() != Lex::TokenKind::Period) {

+ 78 - 0
toolchain/parse/handle_form_literal.cpp

@@ -0,0 +1,78 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/lex/token_kind.h"
+#include "toolchain/lex/tokenized_buffer.h"
+#include "toolchain/parse/context.h"
+#include "toolchain/parse/handle.h"
+#include "toolchain/parse/node_kind.h"
+#include "toolchain/parse/state.h"
+
+namespace Carbon::Parse {
+
+auto HandleFormLiteral(Context& context) -> void {
+  auto state = context.PopState();
+
+  auto keyword = context.ConsumeChecked(Lex::TokenKind::Form);
+  context.AddLeafNode(NodeKind::FormLiteralKeyword, keyword);
+  if (auto paren = context.ConsumeAndAddOpenParen(
+          keyword, NodeKind::FormLiteralOpenParen)) {
+    // Stash the open paren token for use by ConsumeAndAddCloseSymbol.
+    state.token = *paren;
+  } else {
+    state.has_error = true;
+  }
+  context.PushState(state, StateKind::FormLiteralFinish);
+  if (state.has_error) {
+    context.AddInvalidParse(keyword);
+  } else {
+    context.PushState(StateKind::PrimitiveForm, keyword);
+  }
+}
+
+auto HandlePrimitiveForm(Context& context) -> void {
+  auto state = context.PopState();
+  if (context.PositionIs(Lex::TokenKind::Ref) ||
+      context.PositionIs(Lex::TokenKind::Var) ||
+      context.PositionIs(Lex::TokenKind::Val)) {
+    state.token = context.Consume();
+  } else {
+    CARBON_DIAGNOSTIC(ExpectedCategoryModifier, Error,
+                      "expected `ref`, `var`, or `val` after `form(`");
+    context.emitter().Emit(*context.position(), ExpectedCategoryModifier);
+    state.has_error = true;
+  }
+  context.PushState(state, StateKind::PrimitiveFormFinish);
+  context.PushState(StateKind::Expr);
+}
+
+auto HandlePrimitiveFormFinish(Context& context) -> void {
+  auto state = context.PopState();
+
+  // Arbitrary default, only used in error recovery.
+  auto node_kind = NodeKind::ValPrimitiveForm;
+  switch (context.tokens().GetKind(state.token)) {
+    case Lex::TokenKind::Ref:
+      node_kind = NodeKind::RefPrimitiveForm;
+      break;
+    case Lex::TokenKind::Var:
+      node_kind = NodeKind::VarPrimitiveForm;
+      break;
+    case Lex::TokenKind::Val:
+      node_kind = NodeKind::ValPrimitiveForm;
+      break;
+    default:
+      CARBON_CHECK(state.has_error);
+      // Use the default node_kind set earlier for error recovery.
+      break;
+  }
+  context.AddNode(node_kind, state.token, state.has_error);
+}
+
+auto HandleFormLiteralFinish(Context& context) -> void {
+  auto state = context.PopState();
+  context.ConsumeAndAddCloseSymbol(state.token, state, NodeKind::FormLiteral);
+}
+
+}  // namespace Carbon::Parse

+ 10 - 0
toolchain/parse/handle_function.cpp

@@ -25,6 +25,10 @@ auto HandleFunctionAfterParams(Context& context) -> void {
     context.PushState(StateKind::FunctionReturnTypeFinish);
     context.ConsumeAndDiscard();
     context.PushStateForExpr(PrecedenceGroup::ForType());
+  } else if (context.PositionIs(Lex::TokenKind::MinusGreaterQuestion)) {
+    context.PushState(StateKind::FunctionReturnFormFinish);
+    context.ConsumeAndDiscard();
+    context.PushStateForExpr(PrecedenceGroup::ForType());
   }
 }
 
@@ -34,6 +38,12 @@ auto HandleFunctionReturnTypeFinish(Context& context) -> void {
   context.AddNode(NodeKind::ReturnType, state.token, state.has_error);
 }
 
+auto HandleFunctionReturnFormFinish(Context& context) -> void {
+  auto state = context.PopState();
+
+  context.AddNode(NodeKind::ReturnForm, state.token, state.has_error);
+}
+
 auto HandleFunctionSignatureFinish(Context& context) -> void {
   auto state = context.PopState();
 

+ 2 - 0
toolchain/parse/node_ids.h

@@ -177,6 +177,8 @@ using AnyPointerDeferenceExprId =
 using AnyRuntimeBindingPatternName =
     NodeIdOneOf<IdentifierNameNotBeforeParamsId, SelfValueNameId,
                 UnderscoreNameId>;
+using AnyPrimitiveFormIdId =
+    NodeIdOneOf<RefPrimitiveFormId, VarPrimitiveFormId, ValPrimitiveFormId>;
 
 // NodeId with kind that is anything but T::Kind.
 template <typename T>

+ 9 - 0
toolchain/parse/node_kind.def

@@ -145,6 +145,7 @@ CARBON_PARSE_NODE_KIND(CodeBlock)
 CARBON_PARSE_NODE_KIND(FunctionIntroducer)
 CARBON_PARSE_NODE_KIND(LambdaIntroducer)
 CARBON_PARSE_NODE_KIND(ReturnType)
+CARBON_PARSE_NODE_KIND(ReturnForm)
 CARBON_PARSE_NODE_KIND(FunctionDefinitionStart)
 CARBON_PARSE_NODE_KIND(FunctionDefinition)
 CARBON_PARSE_NODE_KIND(FunctionTerseDefinition)
@@ -178,6 +179,7 @@ CARBON_PARSE_NODE_KIND(UnusedPattern)
 CARBON_PARSE_NODE_KIND(LetBindingPattern)
 CARBON_PARSE_NODE_KIND(AssociatedConstantNameAndType)
 CARBON_PARSE_NODE_KIND(VarBindingPattern)
+CARBON_PARSE_NODE_KIND(FormBindingPattern)
 CARBON_PARSE_NODE_KIND(TemplateBindingName)
 CARBON_PARSE_NODE_KIND(CompileTimeBindingPatternStart)
 CARBON_PARSE_NODE_KIND(CompileTimeBindingPattern)
@@ -230,6 +232,13 @@ CARBON_PARSE_NODE_KIND(WhileStatement)
 CARBON_PARSE_NODE_KIND(IndexExprStart)
 CARBON_PARSE_NODE_KIND(IndexExpr)
 
+CARBON_PARSE_NODE_KIND(RefPrimitiveForm)
+CARBON_PARSE_NODE_KIND(VarPrimitiveForm)
+CARBON_PARSE_NODE_KIND(ValPrimitiveForm)
+CARBON_PARSE_NODE_KIND(FormLiteralKeyword)
+CARBON_PARSE_NODE_KIND(FormLiteralOpenParen)
+CARBON_PARSE_NODE_KIND(FormLiteral)
+
 CARBON_PARSE_NODE_KIND(ParenExprStart)
 CARBON_PARSE_NODE_KIND(ParenExpr)
 

+ 0 - 1
toolchain/parse/precedence.cpp

@@ -262,7 +262,6 @@ auto PrecedenceGroup::ForTrailing(Lex::TokenKind kind, bool infix)
     case Lex::TokenKind::TildeEqual:
     case Lex::TokenKind::Exclaim:
     case Lex::TokenKind::LessGreater:
-    case Lex::TokenKind::Question:
     case Lex::TokenKind::Colon:
       break;
 

+ 77 - 5
toolchain/parse/state.def

@@ -96,6 +96,51 @@ CARBON_PARSE_STATE(ArrayExprComma)
 //   (state done)
 CARBON_PARSE_STATE(ArrayExprFinish)
 
+// Handles the start of a form literal.
+//
+// form ( ...
+// ^~~~~~
+//   1. PrimitiveForm
+//   2. FormLiteralFinish
+//
+// form ???
+// ^~~~
+//   1. FormLiteralFinish
+CARBON_PARSE_STATE(FormLiteral)
+
+// Handles the category modifier and type of a form literal.
+//
+// form ( val ...
+//        ^~~
+// form ( var ...
+//        ^~~
+// form ( ref ...
+//        ^~~
+// form ( ???
+//       ^
+//   1. Expr
+//   2. PrimitiveFormFinish
+CARBON_PARSE_STATE(PrimitiveForm)
+
+// Finishes handling of PrimitiveForm.
+//
+// form ( ... )
+//           ^
+// form ???
+//         ^
+CARBON_PARSE_STATE(PrimitiveFormFinish)
+
+// Handles the end of a form literal.
+//
+// form ( ... )
+//            ^
+// form ( ??? )
+//        ^~~~~
+// form ???
+//     ^
+//   (state done)
+CARBON_PARSE_STATE(FormLiteralFinish)
+
 // Handles the `{` of a brace expression.
 //
 // {}
@@ -485,11 +530,16 @@ CARBON_PARSE_STATE(Expr)
 //   1. ParenExpr
 //   2. ExprInPostfixLoop
 //
-//  [
+//  array
 // ^
 //   1. ArrayExpr
 //   2. ExprInPostfixLoop
 //
+//  form
+// ^
+//   1. FormExpr
+//   2. ExprInPostfixLoop
+//
 //  ???
 // ^
 //   (state done)
@@ -765,6 +815,12 @@ CARBON_PARSE_STATE(LambdaBodyFinish)
 //   2. FunctionReturnTypeFinish
 //   3. FunctionSignatureFinish
 //
+// fn F(...) ->? ...
+//           ^~~
+//   1. Expr
+//   2. FunctionReturnFormFinish
+//   3. FunctionSignatureFinish
+//
 // fn F(...) ...
 //          ^
 //   1. FunctionSignatureFinish
@@ -777,6 +833,13 @@ CARBON_PARSE_STATE(FunctionAfterParams)
 //   (state done)
 CARBON_PARSE_STATE(FunctionReturnTypeFinish)
 
+// Finishes a function return form.
+//
+// fn F(...) ->? expr ...
+//                  ^
+//   (state done)
+CARBON_PARSE_STATE(FunctionReturnFormFinish)
+
 // Finishes a function signature. If it's a declaration, the function is done;
 // otherwise, this also starts definition processing.
 //
@@ -1086,11 +1149,11 @@ CARBON_PARSE_STATE(Pattern)
 // with `ref` in expression patterns. See issue #6342.
 //
 // [ref] name: ...
-// ^~~~~
+// ^~~~~~~~~~~
 // [ref] self: ...
-// ^~~~~
+// ^~~~~~~~~~~
 // [ref] _: ...
-// ^~
+// ^~~~~~~~
 //   1. Expr
 //   2. BindingPatternFinishAsRegular
 //
@@ -1103,6 +1166,15 @@ CARBON_PARSE_STATE(Pattern)
 //   1. Expr
 //   2. BindingPatternFinishAsGeneric
 //
+// name:? ...
+// ^~~~~~
+// self:? ...
+// ^~~~~~
+// _:? ...
+// ^~~
+//   1. Expr
+//   2. BindingPatternFinishAsForm
+//
 //  ???
 // ^
 //   1. BindingPatternFinishAsRegular
@@ -1113,7 +1185,7 @@ CARBON_PARSE_STATE(BindingPattern)
 // name: type
 //           ^
 //   (state done)
-CARBON_PARSE_STATE_VARIANTS2(BindingPatternFinish, Generic, Regular)
+CARBON_PARSE_STATE_VARIANTS3(BindingPatternFinish, Generic, Regular, Form)
 
 // Handles `var` in a pattern context.
 //

+ 173 - 0
toolchain/parse/testdata/basics/form_literals.carbon

@@ -0,0 +1,173 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/basics/form_literals.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/basics/form_literals.carbon
+
+// --- basics.carbon
+
+fn F() {
+  form(ref i32);
+  form(var T(X, Y));
+  form(val X.Y);
+}
+
+fn VarReturn() ->? form(ref i32);
+
+// --- fail_invalid_form_literal.carbon
+
+fn F() {
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:7: error: expected `(` after `form` [ExpectedParenAfter]
+  // CHECK:STDERR:   form;
+  // CHECK:STDERR:       ^
+  // CHECK:STDERR:
+  form;
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+8]]:8: error: expected `ref`, `var`, or `val` after `form(` [ExpectedCategoryModifier]
+  // CHECK:STDERR:   form();
+  // CHECK:STDERR:        ^
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8: error: expected expression [ExpectedExpr]
+  // CHECK:STDERR:   form();
+  // CHECK:STDERR:        ^
+  // CHECK:STDERR:
+  form();
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8: error: expected `ref`, `var`, or `val` after `form(` [ExpectedCategoryModifier]
+  // CHECK:STDERR:   form(i32);
+  // CHECK:STDERR:        ^~~
+  // CHECK:STDERR:
+  form(i32);
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+8]]:8: error: expected `ref`, `var`, or `val` after `form(` [ExpectedCategoryModifier]
+  // CHECK:STDERR:   form(whole lot of nonsense);
+  // CHECK:STDERR:        ^~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:14: error: unexpected tokens before `)` [ExpectedCloseSymbol]
+  // CHECK:STDERR:   form(whole lot of nonsense);
+  // CHECK:STDERR:              ^~~
+  // CHECK:STDERR:
+  form(whole lot of nonsense);
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:18: error: unexpected tokens before `)` [ExpectedCloseSymbol]
+  // CHECK:STDERR:   form(var whole lot of nonsense);
+  // CHECK:STDERR:                  ^~~
+  // CHECK:STDERR:
+  form(var whole lot of nonsense);
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+8]]:8: error: expected `(` after `form` [ExpectedParenAfter]
+  // CHECK:STDERR:   form whole lot of nonsense;
+  // CHECK:STDERR:        ^~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8: error: expected `;` after expression statement [ExpectedExprSemi]
+  // CHECK:STDERR:   form whole lot of nonsense;
+  // CHECK:STDERR:        ^~~~~
+  // CHECK:STDERR:
+  form whole lot of nonsense;
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+8]]:8: error: expected `(` after `form` [ExpectedParenAfter]
+  // CHECK:STDERR:   form var whole lot of nonsense;
+  // CHECK:STDERR:        ^~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_invalid_form_literal.carbon:[[@LINE+4]]:8: error: expected `;` after expression statement [ExpectedExprSemi]
+  // CHECK:STDERR:   form var whole lot of nonsense;
+  // CHECK:STDERR:        ^~~
+  // CHECK:STDERR:
+  form var whole lot of nonsense;
+}
+
+// CHECK:STDOUT: - filename: basics.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'F'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: '('},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'RefPrimitiveForm', text: 'ref', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: ')', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: '('},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'T'},
+// CHECK:STDOUT:               {kind: 'CallExprStart', text: '(', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'X'},
+// CHECK:STDOUT:               {kind: 'TupleLiteralComma', text: ','},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'Y'},
+// CHECK:STDOUT:             {kind: 'CallExpr', text: ')', subtree_size: 6},
+// CHECK:STDOUT:           {kind: 'VarPrimitiveForm', text: 'var', subtree_size: 7},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: ')', subtree_size: 10},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 11},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: '('},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'X'},
+// CHECK:STDOUT:               {kind: 'IdentifierNameNotBeforeParams', text: 'Y'},
+// CHECK:STDOUT:             {kind: 'MemberAccessExpr', text: '.', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'ValPrimitiveForm', text: 'val', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: ')', subtree_size: 7},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 31},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'VarReturn'},
+// CHECK:STDOUT:         {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:       {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: '('},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'RefPrimitiveForm', text: 'ref', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: ')', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ReturnForm', text: '->?', subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 11},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_invalid_form_literal.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'F'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: 'form', has_error: yes},
+// CHECK:STDOUT:           {kind: 'InvalidParse', text: 'form', has_error: yes},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: 'form', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: '('},
+// CHECK:STDOUT:             {kind: 'InvalidParse', text: ')', has_error: yes},
+// CHECK:STDOUT:           {kind: 'ValPrimitiveForm', text: 'form', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: ')', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: '('},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'ValPrimitiveForm', text: 'form', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: ')', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'whole'},
+// CHECK:STDOUT:           {kind: 'ValPrimitiveForm', text: 'form', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: ')', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'whole'},
+// CHECK:STDOUT:           {kind: 'VarPrimitiveForm', text: 'var', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: ')', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: 'form', has_error: yes},
+// CHECK:STDOUT:           {kind: 'InvalidParse', text: 'form', has_error: yes},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: 'form', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'FormLiteralKeyword', text: 'form'},
+// CHECK:STDOUT:           {kind: 'FormLiteralOpenParen', text: 'form', has_error: yes},
+// CHECK:STDOUT:           {kind: 'InvalidParse', text: 'form', has_error: yes},
+// CHECK:STDOUT:         {kind: 'FormLiteral', text: 'form', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 45},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 61 - 1
toolchain/parse/testdata/function/declaration.carbon

@@ -148,7 +148,7 @@ fn F();
 
 // --- fail_with_identifier_as_param.carbon
 
-// CHECK:STDERR: fail_with_identifier_as_param.carbon:[[@LINE+4]]:11: error: expected `:` or `:!` in binding pattern [ExpectedBindingPattern]
+// CHECK:STDERR: fail_with_identifier_as_param.carbon:[[@LINE+4]]:11: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern]
 // CHECK:STDERR: fn foo(bar);
 // CHECK:STDERR:           ^
 // CHECK:STDERR:
@@ -162,6 +162,16 @@ fn foo(bar);
 // CHECK:STDERR:
 fn (a tokens c d e f g h i j k l m n o p q r s t u v w x y z);
 
+// --- form_parameter.carbon
+
+fn FormParam(x:? Form);
+fn ComplexFormParam(x:? X.Y(Z));
+
+// --- return_form.carbon
+
+fn SimpleReturnForm() ->? Form;
+fn ComplexReturnForm() ->? X.Y(Z);
+
 // CHECK:STDOUT: - filename: basic.carbon
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
@@ -421,3 +431,53 @@ fn (a tokens c d e f g h i j k l m n o p q r s t u v w x y z);
 // CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: form_parameter.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'FormParam'},
+// CHECK:STDOUT:         {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'Form'},
+// CHECK:STDOUT:         {kind: 'FormBindingPattern', text: ':?', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'ExplicitParamList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 8},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'ComplexFormParam'},
+// CHECK:STDOUT:         {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'X'},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameNotBeforeParams', text: 'Y'},
+// CHECK:STDOUT:               {kind: 'MemberAccessExpr', text: '.', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'CallExprStart', text: '(', subtree_size: 4},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'Z'},
+// CHECK:STDOUT:           {kind: 'CallExpr', text: ')', subtree_size: 6},
+// CHECK:STDOUT:         {kind: 'FormBindingPattern', text: ':?', subtree_size: 8},
+// CHECK:STDOUT:       {kind: 'ExplicitParamList', text: ')', subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 13},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: return_form.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'SimpleReturnForm'},
+// CHECK:STDOUT:         {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:       {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'Form'},
+// CHECK:STDOUT:       {kind: 'ReturnForm', text: '->?', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 7},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'ComplexReturnForm'},
+// CHECK:STDOUT:         {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:       {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'X'},
+// CHECK:STDOUT:               {kind: 'IdentifierNameNotBeforeParams', text: 'Y'},
+// CHECK:STDOUT:             {kind: 'MemberAccessExpr', text: '.', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'CallExprStart', text: '(', subtree_size: 4},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'Z'},
+// CHECK:STDOUT:         {kind: 'CallExpr', text: ')', subtree_size: 6},
+// CHECK:STDOUT:       {kind: 'ReturnForm', text: '->?', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/generics/impl/fail_impl.carbon

@@ -54,7 +54,7 @@ impl forall f32;
 // CHECK:STDERR:
 impl forall [] u32;
 
-// CHECK:STDERR: fail_impl.carbon:[[@LINE+8]]:21: error: expected `:` or `:!` in binding pattern [ExpectedBindingPattern]
+// CHECK:STDERR: fail_impl.carbon:[[@LINE+8]]:21: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern]
 // CHECK:STDERR: impl forall [invalid] i8;
 // CHECK:STDERR:                     ^
 // CHECK:STDERR:

+ 1 - 1
toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon

@@ -9,7 +9,7 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon
 
 interface Foo {
-  // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+4]]:13: error: expected `:` or `:!` in binding pattern [ExpectedBindingPattern]
+  // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+4]]:13: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern]
   // CHECK:STDERR:   fn Sub[me Self](b: Self) -> Self;
   // CHECK:STDERR:             ^~~~
   // CHECK:STDERR:

+ 1 - 1
toolchain/parse/testdata/let/fail_missing_type.carbon

@@ -8,7 +8,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/fail_missing_type.carbon
 
-// CHECK:STDERR: fail_missing_type.carbon:[[@LINE+4]]:7: error: expected `:` or `:!` in binding pattern [ExpectedBindingPattern]
+// CHECK:STDERR: fail_missing_type.carbon:[[@LINE+4]]:7: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern]
 // CHECK:STDERR: let a = 4;
 // CHECK:STDERR:       ^
 // CHECK:STDERR:

+ 6 - 1
toolchain/parse/tree.h

@@ -196,7 +196,7 @@ class Tree : public Printable<Tree> {
 
   // The in-memory representation of data used for a particular node in the
   // tree.
-  class NodeImpl {
+  class NodeImpl : public Printable<NodeImpl> {
    public:
     explicit NodeImpl(NodeKind kind, bool has_error, Lex::TokenIndex token)
         : kind_(kind), has_error_(has_error), token_index_(token.index) {
@@ -210,6 +210,11 @@ class Tree : public Printable<Tree> {
       return Lex::TokenIndex(token_index_);
     }
 
+    auto Print(llvm::raw_ostream& output) const -> void {
+      output << "{kind: " << kind_ << ", has_error: " << has_error_
+             << ", token_index: " << token_index_ << "}\n";
+    }
+
    private:
     // The kind of this node.
     NodeKind kind_;

+ 9 - 7
toolchain/parse/tree_and_subtrees.cpp

@@ -35,17 +35,19 @@ TreeAndSubtrees::TreeAndSubtrees(const Lex::TokenizedBuffer& tokens,
         CARBON_CHECK(static_cast<size_t>(child.index) < subtree_sizes_.size());
         size += subtree_sizes_.Get(child);
         if (kind.has_bracket() && i == kind.child_count() - 1) {
-          CARBON_CHECK(kind.bracket() == tree.node_kind(child),
-                       "Node {0} with child count {1} needs bracket {2}, found "
-                       "wrong bracket {3}",
-                       kind, kind.child_count(), kind.bracket(),
-                       tree.node_kind(child));
+          CARBON_CHECK(
+              kind.bracket() == tree.node_kind(child),
+              "NodeId {0} ({1}) with child count {2} needs bracket {3}, found "
+              "wrong bracket {4}",
+              n, kind, kind.child_count(), kind.bracket(),
+              tree.node_kind(child));
         }
       }
     } else {
       while (true) {
-        CARBON_CHECK(!size_stack.empty(), "Node {0} is missing bracket {1}",
-                     kind, kind.bracket());
+        CARBON_CHECK(!size_stack.empty(),
+                     "NodeId {0} ({1}) is missing bracket {2}", kind, n,
+                     kind.bracket());
         auto child = size_stack.pop_back_val();
         size += subtree_sizes_.Get(child);
         if (kind.bracket() == tree.node_kind(child)) {

+ 62 - 1
toolchain/parse/typed_nodes.h

@@ -365,6 +365,16 @@ struct VarBindingPattern {
   AnyExprId type;
 };
 
+// A form binding pattern, such as `name:? Form`.
+struct FormBindingPattern {
+  static constexpr auto Kind = NodeKind::FormBindingPattern.Define(
+      {.category = NodeCategory::Pattern, .child_count = 2});
+
+  AnyRuntimeBindingPatternName name;
+  Lex::ColonQuestionTokenIndex token;
+  AnyExprId type;
+};
+
 // A template binding name: `template T`.
 struct TemplateBindingName {
   static constexpr auto Kind =
@@ -452,6 +462,14 @@ struct ReturnType {
   AnyExprId type;
 };
 
+// A return form: `->? form(var i32)`
+struct ReturnForm {
+  static constexpr auto Kind = NodeKind::ReturnForm.Define({.child_count = 1});
+
+  Lex::MinusGreaterQuestionTokenIndex token;
+  AnyExprId type;
+};
+
 // A function signature: `fn F() -> i32`.
 template <const NodeKind& KindT, typename TokenKind,
           NodeCategory::RawEnumType Category>
@@ -462,7 +480,7 @@ struct FunctionSignature {
   FunctionIntroducerId introducer;
   llvm::SmallVector<AnyModifierId> modifiers;
   DeclName name;
-  std::optional<ReturnTypeId> return_type;
+  std::optional<NodeIdOneOf<ReturnTypeId, ReturnFormId>> return_type;
   TokenKind token;
 };
 
@@ -964,6 +982,49 @@ struct ArrayExpr {
   Lex::CloseParenTokenIndex token;
 };
 
+struct RefPrimitiveForm {
+  static constexpr auto Kind = NodeKind::RefPrimitiveForm.Define(
+      {.category = NodeCategory::Expr, .child_count = 1});
+
+  Lex::RefTokenIndex token;
+  AnyExprId type;
+};
+
+struct VarPrimitiveForm {
+  static constexpr auto Kind = NodeKind::VarPrimitiveForm.Define(
+      {.category = NodeCategory::Expr, .child_count = 1});
+
+  Lex::VarTokenIndex token;
+  AnyExprId type;
+};
+
+struct ValPrimitiveForm {
+  static constexpr auto Kind = NodeKind::ValPrimitiveForm.Define(
+      {.category = NodeCategory::Expr, .child_count = 1});
+
+  Lex::ValTokenIndex token;
+  AnyExprId type;
+};
+
+using FormLiteralKeyword =
+    LeafNode<NodeKind::FormLiteralKeyword, Lex::FormTokenIndex>;
+
+using FormLiteralOpenParen =
+    LeafNode<NodeKind::FormLiteralOpenParen, Lex::OpenParenTokenIndex>;
+
+// A `form` literal: `form(ref i32)`
+struct FormLiteral {
+  static constexpr auto Kind = NodeKind::FormLiteral.Define(
+      {.category = NodeCategory::Expr,
+       .bracketed_by = NodeKind::FormLiteralKeyword,
+       .child_count = 3});
+
+  FormLiteralKeywordId keyword;
+  FormLiteralOpenParenId start;
+  AnyPrimitiveFormIdId category;
+  Lex::CloseParenTokenIndex token;
+};
+
 // The opening portion of an indexing expression: `a[`.
 //
 // TODO: Consider flattening this into `IndexExpr`.