Browse Source

When a builtin function expects type T also allow an adapter for T. (#4643)

Extends the set of function signatures that support being given a
builtin definition to include cases where a parameter or return type is
an adapter for a supported type. For example, if we can give a builtin
definition to `Add(a: i32, b: i32) -> i32`, then we can also give a
builtin definition to `Add(a: MyI32, b: MyI32) - >MyI32` where `MyI32`
adapts `i32`.

This is a prerequisite for changing `Core.Int` to be a class type that
adapts the builtin int type.
Richard Smith 1 year ago
parent
commit
cd1ecf1297

+ 22 - 15
toolchain/check/handle_function.cpp

@@ -328,21 +328,9 @@ auto HandleParseNode(Context& context, Parse::FunctionDeclId node_id) -> bool {
   return true;
 }
 
-// Processes a function definition after a signature for which we have already
-// built a function ID. This logic is shared between processing regular function
-// definitions and delayed parsing of inline method definitions.
-static auto HandleFunctionDefinitionAfterSignature(
-    Context& context, Parse::FunctionDefinitionStartId node_id,
-    SemIR::FunctionId function_id, SemIR::InstId decl_id) -> void {
-  auto& function = context.functions().Get(function_id);
-
-  // Create the function scope and the entry block.
-  context.return_scope_stack().push_back({.decl_id = decl_id});
-  context.inst_block_stack().Push();
-  context.scope_stack().Push(decl_id);
-  StartGenericDefinition(context);
-  context.AddCurrentCodeBlockToFunction();
-
+static auto CheckFunctionDefinitionSignature(Context& context,
+                                             SemIR::Function& function)
+    -> void {
   // Check the return type is complete.
   CheckFunctionReturnType(context, function.return_slot_pattern_id, function,
                           SemIR::SpecificId::Invalid);
@@ -370,6 +358,24 @@ static auto HandleFunctionDefinitionAfterSignature(
               param_ref_id, IncompleteTypeInFunctionParam, param_ref_id);
         });
   }
+}
+
+// Processes a function definition after a signature for which we have already
+// built a function ID. This logic is shared between processing regular function
+// definitions and delayed parsing of inline method definitions.
+static auto HandleFunctionDefinitionAfterSignature(
+    Context& context, Parse::FunctionDefinitionStartId node_id,
+    SemIR::FunctionId function_id, SemIR::InstId decl_id) -> void {
+  auto& function = context.functions().Get(function_id);
+
+  // Create the function scope and the entry block.
+  context.return_scope_stack().push_back({.decl_id = decl_id});
+  context.inst_block_stack().Push();
+  context.scope_stack().Push(decl_id);
+  StartGenericDefinition(context);
+  context.AddCurrentCodeBlockToFunction();
+
+  CheckFunctionDefinitionSignature(context, function);
 
   context.node_stack().Push(node_id, function_id);
 }
@@ -508,6 +514,7 @@ auto HandleParseNode(Context& context,
   auto builtin_kind = LookupBuiltinFunctionKind(context, name_id);
   if (builtin_kind != SemIR::BuiltinFunctionKind::None) {
     auto& function = context.functions().Get(function_id);
+    CheckFunctionDefinitionSignature(context, function);
     if (IsValidBuiltinDeclaration(context, function, builtin_kind)) {
       function.builtin_function_kind = builtin_kind;
       // Build an empty generic definition if this is a generic builtin.

+ 258 - 0
toolchain/check/testdata/function/builtin/no_prelude/adapted_type.carbon

@@ -0,0 +1,258 @@
+// 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/check/testdata/function/builtin/no_prelude/adapted_type.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/builtin/no_prelude/adapted_type.carbon
+
+// --- adapt.carbon
+
+library "[[@TEST_NAME]]";
+
+fn IntLiteral() -> type = "int_literal.make_type";
+
+class MyIntLiteral {
+  adapt IntLiteral();
+}
+
+fn Int(N: MyIntLiteral) -> type = "int.make_type_signed";
+
+class MyInt32 {
+  adapt Int(32 as MyIntLiteral);
+
+  fn Make(a: MyIntLiteral) -> MyInt32;
+}
+
+fn MyInt32.Make(a: MyIntLiteral) -> MyInt32 = "int.convert_checked";
+
+fn MyAdd(a: MyInt32, b: MyInt32) -> MyInt32 = "int.sadd";
+
+var v: MyInt32 = MyAdd(MyInt32.Make(1 as MyIntLiteral), MyInt32.Make(2 as MyIntLiteral));
+
+// --- fail_bad_adapt.carbon
+
+library "[[@TEST_NAME]]";
+
+class MyIntLiteral {
+  adapt {};
+}
+
+// CHECK:STDERR: fail_bad_adapt.carbon:[[@LINE+3]]:1: error: invalid signature for builtin function "int.make_type_signed" [InvalidBuiltinSignature]
+// CHECK:STDERR: fn Int(N: MyIntLiteral) -> type = "int.make_type_signed";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+fn Int(N: MyIntLiteral) -> type = "int.make_type_signed";
+
+// CHECK:STDOUT: --- adapt.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %IntLiteral.type: type = fn_type @IntLiteral [template]
+// CHECK:STDOUT:   %IntLiteral: %IntLiteral.type = struct_value () [template]
+// CHECK:STDOUT:   %MyIntLiteral: type = class_type @MyIntLiteral [template]
+// CHECK:STDOUT:   %complete_type.1: <witness> = complete_type_witness Core.IntLiteral [template]
+// CHECK:STDOUT:   %Int.type: type = fn_type @Int [template]
+// CHECK:STDOUT:   %Int: %Int.type = struct_value () [template]
+// CHECK:STDOUT:   %MyInt32: type = class_type @MyInt32 [template]
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [template]
+// CHECK:STDOUT:   %i32: type = int_type signed, %int_32 [template]
+// CHECK:STDOUT:   %Make.type: type = fn_type @Make [template]
+// CHECK:STDOUT:   %Make: %Make.type = struct_value () [template]
+// CHECK:STDOUT:   %complete_type.2: <witness> = complete_type_witness %i32 [template]
+// CHECK:STDOUT:   %MyAdd.type: type = fn_type @MyAdd [template]
+// CHECK:STDOUT:   %MyAdd: %MyAdd.type = struct_value () [template]
+// CHECK:STDOUT:   %int_1.1: Core.IntLiteral = int_value 1 [template]
+// CHECK:STDOUT:   %int_1.2: %MyInt32 = int_value 1 [template]
+// CHECK:STDOUT:   %int_2.1: Core.IntLiteral = int_value 2 [template]
+// CHECK:STDOUT:   %int_2.2: %MyInt32 = int_value 2 [template]
+// CHECK:STDOUT:   %int_3: %MyInt32 = int_value 3 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .IntLiteral = %IntLiteral.decl
+// CHECK:STDOUT:     .MyIntLiteral = %MyIntLiteral.decl
+// CHECK:STDOUT:     .Int = %Int.decl
+// CHECK:STDOUT:     .MyInt32 = %MyInt32.decl
+// CHECK:STDOUT:     .MyAdd = %MyAdd.decl
+// CHECK:STDOUT:     .v = %v
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %IntLiteral.decl: %IntLiteral.type = fn_decl @IntLiteral [template = constants.%IntLiteral] {
+// CHECK:STDOUT:     %return.patt: type = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: type = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %return.param: ref type = out_param runtime_param0
+// CHECK:STDOUT:     %return: ref type = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyIntLiteral.decl: type = class_decl @MyIntLiteral [template = constants.%MyIntLiteral] {} {}
+// CHECK:STDOUT:   %Int.decl: %Int.type = fn_decl @Int [template = constants.%Int] {
+// CHECK:STDOUT:     %N.patt: %MyIntLiteral = binding_pattern N
+// CHECK:STDOUT:     %N.param_patt: %MyIntLiteral = value_param_pattern %N.patt, runtime_param0
+// CHECK:STDOUT:     %return.patt: type = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: type = out_param_pattern %return.patt, runtime_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %MyIntLiteral.ref: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral]
+// CHECK:STDOUT:     %N.param: %MyIntLiteral = value_param runtime_param0
+// CHECK:STDOUT:     %N: %MyIntLiteral = bind_name N, %N.param
+// CHECK:STDOUT:     %return.param: ref type = out_param runtime_param1
+// CHECK:STDOUT:     %return: ref type = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyInt32.decl: type = class_decl @MyInt32 [template = constants.%MyInt32] {} {}
+// CHECK:STDOUT:   %Make.decl: %Make.type = fn_decl @Make [template = constants.%Make] {
+// CHECK:STDOUT:     %a.patt: %MyIntLiteral = binding_pattern a
+// CHECK:STDOUT:     %a.param_patt: %MyIntLiteral = value_param_pattern %a.patt, runtime_param0
+// CHECK:STDOUT:     %return.patt: %MyInt32 = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %MyInt32 = out_param_pattern %return.patt, runtime_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %MyIntLiteral.ref.loc18: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral]
+// CHECK:STDOUT:     %MyInt32.ref.loc18: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32]
+// CHECK:STDOUT:     %a.param.loc18: %MyIntLiteral = value_param runtime_param0
+// CHECK:STDOUT:     %a.loc18: %MyIntLiteral = bind_name a, %a.param.loc18
+// CHECK:STDOUT:     %return.param.loc18: ref %MyInt32 = out_param runtime_param1
+// CHECK:STDOUT:     %return.loc18: ref %MyInt32 = return_slot %return.param.loc18
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyAdd.decl: %MyAdd.type = fn_decl @MyAdd [template = constants.%MyAdd] {
+// CHECK:STDOUT:     %a.patt: %MyInt32 = binding_pattern a
+// CHECK:STDOUT:     %a.param_patt: %MyInt32 = value_param_pattern %a.patt, runtime_param0
+// CHECK:STDOUT:     %b.patt: %MyInt32 = binding_pattern b
+// CHECK:STDOUT:     %b.param_patt: %MyInt32 = value_param_pattern %b.patt, runtime_param1
+// CHECK:STDOUT:     %return.patt: %MyInt32 = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %MyInt32 = out_param_pattern %return.patt, runtime_param2
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %MyInt32.ref.loc20_13: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32]
+// CHECK:STDOUT:     %MyInt32.ref.loc20_25: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32]
+// CHECK:STDOUT:     %MyInt32.ref.loc20_37: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32]
+// CHECK:STDOUT:     %a.param: %MyInt32 = value_param runtime_param0
+// CHECK:STDOUT:     %a: %MyInt32 = bind_name a, %a.param
+// CHECK:STDOUT:     %b.param: %MyInt32 = value_param runtime_param1
+// CHECK:STDOUT:     %b: %MyInt32 = bind_name b, %b.param
+// CHECK:STDOUT:     %return.param: ref %MyInt32 = out_param runtime_param2
+// CHECK:STDOUT:     %return: ref %MyInt32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyInt32.ref: type = name_ref MyInt32, %MyInt32.decl [template = constants.%MyInt32]
+// CHECK:STDOUT:   %v.var: ref %MyInt32 = var v
+// CHECK:STDOUT:   %v: ref %MyInt32 = bind_name v, %v.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @MyIntLiteral {
+// CHECK:STDOUT:   %IntLiteral.ref: %IntLiteral.type = name_ref IntLiteral, file.%IntLiteral.decl [template = constants.%IntLiteral]
+// CHECK:STDOUT:   %int_literal.make_type: init type = call %IntLiteral.ref() [template = Core.IntLiteral]
+// CHECK:STDOUT:   %.loc7_21.1: type = value_of_initializer %int_literal.make_type [template = Core.IntLiteral]
+// CHECK:STDOUT:   %.loc7_21.2: type = converted %int_literal.make_type, %.loc7_21.1 [template = Core.IntLiteral]
+// CHECK:STDOUT:   adapt_decl %.loc7_21.2 [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness Core.IntLiteral [template = constants.%complete_type.1]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%MyIntLiteral
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @MyInt32 {
+// CHECK:STDOUT:   %Int.ref: %Int.type = name_ref Int, file.%Int.decl [template = constants.%Int]
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [template = constants.%int_32]
+// CHECK:STDOUT:   %MyIntLiteral.ref: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral]
+// CHECK:STDOUT:   %.loc13_16.1: %MyIntLiteral = as_compatible %int_32 [template = constants.%int_32]
+// CHECK:STDOUT:   %.loc13_16.2: %MyIntLiteral = converted %int_32, %.loc13_16.1 [template = constants.%int_32]
+// CHECK:STDOUT:   %int.make_type_signed: init type = call %Int.ref(%.loc13_16.2) [template = constants.%i32]
+// CHECK:STDOUT:   %.loc13_32.1: type = value_of_initializer %int.make_type_signed [template = constants.%i32]
+// CHECK:STDOUT:   %.loc13_32.2: type = converted %int.make_type_signed, %.loc13_32.1 [template = constants.%i32]
+// CHECK:STDOUT:   adapt_decl %.loc13_32.2 [template]
+// CHECK:STDOUT:   %Make.decl: %Make.type = fn_decl @Make [template = constants.%Make] {
+// CHECK:STDOUT:     %a.patt: %MyIntLiteral = binding_pattern a
+// CHECK:STDOUT:     %a.param_patt: %MyIntLiteral = value_param_pattern %a.patt, runtime_param0
+// CHECK:STDOUT:     %return.patt: %MyInt32 = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %MyInt32 = out_param_pattern %return.patt, runtime_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %MyIntLiteral.ref.loc15: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral]
+// CHECK:STDOUT:     %MyInt32.ref.loc15: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32]
+// CHECK:STDOUT:     %a.param.loc15: %MyIntLiteral = value_param runtime_param0
+// CHECK:STDOUT:     %a.loc15: %MyIntLiteral = bind_name a, %a.param.loc15
+// CHECK:STDOUT:     %return.param.loc15: ref %MyInt32 = out_param runtime_param1
+// CHECK:STDOUT:     %return.loc15: ref %MyInt32 = return_slot %return.param.loc15
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %i32 [template = constants.%complete_type.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%MyInt32
+// CHECK:STDOUT:   .Make = %Make.decl
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @IntLiteral() -> type = "int_literal.make_type";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int(%N.param_patt: %MyIntLiteral) -> type = "int.make_type_signed";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make(%a.param_patt: %MyIntLiteral) -> %MyInt32 = "int.convert_checked";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyAdd(%a.param_patt: %MyInt32, %b.param_patt: %MyInt32) -> %MyInt32 = "int.sadd";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %MyAdd.ref: %MyAdd.type = name_ref MyAdd, file.%MyAdd.decl [template = constants.%MyAdd]
+// CHECK:STDOUT:   %MyInt32.ref.loc22_24: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32]
+// CHECK:STDOUT:   %Make.ref.loc22_31: %Make.type = name_ref Make, @MyInt32.%Make.decl [template = constants.%Make]
+// CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [template = constants.%int_1.1]
+// CHECK:STDOUT:   %MyIntLiteral.ref.loc22_42: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral]
+// CHECK:STDOUT:   %.loc22_39.1: %MyIntLiteral = as_compatible %int_1 [template = constants.%int_1.1]
+// CHECK:STDOUT:   %.loc22_39.2: %MyIntLiteral = converted %int_1, %.loc22_39.1 [template = constants.%int_1.1]
+// CHECK:STDOUT:   %int.convert_checked.loc22_54: init %MyInt32 = call %Make.ref.loc22_31(%.loc22_39.2) [template = constants.%int_1.2]
+// CHECK:STDOUT:   %MyInt32.ref.loc22_57: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32]
+// CHECK:STDOUT:   %Make.ref.loc22_64: %Make.type = name_ref Make, @MyInt32.%Make.decl [template = constants.%Make]
+// CHECK:STDOUT:   %int_2: Core.IntLiteral = int_value 2 [template = constants.%int_2.1]
+// CHECK:STDOUT:   %MyIntLiteral.ref.loc22_75: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral]
+// CHECK:STDOUT:   %.loc22_72.1: %MyIntLiteral = as_compatible %int_2 [template = constants.%int_2.1]
+// CHECK:STDOUT:   %.loc22_72.2: %MyIntLiteral = converted %int_2, %.loc22_72.1 [template = constants.%int_2.1]
+// CHECK:STDOUT:   %int.convert_checked.loc22_87: init %MyInt32 = call %Make.ref.loc22_64(%.loc22_72.2) [template = constants.%int_2.2]
+// CHECK:STDOUT:   %.loc22_54.1: %MyInt32 = value_of_initializer %int.convert_checked.loc22_54 [template = constants.%int_1.2]
+// CHECK:STDOUT:   %.loc22_54.2: %MyInt32 = converted %int.convert_checked.loc22_54, %.loc22_54.1 [template = constants.%int_1.2]
+// CHECK:STDOUT:   %.loc22_87.1: %MyInt32 = value_of_initializer %int.convert_checked.loc22_87 [template = constants.%int_2.2]
+// CHECK:STDOUT:   %.loc22_87.2: %MyInt32 = converted %int.convert_checked.loc22_87, %.loc22_87.1 [template = constants.%int_2.2]
+// CHECK:STDOUT:   %int.sadd: init %MyInt32 = call %MyAdd.ref(%.loc22_54.2, %.loc22_87.2) [template = constants.%int_3]
+// CHECK:STDOUT:   assign file.%v.var, %int.sadd
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_bad_adapt.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyIntLiteral: type = class_type @MyIntLiteral [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT:   %Int.type: type = fn_type @Int [template]
+// CHECK:STDOUT:   %Int: %Int.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .MyIntLiteral = %MyIntLiteral.decl
+// CHECK:STDOUT:     .Int = %Int.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyIntLiteral.decl: type = class_decl @MyIntLiteral [template = constants.%MyIntLiteral] {} {}
+// CHECK:STDOUT:   %Int.decl: %Int.type = fn_decl @Int [template = constants.%Int] {
+// CHECK:STDOUT:     %N.patt: %MyIntLiteral = binding_pattern N
+// CHECK:STDOUT:     %N.param_patt: %MyIntLiteral = value_param_pattern %N.patt, runtime_param0
+// CHECK:STDOUT:     %return.patt: type = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: type = out_param_pattern %return.patt, runtime_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %MyIntLiteral.ref: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral]
+// CHECK:STDOUT:     %N.param: %MyIntLiteral = value_param runtime_param0
+// CHECK:STDOUT:     %N: %MyIntLiteral = bind_name N, %N.param
+// CHECK:STDOUT:     %return.param: ref type = out_param runtime_param1
+// CHECK:STDOUT:     %return: ref type = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @MyIntLiteral {
+// CHECK:STDOUT:   %.loc5_10: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc5_11: type = converted %.loc5_10, constants.%empty_struct_type [template = constants.%empty_struct_type]
+// CHECK:STDOUT:   adapt_decl %.loc5_11 [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%MyIntLiteral
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int(%N.param_patt: %MyIntLiteral) -> type;
+// CHECK:STDOUT:

+ 27 - 3
toolchain/sem_ir/builtin_function_kind.cpp

@@ -45,8 +45,11 @@ struct TypeParam {
     if (state.type_params[I].is_valid() && type_id != state.type_params[I]) {
       return false;
     }
+    if (!TypeConstraint::Check(sem_ir, state, type_id)) {
+      return false;
+    }
     state.type_params[I] = type_id;
-    return TypeConstraint::Check(sem_ir, state, type_id);
+    return true;
   }
 };
 
@@ -99,6 +102,27 @@ struct AnyFloat {
   }
 };
 
+// Checks that the specified type matches the given type constraint.
+template <typename TypeConstraint>
+auto Check(const File& sem_ir, ValidateState& state, TypeId type_id) -> bool {
+  while (type_id.is_valid()) {
+    // Allow a type that satisfies the constraint.
+    if (TypeConstraint::Check(sem_ir, state, type_id)) {
+      return true;
+    }
+
+    // Also allow a class type that adapts a matching type.
+    auto class_type = sem_ir.types().TryGetAs<ClassType>(type_id);
+    if (!class_type) {
+      break;
+    }
+    type_id = sem_ir.classes()
+                  .Get(class_type->class_id)
+                  .GetAdaptedType(sem_ir, class_type->specific_id);
+  }
+  return false;
+}
+
 // Constraint that requires the type to be the type type.
 using Type = BuiltinType<TypeType::SingletonInstId>;
 
@@ -136,7 +160,7 @@ static auto ValidateSignature(const File& sem_ir,
 
   // Argument types must match.
   if (![&]<size_t... Indexes>(std::index_sequence<Indexes...>) {
-        return ((SignatureTraits::template arg_t<Indexes>::Check(
+        return ((Check<typename SignatureTraits::template arg_t<Indexes>>(
                     sem_ir, state, arg_types[Indexes])) &&
                 ...);
       }(std::make_index_sequence<SignatureTraits::num_args>())) {
@@ -144,7 +168,7 @@ static auto ValidateSignature(const File& sem_ir,
   }
 
   // Result type must match.
-  if (!SignatureTraits::result_t::Check(sem_ir, state, return_type)) {
+  if (!Check<typename SignatureTraits::result_t>(sem_ir, state, return_type)) {
     return false;
   }