Selaa lähdekoodia

Add `MaybeUnformed(T)` type. (#5989)

This type has the same object representation as `T`, but always uses a
pointer type as its value representation. No other semantics are
provided for it yet.
Richard Smith 8 kuukautta sitten
vanhempi
sitoutus
0e6dd7e701

+ 1 - 0
core/prelude/types.carbon

@@ -10,6 +10,7 @@ export import library "prelude/types/float";
 export import library "prelude/types/float_literal";
 export import library "prelude/types/int";
 export import library "prelude/types/int_literal";
+export import library "prelude/types/maybe_unformed";
 export import library "prelude/types/optional";
 export import library "prelude/types/string";
 export import library "prelude/types/uint";

+ 13 - 0
core/prelude/types/maybe_unformed.carbon

@@ -0,0 +1,13 @@
+// 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
+
+package Core library "prelude/types/maybe_unformed";
+
+import library "prelude/destroy";
+
+private fn MakeMaybeUnformed(t: type) -> type = "maybe_unformed.make_type";
+
+class MaybeUnformed(T:! type) {
+  adapt MakeMaybeUnformed(T);
+}

+ 9 - 0
toolchain/check/eval.cpp

@@ -1758,6 +1758,15 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
       return context.constant_values().Get(SemIR::BoolType::TypeInstId);
     }
 
+    case SemIR::BuiltinFunctionKind::MaybeUnformedMakeType: {
+      return MakeConstantResult(
+          context,
+          SemIR::MaybeUnformedType{
+              .type_id = SemIR::TypeType::TypeId,
+              .inner_id = context.types().GetAsTypeInstId(arg_ids[0])},
+          phase);
+    }
+
     // Character conversions.
     case SemIR::BuiltinFunctionKind::CharConvertChecked: {
       if (phase != Phase::Concrete) {

+ 127 - 0
toolchain/check/testdata/builtins/maybe_unformed/make_type.carbon

@@ -0,0 +1,127 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtins/maybe_unformed/make_type.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtins/maybe_unformed/make_type.carbon
+
+// --- types.carbon
+
+library "[[@TEST_NAME]]";
+
+fn Make(t: type) -> type = "maybe_unformed.make_type";
+
+// --- use_types.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "types";
+
+//@dump-sem-ir-begin
+var b: Make(Make({}));
+//@dump-sem-ir-end
+
+// --- runtime_call.carbon
+
+library "[[@TEST_NAME]]";
+
+import library "types";
+
+//@dump-sem-ir-begin
+let t: type = {};
+let u: type = Make(t);
+//@dump-sem-ir-end
+
+// --- fail_bad_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+4]]:1: error: invalid signature for builtin function "maybe_unformed.make_type" [InvalidBuiltinSignature]
+// CHECK:STDERR: fn NoParam() -> type = "maybe_unformed.make_type";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn NoParam() -> type = "maybe_unformed.make_type";
+
+// CHECK:STDERR: fail_bad_decl.carbon:[[@LINE+4]]:1: error: invalid signature for builtin function "maybe_unformed.make_type" [InvalidBuiltinSignature]
+// CHECK:STDERR: fn ColonBangParam(T:! type) -> type = "maybe_unformed.make_type";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn ColonBangParam(T:! type) -> type = "maybe_unformed.make_type";
+
+// CHECK:STDOUT: --- use_types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Make.type: type = fn_type @Make [concrete]
+// CHECK:STDOUT:   %Make: %Make.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %.a8d: type = maybe_unformed_type %empty_struct_type [concrete]
+// CHECK:STDOUT:   %.2ba: type = maybe_unformed_type %.a8d [concrete]
+// CHECK:STDOUT:   %pattern_type.ce6: type = pattern_type %.2ba [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.Make: %Make.type = import_ref Main//types, Make, loaded [concrete = constants.%Make]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.ce6 = binding_pattern b [concrete]
+// CHECK:STDOUT:     %b.var_patt: %pattern_type.ce6 = var_pattern %b.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b.var: ref %.2ba = var %b.var_patt [concrete]
+// CHECK:STDOUT:   %.loc7_21.1: type = splice_block %.loc7_21.3 [concrete = constants.%.2ba] {
+// CHECK:STDOUT:     %Make.ref.loc7_8: %Make.type = name_ref Make, imports.%Main.Make [concrete = constants.%Make]
+// CHECK:STDOUT:     %Make.ref.loc7_13: %Make.type = name_ref Make, imports.%Main.Make [concrete = constants.%Make]
+// CHECK:STDOUT:     %.loc7_19.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:     %.loc7_19.2: type = converted %.loc7_19.1, constants.%empty_struct_type [concrete = constants.%empty_struct_type]
+// CHECK:STDOUT:     %Make.call.loc7_20: init type = call %Make.ref.loc7_13(%.loc7_19.2) [concrete = constants.%.a8d]
+// CHECK:STDOUT:     %.loc7_20.1: type = value_of_initializer %Make.call.loc7_20 [concrete = constants.%.a8d]
+// CHECK:STDOUT:     %.loc7_20.2: type = converted %Make.call.loc7_20, %.loc7_20.1 [concrete = constants.%.a8d]
+// CHECK:STDOUT:     %Make.call.loc7_21: init type = call %Make.ref.loc7_8(%.loc7_20.2) [concrete = constants.%.2ba]
+// CHECK:STDOUT:     %.loc7_21.2: type = value_of_initializer %Make.call.loc7_21 [concrete = constants.%.2ba]
+// CHECK:STDOUT:     %.loc7_21.3: type = converted %Make.call.loc7_21, %.loc7_21.2 [concrete = constants.%.2ba]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: ref %.2ba = bind_name b, %b.var [concrete = %b.var]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- runtime_call.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %pattern_type: type = pattern_type type [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %Make.type: type = fn_type @Make [concrete]
+// CHECK:STDOUT:   %Make: %Make.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.Make: %Make.type = import_ref Main//types, Make, loaded [concrete = constants.%Make]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %t.patt: %pattern_type = binding_pattern t [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc7: type = converted @__global_init.%.loc7, constants.%empty_struct_type [concrete = constants.%empty_struct_type]
+// CHECK:STDOUT:   %t: type = bind_name t, %.loc7
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %u.patt: %pattern_type = binding_pattern u [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc8_21.1: type = value_of_initializer @__global_init.%Make.call
+// CHECK:STDOUT:   %.loc8_21.2: type = converted @__global_init.%Make.call, %.loc8_21.1
+// CHECK:STDOUT:   %u: type = bind_name u, %.loc8_21.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %Make.ref: %Make.type = name_ref Make, imports.%Main.Make [concrete = constants.%Make]
+// CHECK:STDOUT:   %t.ref: type = name_ref t, file.%t
+// CHECK:STDOUT:   %Make.call: init type = call %Make.ref(%t.ref)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 12 - 12
toolchain/check/testdata/for/actual.carbon

@@ -220,28 +220,28 @@ fn Read() {
 // CHECK:STDOUT:   %Core.import_ref.1c9: %Iterate.assoc_type = import_ref Core//prelude/iterate, loc12_18, loaded [concrete = constants.%assoc0.724]
 // CHECK:STDOUT:   %Core.import_ref.ed6: %Iterate.assoc_type = import_ref Core//prelude/iterate, loc13_17, loaded [concrete = constants.%assoc1.02e]
 // CHECK:STDOUT:   %Core.import_ref.9e6: type = import_ref Core//prelude/iterate, loc13_17, loaded [concrete = %CursorType]
-// CHECK:STDOUT:   %Core.import_ref.f49: @Optional.%Optional.None.type (%Optional.None.type.ef2) = import_ref Core//prelude/iterate, inst139 [indirect], loaded [symbolic = @Optional.%Optional.None (constants.%Optional.None.fd6)]
-// CHECK:STDOUT:   %Core.import_ref.1a8: @Optional.%Optional.Some.type (%Optional.Some.type.b2c) = import_ref Core//prelude/iterate, inst140 [indirect], loaded [symbolic = @Optional.%Optional.Some (constants.%Optional.Some.d0d)]
-// CHECK:STDOUT:   %Core.import_ref.36a9: @Optional.as.Destroy.impl.%Optional.as.Destroy.impl.Op.type (%Optional.as.Destroy.impl.Op.type.764) = import_ref Core//prelude/iterate, inst6890 [indirect], loaded [symbolic = @Optional.as.Destroy.impl.%Optional.as.Destroy.impl.Op (constants.%Optional.as.Destroy.impl.Op.bf8)]
+// CHECK:STDOUT:   %Core.import_ref.f49: @Optional.%Optional.None.type (%Optional.None.type.ef2) = import_ref Core//prelude/iterate, inst140 [indirect], loaded [symbolic = @Optional.%Optional.None (constants.%Optional.None.fd6)]
+// CHECK:STDOUT:   %Core.import_ref.1a8: @Optional.%Optional.Some.type (%Optional.Some.type.b2c) = import_ref Core//prelude/iterate, inst141 [indirect], loaded [symbolic = @Optional.%Optional.Some (constants.%Optional.Some.d0d)]
+// CHECK:STDOUT:   %Core.import_ref.36a9: @Optional.as.Destroy.impl.%Optional.as.Destroy.impl.Op.type (%Optional.as.Destroy.impl.Op.type.764) = import_ref Core//prelude/iterate, inst6891 [indirect], loaded [symbolic = @Optional.as.Destroy.impl.%Optional.as.Destroy.impl.Op (constants.%Optional.as.Destroy.impl.Op.bf8)]
 // CHECK:STDOUT:   %Destroy.impl_witness_table.2ff = impl_witness_table (%Core.import_ref.36a9), @Optional.as.Destroy.impl [concrete]
-// CHECK:STDOUT:   %Core.import_ref.cf4: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.0f9) = import_ref Core//prelude/iterate, inst483 [indirect], loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.f06)]
+// CHECK:STDOUT:   %Core.import_ref.cf4: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.0f9) = import_ref Core//prelude/iterate, inst484 [indirect], loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.f06)]
 // CHECK:STDOUT:   %ImplicitAs.impl_witness_table.2b9 = impl_witness_table (%Core.import_ref.cf4), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
-// CHECK:STDOUT:   %Core.import_ref.741: @Int.as.Destroy.impl.%Int.as.Destroy.impl.Op.type (%Int.as.Destroy.impl.Op.type) = import_ref Core//prelude/iterate, inst451 [indirect], loaded [symbolic = @Int.as.Destroy.impl.%Int.as.Destroy.impl.Op (constants.%Int.as.Destroy.impl.Op)]
+// CHECK:STDOUT:   %Core.import_ref.741: @Int.as.Destroy.impl.%Int.as.Destroy.impl.Op.type (%Int.as.Destroy.impl.Op.type) = import_ref Core//prelude/iterate, inst452 [indirect], loaded [symbolic = @Int.as.Destroy.impl.%Int.as.Destroy.impl.Op (constants.%Int.as.Destroy.impl.Op)]
 // CHECK:STDOUT:   %Destroy.impl_witness_table.1b4 = impl_witness_table (%Core.import_ref.741), @Int.as.Destroy.impl [concrete]
-// CHECK:STDOUT:   %Core.import_ref.19a: @OrderedWith.%OrderedWith.assoc_type (%OrderedWith.assoc_type.03c) = import_ref Core//prelude/iterate, inst863 [indirect], loaded [symbolic = @OrderedWith.%assoc0 (constants.%assoc0.5db)]
-// CHECK:STDOUT:   %Core.import_ref.b2b: @Int.as.OrderedWith.impl.db3.%Int.as.OrderedWith.impl.Less.type (%Int.as.OrderedWith.impl.Less.type.2c7) = import_ref Core//prelude/iterate, inst952 [indirect], loaded [symbolic = @Int.as.OrderedWith.impl.db3.%Int.as.OrderedWith.impl.Less (constants.%Int.as.OrderedWith.impl.Less.a5a)]
-// CHECK:STDOUT:   %Core.import_ref.ab6 = import_ref Core//prelude/iterate, inst953 [indirect], unloaded
-// CHECK:STDOUT:   %Core.import_ref.875 = import_ref Core//prelude/iterate, inst954 [indirect], unloaded
-// CHECK:STDOUT:   %Core.import_ref.82b = import_ref Core//prelude/iterate, inst955 [indirect], unloaded
+// CHECK:STDOUT:   %Core.import_ref.19a: @OrderedWith.%OrderedWith.assoc_type (%OrderedWith.assoc_type.03c) = import_ref Core//prelude/iterate, inst864 [indirect], loaded [symbolic = @OrderedWith.%assoc0 (constants.%assoc0.5db)]
+// CHECK:STDOUT:   %Core.import_ref.b2b: @Int.as.OrderedWith.impl.db3.%Int.as.OrderedWith.impl.Less.type (%Int.as.OrderedWith.impl.Less.type.2c7) = import_ref Core//prelude/iterate, inst953 [indirect], loaded [symbolic = @Int.as.OrderedWith.impl.db3.%Int.as.OrderedWith.impl.Less (constants.%Int.as.OrderedWith.impl.Less.a5a)]
+// CHECK:STDOUT:   %Core.import_ref.ab6 = import_ref Core//prelude/iterate, inst954 [indirect], unloaded
+// CHECK:STDOUT:   %Core.import_ref.875 = import_ref Core//prelude/iterate, inst955 [indirect], unloaded
+// CHECK:STDOUT:   %Core.import_ref.82b = import_ref Core//prelude/iterate, inst956 [indirect], unloaded
 // CHECK:STDOUT:   %OrderedWith.impl_witness_table.476 = impl_witness_table (%Core.import_ref.b2b, %Core.import_ref.ab6, %Core.import_ref.875, %Core.import_ref.82b), @Int.as.OrderedWith.impl.db3 [concrete]
-// CHECK:STDOUT:   %Core.import_ref.13d: @OrderedWith.%OrderedWith.Less.type (%OrderedWith.Less.type.f19) = import_ref Core//prelude/iterate, inst1927 [indirect], loaded [symbolic = @OrderedWith.%OrderedWith.Less (constants.%OrderedWith.Less.02e)]
+// CHECK:STDOUT:   %Core.import_ref.13d: @OrderedWith.%OrderedWith.Less.type (%OrderedWith.Less.type.f19) = import_ref Core//prelude/iterate, inst1928 [indirect], loaded [symbolic = @OrderedWith.%OrderedWith.Less (constants.%OrderedWith.Less.02e)]
 // CHECK:STDOUT:   %CursorType: type = assoc_const_decl @CursorType [concrete] {}
 // CHECK:STDOUT:   %Core.import_ref.4f9: type = import_ref Core//prelude/iterate, loc12_18, loaded [concrete = %ElementType]
 // CHECK:STDOUT:   %ElementType: type = assoc_const_decl @ElementType [concrete] {}
 // CHECK:STDOUT:   %Core.Optional: %Optional.type = import_ref Core//prelude/types/optional, Optional, loaded [concrete = constants.%Optional.generic]
 // CHECK:STDOUT:   %Core.Destroy: type = import_ref Core//prelude/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
 // CHECK:STDOUT:   %Core.OrderedWith: %OrderedWith.type.270 = import_ref Core//prelude/operators/comparison, OrderedWith, loaded [concrete = constants.%OrderedWith.generic]
-// CHECK:STDOUT:   %Core.import_ref.d49 = import_ref Core//prelude/iterate, inst6645 [indirect], unloaded
+// CHECK:STDOUT:   %Core.import_ref.d49 = import_ref Core//prelude/iterate, inst6646 [indirect], unloaded
 // CHECK:STDOUT:   %Core.Inc: type = import_ref Core//prelude/operators/arithmetic, Inc, loaded [concrete = constants.%Inc.type]
 // CHECK:STDOUT:   %Core.ImplicitAs: %ImplicitAs.type.cc7 = import_ref Core//prelude/operators/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic]
 // CHECK:STDOUT: }

+ 16 - 0
toolchain/check/type_completion.cpp

@@ -142,6 +142,10 @@ class TypeCompleter {
                         SemIR::CustomLayoutType inst) const
       -> SemIR::CompleteTypeInfo;
 
+  auto BuildInfoForInst(SemIR::TypeId /*type_id*/,
+                        SemIR::MaybeUnformedType inst) const
+      -> SemIR::CompleteTypeInfo;
+
   auto BuildInfoForInst(SemIR::TypeId /*type_id*/,
                         SemIR::PartialType inst) const
       -> SemIR::CompleteTypeInfo;
@@ -314,6 +318,10 @@ auto TypeCompleter::AddNestedIncompleteTypes(SemIR::Inst type_inst) -> bool {
       }
       break;
     }
+    case CARBON_KIND(SemIR::MaybeUnformedType inst): {
+      Push(context_->types().GetTypeIdForTypeInstId(inst.inner_id));
+      break;
+    }
     case CARBON_KIND(SemIR::PartialType inst): {
       Push(context_->types().GetTypeIdForTypeInstId(inst.inner_id));
       break;
@@ -539,6 +547,14 @@ auto TypeCompleter::BuildInfoForInst(SemIR::TypeId type_id,
   return {.value_repr = MakePointerValueRepr(type_id)};
 }
 
+auto TypeCompleter::BuildInfoForInst(SemIR::TypeId type_id,
+                                     SemIR::MaybeUnformedType /*inst*/) const
+    -> SemIR::CompleteTypeInfo {
+  // `MaybeUnformed(T)` always has a pointer value representation, regardless of
+  // `T`'s value representation.
+  return {.value_repr = MakePointerValueRepr(type_id)};
+}
+
 auto TypeCompleter::BuildInfoForInst(SemIR::TypeId /*type_id*/,
                                      SemIR::PartialType inst) const
     -> SemIR::CompleteTypeInfo {

+ 4 - 8
toolchain/lower/file_context.cpp

@@ -763,8 +763,10 @@ static auto BuildTypeForInst(FileContext& context, SemIR::ClassType inst)
   return context.GetType(object_repr_id);
 }
 
-static auto BuildTypeForInst(FileContext& context, SemIR::ConstType inst)
-    -> llvm::Type* {
+template <typename InstT>
+  requires(InstT::Kind.template IsAnyOf<
+           SemIR::ConstType, SemIR::MaybeUnformedType, SemIR::PartialType>())
+static auto BuildTypeForInst(FileContext& context, InstT inst) -> llvm::Type* {
   return context.GetType(
       context.sem_ir().types().GetTypeIdForTypeInstId(inst.inner_id));
 }
@@ -776,12 +778,6 @@ static auto BuildTypeForInst(FileContext& context, SemIR::CustomLayoutType inst)
                               layout[SemIR::CustomLayoutId::SizeIndex]);
 }
 
-static auto BuildTypeForInst(FileContext& context, SemIR::PartialType inst)
-    -> llvm::Type* {
-  return context.GetType(
-      context.sem_ir().types().GetTypeIdForTypeInstId(inst.inner_id));
-}
-
 static auto BuildTypeForInst(FileContext& context,
                              SemIR::ImplWitnessAssociatedConstant inst)
     -> llvm::Type* {

+ 1 - 0
toolchain/lower/handle_call.cpp

@@ -324,6 +324,7 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
     case SemIR::BuiltinFunctionKind::IntLiteralMakeType:
     case SemIR::BuiltinFunctionKind::IntMakeTypeSigned:
     case SemIR::BuiltinFunctionKind::IntMakeTypeUnsigned:
+    case SemIR::BuiltinFunctionKind::MaybeUnformedMakeType:
       context.SetLocal(inst_id, context.GetTypeAsValue());
       return;
 

+ 100 - 0
toolchain/lower/testdata/builtins/maybe_unformed.carbon

@@ -0,0 +1,100 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/builtins/maybe_unformed.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/builtins/maybe_unformed.carbon
+
+fn MaybeUnformedBuiltin(t: type) -> type = "maybe_unformed.make_type";
+
+fn TakeBuiltin(x: MaybeUnformedBuiltin(i32)) {}
+fn MakeBuiltin() -> MaybeUnformedBuiltin(i32);
+
+fn PassBuiltin() {
+  // This is passed by pointer even though `i32` would generally be passed in
+  // registers.
+  TakeBuiltin(MakeBuiltin());
+}
+
+// Also test that an adapter wrapped around the builtin behaves the same.
+
+class MaybeUnformedAdapter(T:! type) {
+  adapt MaybeUnformedBuiltin(T);
+}
+
+fn TakeAdapter(x: MaybeUnformedAdapter(i32)) {}
+fn MakeAdapter() -> MaybeUnformedAdapter(i32);
+
+fn PassAdapter() {
+  TakeAdapter(MakeAdapter());
+}
+
+// CHECK:STDOUT: ; ModuleID = 'maybe_unformed.carbon'
+// CHECK:STDOUT: source_filename = "maybe_unformed.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CTakeBuiltin.Main(ptr %x) !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !7
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CMakeBuiltin.Main(ptr sret(i32))
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CPassBuiltin.Main() !dbg !8 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc21_27.1.temp = alloca i32, align 4, !dbg !9
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc21_27.1.temp), !dbg !9
+// CHECK:STDOUT:   call void @_CMakeBuiltin.Main(ptr %.loc21_27.1.temp), !dbg !9
+// CHECK:STDOUT:   call void @_CTakeBuiltin.Main(ptr %.loc21_27.1.temp), !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CTakeAdapter.Main(ptr %x) !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CMakeAdapter.Main(ptr sret(i32))
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CPassAdapter.Main() !dbg !14 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc34_27.1.temp = alloca i32, align 4, !dbg !15
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc34_27.1.temp), !dbg !15
+// CHECK:STDOUT:   call void @_CMakeAdapter.Main(ptr %.loc34_27.1.temp), !dbg !15
+// CHECK:STDOUT:   call void @_CTakeAdapter.Main(ptr %.loc34_27.1.temp), !dbg !16
+// CHECK:STDOUT:   ret void, !dbg !17
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "maybe_unformed.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "TakeBuiltin", linkageName: "_CTakeBuiltin.Main", scope: null, file: !3, line: 15, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 15, column: 1, scope: !4)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "PassBuiltin", linkageName: "_CPassBuiltin.Main", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !9 = !DILocation(line: 21, column: 15, scope: !8)
+// CHECK:STDOUT: !10 = !DILocation(line: 21, column: 3, scope: !8)
+// CHECK:STDOUT: !11 = !DILocation(line: 18, column: 1, scope: !8)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "TakeAdapter", linkageName: "_CTakeAdapter.Main", scope: null, file: !3, line: 30, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !13 = !DILocation(line: 30, column: 1, scope: !12)
+// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "PassAdapter", linkageName: "_CPassAdapter.Main", scope: null, file: !3, line: 33, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !15 = !DILocation(line: 34, column: 15, scope: !14)
+// CHECK:STDOUT: !16 = !DILocation(line: 34, column: 3, scope: !14)
+// CHECK:STDOUT: !17 = !DILocation(line: 33, column: 1, scope: !14)

+ 4 - 0
toolchain/sem_ir/builtin_function_kind.cpp

@@ -307,6 +307,10 @@ constexpr BuiltinInfo FloatMakeType = {"float.make_type",
 constexpr BuiltinInfo BoolMakeType = {"bool.make_type",
                                       ValidateSignature<auto()->Type>};
 
+// Returns the `MaybeUnformed(T)` type.
+constexpr BuiltinInfo MaybeUnformedMakeType = {
+    "maybe_unformed.make_type", ValidateSignature<auto(Type)->Type>};
+
 // Converts between char types, with a diagnostic if the value doesn't fit.
 constexpr BuiltinInfo CharConvertChecked = {
     "char.convert_checked",

+ 1 - 0
toolchain/sem_ir/builtin_function_kind.def

@@ -35,6 +35,7 @@ CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMakeTypeSigned)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMakeTypeUnsigned)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(FloatMakeType)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(BoolMakeType)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(MaybeUnformedMakeType)
 
 // Character conversion.
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(CharConvertChecked)

+ 1 - 0
toolchain/sem_ir/expr_info.cpp

@@ -138,6 +138,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case IntType::Kind:
       case IntValue::Kind:
       case InterfaceDecl::Kind:
+      case MaybeUnformedType::Kind:
       case NamespaceType::Kind:
       case PartialType::Kind:
       case PatternType::Kind:

+ 2 - 1
toolchain/sem_ir/inst_kind.def

@@ -75,7 +75,6 @@ CARBON_SEM_IR_INST_KIND(FunctionTypeWithSelfType)
 CARBON_SEM_IR_INST_KIND(GenericClassType)
 CARBON_SEM_IR_INST_KIND(GenericInterfaceType)
 CARBON_SEM_IR_INST_KIND(ImplDecl)
-CARBON_SEM_IR_INST_KIND(LookupImplWitness)
 CARBON_SEM_IR_INST_KIND(ImplWitness)
 CARBON_SEM_IR_INST_KIND(ImplWitnessAccess)
 CARBON_SEM_IR_INST_KIND(ImplWitnessAccessSubstituted)
@@ -94,6 +93,8 @@ CARBON_SEM_IR_INST_KIND(IntLiteralType)
 CARBON_SEM_IR_INST_KIND(IntType)
 CARBON_SEM_IR_INST_KIND(IntValue)
 CARBON_SEM_IR_INST_KIND(InterfaceDecl)
+CARBON_SEM_IR_INST_KIND(LookupImplWitness)
+CARBON_SEM_IR_INST_KIND(MaybeUnformedType)
 CARBON_SEM_IR_INST_KIND(NameBindingDecl)
 CARBON_SEM_IR_INST_KIND(NameRef)
 CARBON_SEM_IR_INST_KIND(Namespace)

+ 4 - 0
toolchain/sem_ir/stringify.cpp

@@ -521,6 +521,10 @@ class Stringifier {
         sem_ir_->specific_interfaces().Get(inst.query_specific_interface_id));
   }
 
+  auto StringifyInst(InstId /*inst_id*/, MaybeUnformedType inst) -> void {
+    step_stack_->Push("<builtin MaybeUnformed(", inst.inner_id, ")>");
+  }
+
   auto StringifyInst(InstId /*inst_id*/, NameRef inst) -> void {
     *out_ << sem_ir_->names().GetFormatted(inst.name_id);
   }

+ 8 - 0
toolchain/sem_ir/type_iterator.cpp

@@ -127,6 +127,12 @@ auto TypeIterator::Next() -> Step {
         PushInstId(const_type.inner_id);
         break;
       }
+      case CARBON_KIND(SemIR::MaybeUnformedType partial_type): {
+        // We don't stop at `MaybeUnformed` since it is a modifier; just move to
+        // the inner type.
+        PushInstId(partial_type.inner_id);
+        break;
+      }
       case CARBON_KIND(SemIR::PartialType partial_type): {
         // We don't stop at `partial` since it is a modifier; just move to the
         // inner type.
@@ -171,6 +177,8 @@ auto TypeIterator::Next() -> Step {
         return Step::Error();
 
       default:
+        // TODO: Rearrange this so that missing instruction kinds are detected
+        // at compile-time not runtime.
         CARBON_FATAL("Unhandled type instruction {0}", inst_id);
     }
   }

+ 15 - 0
toolchain/sem_ir/typed_insts.h

@@ -1162,6 +1162,21 @@ struct LookupImplWitness {
   SpecificInterfaceId query_specific_interface_id;
 };
 
+// A type that holds an object representation of another type, that may or may
+// not be a valid representation. In particular, it may also hold an unformed
+// state.
+struct MaybeUnformedType {
+  static constexpr auto Kind =
+      InstKind::MaybeUnformedType.Define<Parse::NodeId>({
+          .ir_name = "maybe_unformed_type",
+          .is_type = InstIsType::Always,
+          .constant_kind = InstConstantKind::WheneverPossible,
+      });
+
+  TypeId type_id;
+  TypeInstId inner_id;
+};
+
 // A name-binding declaration, i.e. a declaration introduced with `let` or
 // `var`.
 struct NameBindingDecl {