Просмотр исходного кода

Basic support for generic bindings. (#3555)

This change adds a `BindSymbolicName` instruction for generic bindings,
paralleling the existing `BindName`. A mechanism is also added to allow
both kinds of binding to be accessed uniformly, for convenience in the
case where the two different kinds of binding are treated the same.

Generic bindings of type `type` are allowed to be used as types,
although no operations are provided for such types. For now lowering
treats these types as empty structs, which seems like a reasonable
lowering for non-monomorphized unconstrained types.
Richard Smith 2 лет назад
Родитель
Сommit
a6508fcf05

+ 7 - 0
toolchain/check/context.cpp

@@ -1009,6 +1009,7 @@ class TypeCompleter {
       case SemIR::Builtin::Kind:
         CARBON_FATAL() << "Builtins should be named as cross-references";
 
+      case SemIR::BindSymbolicName::Kind:
       case SemIR::PointerType::Kind:
       case SemIR::UnboundElementType::Kind:
         return MakeCopyValueRepr(type_id);
@@ -1126,6 +1127,12 @@ static auto ProfileType(Context& semantics_context, SemIR::Inst inst,
               .GetUnqualifiedType(inst.As<SemIR::ConstType>().inner_id)
               .index);
       break;
+    case SemIR::BindSymbolicName::Kind:
+      // TODO: Use de Bruijn levels or similar to identify equivalent type
+      // bindings across redeclarations.
+      canonical_id.AddInteger(
+          inst.As<SemIR::BindSymbolicName>().bind_name_id.index);
+      break;
     case SemIR::PointerType::Kind:
       canonical_id.AddInteger(inst.As<SemIR::PointerType>().pointee_id.index);
       break;

+ 4 - 0
toolchain/check/convert.cpp

@@ -51,6 +51,10 @@ static auto FindReturnSlotForInitializer(SemIR::File& sem_ir,
         if (!SemIR::GetInitRepr(sem_ir, call.type_id).has_return_slot()) {
           return SemIR::InstId::Invalid;
         }
+        if (!call.args_id.is_valid()) {
+          // Argument initialization failed, so we have no return slot.
+          return SemIR::InstId::Invalid;
+        }
         return sem_ir.inst_blocks().Get(call.args_id).back();
       }
     }

+ 38 - 19
toolchain/check/handle_binding_pattern.cpp

@@ -10,10 +10,10 @@
 namespace Carbon::Check {
 
 auto HandleAddress(Context& context, Parse::AddressId parse_node) -> bool {
-  auto self_param_id =
-      context.node_stack().Pop<Parse::NodeKind::BindingPattern>();
-  auto self_param = context.insts().TryGetAs<SemIR::BindName>(self_param_id);
-  if (self_param &&
+  auto self_param_id = context.node_stack().PopPattern();
+  if (auto self_param =
+          context.insts().TryGetAs<SemIR::AnyBindName>(self_param_id);
+      self_param &&
       context.bind_names().Get(self_param->bind_name_id).name_id ==
           SemIR::NameId::SelfValue) {
     // TODO: The type of an `addr_pattern` should probably be the non-pointer
@@ -30,14 +30,8 @@ auto HandleAddress(Context& context, Parse::AddressId parse_node) -> bool {
   return true;
 }
 
-auto HandleGenericBindingPattern(Context& context,
-                                 Parse::GenericBindingPatternId parse_node)
-    -> bool {
-  return context.TODO(parse_node, "GenericBindingPattern");
-}
-
-auto HandleBindingPattern(Context& context, Parse::BindingPatternId parse_node)
-    -> bool {
+auto HandleAnyBindingPattern(Context& context, Parse::NodeId parse_node,
+                             bool is_generic) -> bool {
   auto [type_node, parsed_type_id] =
       context.node_stack().PopExprWithParseNode();
   auto type_node_copy = type_node;
@@ -49,8 +43,6 @@ auto HandleBindingPattern(Context& context, Parse::BindingPatternId parse_node)
   auto [name_node, name_id] = context.node_stack().PopNameWithParseNode();
 
   // Create the appropriate kind of binding for this pattern.
-  //
-  // TODO: Update this to create a generic or template binding as needed.
   auto make_bind_name = [&, name_node = name_node, name_id = name_id](
                             SemIR::TypeId type_id,
                             SemIR::InstId value_id) -> SemIR::Inst {
@@ -58,7 +50,13 @@ auto HandleBindingPattern(Context& context, Parse::BindingPatternId parse_node)
     auto bind_name_id = context.bind_names().Add(
         {.name_id = name_id,
          .enclosing_scope_id = SemIR::NameScopeId::Invalid});
-    return SemIR::BindName{name_node, type_id, bind_name_id, value_id};
+    if (is_generic) {
+      // TODO: Create a `BindTemplateName` instead inside a `template` pattern.
+      return SemIR::BindSymbolicName{name_node, type_id, bind_name_id,
+                                     value_id};
+    } else {
+      return SemIR::BindName{name_node, type_id, bind_name_id, value_id};
+    }
   };
 
   // A `self` binding can only appear in an implicit parameter list.
@@ -78,6 +76,17 @@ auto HandleBindingPattern(Context& context, Parse::BindingPatternId parse_node)
               context.node_stack().PeekParseNodeKind()) {
     case Parse::NodeKind::ReturnedModifier:
     case Parse::NodeKind::VariableIntroducer: {
+      if (is_generic) {
+        CARBON_DIAGNOSTIC(
+            CompileTimeBindingInVarDecl, Error,
+            "`var` declaration cannot declare a compile-time binding.");
+        context.emitter().Emit(type_node, CompileTimeBindingInVarDecl);
+      }
+      auto binding_id =
+          is_generic
+              ? Parse::NodeId::Invalid
+              : context.parse_tree().As<Parse::BindingPatternId>(parse_node);
+
       // A `var` declaration at class scope introduces a field.
       auto enclosing_class_decl = context.GetCurrentScopeAs<SemIR::ClassDecl>();
       cast_type_id = context.AsCompleteType(cast_type_id, [&] {
@@ -103,18 +112,18 @@ auto HandleBindingPattern(Context& context, Parse::BindingPatternId parse_node)
         auto& class_info =
             context.classes().Get(enclosing_class_decl->class_id);
         auto field_type_inst_id = context.AddInst(SemIR::UnboundElementType{
-            parse_node, context.GetBuiltinType(SemIR::BuiltinKind::TypeType),
+            binding_id, context.GetBuiltinType(SemIR::BuiltinKind::TypeType),
             class_info.self_type_id, cast_type_id});
         value_type_id = context.CanonicalizeType(field_type_inst_id);
         value_id = context.AddInst(
-            SemIR::FieldDecl{parse_node, value_type_id, name_id,
+            SemIR::FieldDecl{binding_id, value_type_id, name_id,
                              SemIR::ElementIndex(context.args_type_info_stack()
                                                      .PeekCurrentBlockContents()
                                                      .size())});
 
         // Add a corresponding field to the object representation of the class.
         context.args_type_info_stack().AddInst(
-            SemIR::StructTypeField{parse_node, name_id, cast_type_id});
+            SemIR::StructTypeField{binding_id, name_id, cast_type_id});
       } else {
         value_id = context.AddInst(
             SemIR::VarStorage{name_node, value_type_id, name_id});
@@ -166,8 +175,18 @@ auto HandleBindingPattern(Context& context, Parse::BindingPatternId parse_node)
   return true;
 }
 
+auto HandleBindingPattern(Context& context, Parse::BindingPatternId parse_node)
+    -> bool {
+  return HandleAnyBindingPattern(context, parse_node, /*is_generic=*/false);
+}
+
+auto HandleGenericBindingPattern(Context& context,
+                                 Parse::GenericBindingPatternId parse_node)
+    -> bool {
+  return HandleAnyBindingPattern(context, parse_node, /*is_generic=*/true);
+}
+
 auto HandleTemplate(Context& context, Parse::TemplateId parse_node) -> bool {
-  // TODO: diagnose if this occurs in a `var` context.
   return context.TODO(parse_node, "HandleTemplate");
 }
 

+ 4 - 0
toolchain/check/handle_class.cpp

@@ -23,6 +23,10 @@ auto HandleClassIntroducer(Context& context,
 
 static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId parse_node)
     -> std::tuple<SemIR::ClassId, SemIR::InstId> {
+  if (context.node_stack().PopIf<Parse::NodeKind::TuplePattern>()) {
+    context.TODO(parse_node, "generic class");
+  }
+
   auto name_context = context.decl_name_stack().FinishName();
   context.node_stack()
       .PopAndDiscardSoloParseNode<Parse::NodeKind::ClassIntroducer>();

+ 1 - 1
toolchain/check/handle_function.cpp

@@ -261,7 +261,7 @@ auto HandleFunctionDefinitionStart(Context& context,
           context.sem_ir().StringifyType(param.type_id()));
     });
 
-    if (auto fn_param = param.TryAs<SemIR::BindName>()) {
+    if (auto fn_param = param.TryAs<SemIR::AnyBindName>()) {
       context.AddNameToLookup(
           fn_param->parse_node,
           context.bind_names().Get(fn_param->bind_name_id).name_id, param_id);

+ 4 - 0
toolchain/check/handle_interface.cpp

@@ -24,6 +24,10 @@ auto HandleInterfaceIntroducer(Context& context,
 static auto BuildInterfaceDecl(Context& context,
                                Parse::AnyInterfaceDeclId parse_node)
     -> std::tuple<SemIR::InterfaceId, SemIR::InstId> {
+  if (context.node_stack().PopIf<Parse::NodeKind::TuplePattern>()) {
+    context.TODO(parse_node, "generic interface");
+  }
+
   auto name_context = context.decl_name_stack().FinishName();
   context.node_stack()
       .PopAndDiscardSoloParseNode<Parse::NodeKind::InterfaceIntroducer>();

+ 2 - 3
toolchain/check/handle_let.cpp

@@ -14,8 +14,7 @@ auto HandleLetDecl(Context& context, Parse::LetDeclId parse_node) -> bool {
   if (context.node_stack().PeekIs<Parse::NodeKind::TuplePattern>()) {
     return context.TODO(parse_node, "tuple pattern in let");
   }
-  SemIR::InstId pattern_id =
-      context.node_stack().Pop<Parse::NodeKind::BindingPattern>();
+  SemIR::InstId pattern_id = context.node_stack().PopPattern();
   context.node_stack()
       .PopAndDiscardSoloParseNode<Parse::NodeKind::LetIntroducer>();
   // Process declaration modifiers.
@@ -44,7 +43,7 @@ auto HandleLetDecl(Context& context, Parse::LetDeclId parse_node) -> bool {
   // Update the binding with its value and add it to the current block, after
   // the computation of the value.
   // TODO: Support other kinds of pattern here.
-  auto bind_name = pattern.As<SemIR::BindName>();
+  auto bind_name = pattern.As<SemIR::AnyBindName>();
   CARBON_CHECK(!bind_name.value_id.is_valid())
       << "Binding should not already have a value!";
   bind_name.value_id = value_id;

+ 4 - 2
toolchain/check/handle_variable.cpp

@@ -53,8 +53,9 @@ auto HandleVariableDecl(Context& context, Parse::VariableDeclId parse_node)
   }
 
   // Extract the name binding.
-  auto value_id = context.node_stack().Pop<Parse::NodeKind::BindingPattern>();
-  if (auto bind_name = context.insts().Get(value_id).TryAs<SemIR::BindName>()) {
+  auto value_id = context.node_stack().PopPattern();
+  if (auto bind_name =
+          context.insts().Get(value_id).TryAs<SemIR::AnyBindName>()) {
     // Form a corresponding name in the current context, and bind the name to
     // the variable.
     auto name_context = context.decl_name_stack().MakeUnqualifiedName(
@@ -63,6 +64,7 @@ auto HandleVariableDecl(Context& context, Parse::VariableDeclId parse_node)
     context.decl_name_stack().AddNameToLookup(name_context, value_id);
     value_id = bind_name->value_id;
   }
+  // TODO: Handle other kinds of pattern.
 
   // Pop the `returned` specifier if present.
   context.node_stack()

+ 13 - 0
toolchain/check/node_stack.h

@@ -134,6 +134,12 @@ class NodeStack {
     return PopWithParseNode<SemIR::InstId>();
   }
 
+  // Pops a pattern from the top of the stack and returns the parse_node and
+  // the ID.
+  auto PopPatternWithParseNode() -> std::pair<Parse::NodeId, SemIR::InstId> {
+    return PopWithParseNode<SemIR::InstId>();
+  }
+
   // Pops a name from the top of the stack and returns the parse_node and
   // the ID.
   auto PopNameWithParseNode() -> std::pair<Parse::NodeId, SemIR::NameId> {
@@ -203,6 +209,12 @@ class NodeStack {
   // Expressions map multiple Parse::NodeKinds to SemIR::InstId always.
   auto PopExpr() -> SemIR::InstId { return PopExprWithParseNode().second; }
 
+  // Pops a pattern from the top of the stack and returns the ID.
+  // Patterns map multiple Parse::NodeKinds to SemIR::InstId always.
+  auto PopPattern() -> SemIR::InstId {
+    return PopPatternWithParseNode().second;
+  }
+
   // Pops a name from the top of the stack and returns the ID.
   auto PopName() -> SemIR::NameId { return PopNameWithParseNode().second; }
 
@@ -359,6 +371,7 @@ class NodeStack {
       case Parse::NodeKind::BindingPattern:
       case Parse::NodeKind::CallExpr:
       case Parse::NodeKind::CallExprStart:
+      case Parse::NodeKind::GenericBindingPattern:
       case Parse::NodeKind::IdentifierNameExpr:
       case Parse::NodeKind::IfExprThen:
       case Parse::NodeKind::IfExprElse:

+ 35 - 0
toolchain/check/testdata/function/generic/fail_type_param_mismatch.carbon

@@ -0,0 +1,35 @@
+// 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
+
+fn F(T:! type, U:! type) {
+  var p: T*;
+  // CHECK:STDERR: fail_type_param_mismatch.carbon:[[@LINE+3]]:3: ERROR: Cannot implicitly convert from `T` to `U`.
+  // CHECK:STDERR:   let n: U = *p;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~
+  let n: U = *p;
+}
+
+// CHECK:STDOUT: --- fail_type_param_mismatch.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.F = %F}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%T: type, %U: type) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %T.ref: type = name_ref T, %T
+// CHECK:STDOUT:   %.loc8: type = ptr_type T
+// CHECK:STDOUT:   %p.var: ref T* = var p
+// CHECK:STDOUT:   %p: ref T* = bind_name p, %p.var
+// CHECK:STDOUT:   %U.ref: type = name_ref U, %U
+// CHECK:STDOUT:   %p.ref: ref T* = name_ref p, %p
+// CHECK:STDOUT:   %.loc12_15: T* = bind_value %p.ref
+// CHECK:STDOUT:   %.loc12_14: ref T = deref %.loc12_15
+// CHECK:STDOUT:   %n: U = bind_name n, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 33 - 0
toolchain/check/testdata/function/generic/type_param.carbon

@@ -0,0 +1,33 @@
+// 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
+
+fn F(T:! type) {
+  var p: T*;
+  let n: T = *p;
+}
+
+// CHECK:STDOUT: --- type_param.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.F = %F}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%T: type) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %T.ref.loc8: type = name_ref T, %T
+// CHECK:STDOUT:   %.loc8: type = ptr_type T
+// CHECK:STDOUT:   %p.var: ref T* = var p
+// CHECK:STDOUT:   %p: ref T* = bind_name p, %p.var
+// CHECK:STDOUT:   %T.ref.loc9: type = name_ref T, %T
+// CHECK:STDOUT:   %p.ref: ref T* = name_ref p, %p
+// CHECK:STDOUT:   %.loc9_15: T* = bind_value %p.ref
+// CHECK:STDOUT:   %.loc9_14.1: ref T = deref %.loc9_15
+// CHECK:STDOUT:   %.loc9_14.2: T = bind_value %.loc9_14.1
+// CHECK:STDOUT:   %n: T = bind_name n, %.loc9_14.2
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 36 - 0
toolchain/check/testdata/let/fail_generic.carbon

@@ -0,0 +1,36 @@
+// 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
+
+// TODO: Should this be valid?
+fn F(a: i32) -> i32 {
+  let T:! type = i32;
+  // CHECK:STDERR: fail_generic.carbon:[[@LINE+3]]:3: ERROR: Cannot implicitly convert from `i32` to `T`.
+  // CHECK:STDERR:   let x: T = 5;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~
+  let x: T = 5;
+  // CHECK:STDERR: fail_generic.carbon:[[@LINE+3]]:3: ERROR: Cannot implicitly convert from `T` to `i32`.
+  // CHECK:STDERR:   return x;
+  // CHECK:STDERR:   ^~~~~~~~~
+  return x;
+}
+
+// CHECK:STDOUT: --- fail_generic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.F = %F}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%a: i32) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, i32
+// CHECK:STDOUT:   %T.ref: type = name_ref T, %T
+// CHECK:STDOUT:   %.loc13: i32 = int_literal 5
+// CHECK:STDOUT:   %x: T = bind_name x, <error>
+// CHECK:STDOUT:   %x.ref: T = name_ref x, %x
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 37 - 0
toolchain/check/testdata/let/generic.carbon

@@ -0,0 +1,37 @@
+// 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
+
+fn F() {
+  let T:! type = i32;
+  var p: T*;
+  var a: T = *p;
+}
+
+// CHECK:STDOUT: --- generic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.F = %F}
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, i32
+// CHECK:STDOUT:   %T.ref.loc9: type = name_ref T, %T
+// CHECK:STDOUT:   %.loc9: type = ptr_type T
+// CHECK:STDOUT:   %p.var: ref T* = var p
+// CHECK:STDOUT:   %p: ref T* = bind_name p, %p.var
+// CHECK:STDOUT:   %T.ref.loc10: type = name_ref T, %T
+// CHECK:STDOUT:   %a.var: ref T = var a
+// CHECK:STDOUT:   %a: ref T = bind_name a, %a.var
+// CHECK:STDOUT:   %p.ref: ref T* = name_ref p, %p
+// CHECK:STDOUT:   %.loc10_15: T* = bind_value %p.ref
+// CHECK:STDOUT:   %.loc10_14.1: ref T = deref %.loc10_15
+// CHECK:STDOUT:   %.loc10_14.2: T = bind_value %.loc10_14.1
+// CHECK:STDOUT:   assign %a.var, %.loc10_14.2
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 29 - 0
toolchain/check/testdata/var/fail_generic.carbon

@@ -0,0 +1,29 @@
+// 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
+
+fn Main() {
+  // CHECK:STDERR: fail_generic.carbon:[[@LINE+3]]:11: ERROR: `var` declaration cannot declare a compile-time binding.
+  // CHECK:STDERR:   var x:! i32 = 0;
+  // CHECK:STDERR:           ^~~
+  var x:! i32 = 0;
+}
+
+// CHECK:STDOUT: --- fail_generic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Main = %Main}
+// CHECK:STDOUT:   %Main: <function> = fn_decl @Main
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Main() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %x.var: ref i32 = var x
+// CHECK:STDOUT:   %x: i32 = bind_symbolic_name x, %x.var
+// CHECK:STDOUT:   %.loc11: i32 = int_literal 0
+// CHECK:STDOUT:   assign %x.var, %.loc11
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -174,6 +174,7 @@ CARBON_DIAGNOSTIC_KIND(DerefOfNonPointer)
 CARBON_DIAGNOSTIC_KIND(DerefOfType)
 CARBON_DIAGNOSTIC_KIND(FunctionPreviousDefinition)
 CARBON_DIAGNOSTIC_KIND(FunctionRedefinition)
+CARBON_DIAGNOSTIC_KIND(CompileTimeBindingInVarDecl)
 CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend)
 CARBON_DIAGNOSTIC_KIND(NameNotFound)
 CARBON_DIAGNOSTIC_KIND(NameDeclDuplicate)

+ 6 - 1
toolchain/lower/file_context.cpp

@@ -219,7 +219,9 @@ auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id)
             sem_ir().insts().TryGetAs<SemIR::AddrPattern>(param_ref_id)) {
       bind_name_id = addr->inner_id;
     }
-    CARBON_CHECK(sem_ir().insts().TryGetAs<SemIR::BindName>(bind_name_id));
+    auto bind_name = sem_ir().insts().Get(bind_name_id);
+    // TODO: Should we stop passing compile-time bindings at runtime?
+    CARBON_CHECK(bind_name.Is<SemIR::AnyBindName>());
     function_lowering.SetLocal(bind_name_id, param_value);
   }
 
@@ -272,6 +274,9 @@ auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
           GetType(array_type.element_type_id),
           sem_ir_->GetArrayBoundValue(array_type.bound_id));
     }
+    case SemIR::BindSymbolicName::Kind:
+      // Treat non-monomorphized type bindings as opaque.
+      return llvm::StructType::get(*llvm_context_);
     case SemIR::ClassType::Kind: {
       auto object_repr_id = sem_ir_->classes()
                                 .Get(inst.As<SemIR::ClassType>().class_id)

+ 5 - 0
toolchain/lower/handle.cpp

@@ -65,6 +65,11 @@ auto HandleBindName(FunctionContext& context, SemIR::InstId inst_id,
   context.SetLocal(inst_id, context.GetValue(inst.value_id));
 }
 
+auto HandleBindSymbolicName(FunctionContext& context, SemIR::InstId inst_id,
+                            SemIR::BindSymbolicName inst) -> void {
+  context.SetLocal(inst_id, context.GetValue(inst.value_id));
+}
+
 auto HandleBlockArg(FunctionContext& context, SemIR::InstId inst_id,
                     SemIR::BlockArg inst) -> void {
   context.SetLocal(inst_id, context.GetBlockArg(inst.block_id, inst.type_id));

+ 22 - 0
toolchain/lower/testdata/function/generic/type_param.carbon

@@ -0,0 +1,22 @@
+// 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
+
+fn F(T:! type) {
+  var p: T*;
+  let n: T = *p;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'type_param.carbon'
+// CHECK:STDOUT: source_filename = "type_param.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %type = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @F(%type %T) {
+// CHECK:STDOUT:   %p = alloca ptr, align 8
+// CHECK:STDOUT:   %1 = load ptr, ptr %p, align 8
+// CHECK:STDOUT:   %2 = load {}, ptr %1, align 1
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }

+ 2 - 0
toolchain/parse/node_kind.def

@@ -298,6 +298,8 @@ CARBON_PARSE_NODE_KIND_BRACKET(ArrayExpr, ArrayExprSemi, CloseSquareBracket)
 //     [Generic]BindingPattern
 //   _optional_ Address
 // _optional_ Template
+//
+// TODO: Rename GenericBindingPattern to CompileTimeBindingPattern.
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(BindingPattern, 2, CARBON_IF_VALID(Colon))
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(GenericBindingPattern, 2, ColonExclaim)
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(Address, 1, Addr)

+ 8 - 1
toolchain/sem_ir/file.cpp

@@ -26,7 +26,7 @@ auto Function::GetParamFromParamRefId(const File& sem_ir, InstId param_ref_id)
     ref = sem_ir.insts().Get(param_ref_id);
   }
 
-  if (auto bind_name = ref.TryAs<SemIR::BindName>()) {
+  if (auto bind_name = ref.TryAs<SemIR::AnyBindName>()) {
     param_ref_id = bind_name->value_id;
     ref = sem_ir.insts().Get(param_ref_id);
   }
@@ -178,6 +178,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
   // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
   switch (kind) {
     case ArrayType::Kind:
+    case BindSymbolicName::Kind:
     case Builtin::Kind:
     case ClassType::Kind:
     case NameRef::Kind:
@@ -300,6 +301,11 @@ auto File::StringifyTypeExpr(InstId outer_inst_id) const -> std::string {
         }
         break;
       }
+      case BindSymbolicName::Kind: {
+        auto name_id = inst.As<BindSymbolicName>().bind_name_id;
+        out << names().GetFormatted(bind_names().Get(name_id).name_id);
+        break;
+      }
       case ClassType::Kind: {
         auto class_name_id =
             classes().Get(inst.As<ClassType>().class_id).name_id;
@@ -509,6 +515,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case AddressOf::Kind:
       case AddrPattern::Kind:
       case ArrayType::Kind:
+      case BindSymbolicName::Kind:
       case BindValue::Kind:
       case BlockArg::Kind:
       case BoolLiteral::Kind:

+ 3 - 2
toolchain/sem_ir/formatter.cpp

@@ -462,9 +462,10 @@ class InstNamer {
           CollectNamesInBlock(scope_idx, inst.As<SpliceBlock>().block_id);
           break;
         }
-        case BindName::Kind: {
+        case BindName::Kind:
+        case BindSymbolicName::Kind: {
           add_inst_name_id(sem_ir_.bind_names()
-                               .Get(inst.As<BindName>().bind_name_id)
+                               .Get(inst.As<AnyBindName>().bind_name_id)
                                .name_id);
           continue;
         }

+ 2 - 3
toolchain/sem_ir/inst.h

@@ -255,9 +255,8 @@ class Inst : public Printable<Inst> {
 };
 
 // TODO: This is currently 20 bytes because we sometimes have 2 arguments for a
-// pair of Insts. However, InstKind is 1 byte; if args
-// were 3.5 bytes, we could potentially shrink Inst by 4 bytes. This
-// may be worth investigating further.
+// pair of Insts. However, InstKind is 1 byte; if args were 3.5 bytes, we could
+// potentially shrink Inst by 4 bytes. This may be worth investigating further.
 static_assert(sizeof(Inst) == 20, "Unexpected Inst size");
 
 // Instruction-like types can be printed by converting them to instructions.

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -24,6 +24,7 @@ CARBON_SEM_IR_INST_KIND(ArrayInit)
 CARBON_SEM_IR_INST_KIND(ArrayType)
 CARBON_SEM_IR_INST_KIND(Assign)
 CARBON_SEM_IR_INST_KIND(BaseDecl)
+CARBON_SEM_IR_INST_KIND(BindSymbolicName)
 CARBON_SEM_IR_INST_KIND(BindName)
 CARBON_SEM_IR_INST_KIND(BindValue)
 CARBON_SEM_IR_INST_KIND(BlockArg)

+ 24 - 1
toolchain/sem_ir/typed_insts.h

@@ -64,7 +64,7 @@ struct AddrPattern {
 
   Parse::AddressId parse_node;
   TypeId type_id;
-  // The `self` parameter.
+  // The `self` binding.
   InstId inner_id;
 };
 
@@ -125,6 +125,29 @@ struct BaseDecl {
   ElementIndex index;
 };
 
+// Common representation for both kinds of `bind*name` node.
+struct AnyBindName {
+  // TODO: Also handle BindTemplateName once it exists.
+  static constexpr InstKind Kinds[] = {InstKind::BindName,
+                                       InstKind::BindSymbolicName};
+
+  InstKind kind;
+  Parse::NodeId parse_node;
+  TypeId type_id;
+  BindNameId bind_name_id;
+  InstId value_id;
+};
+
+struct BindSymbolicName {
+  static constexpr auto Kind =
+      InstKind::BindSymbolicName.Define("bind_symbolic_name");
+
+  Parse::NodeId parse_node;
+  TypeId type_id;
+  BindNameId bind_name_id;
+  InstId value_id;
+};
+
 struct BindName {
   static constexpr auto Kind = InstKind::BindName.Define("bind_name");