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

Support passing Carbon `Optional(T*)` to C++ `T*` parameter. (#6422)

We already did this translation in the other direction, but we had no
mapping from `Optional(T)` to anything, so round-tripping a nullable
pointer from C++ through Carbon and back to C++ was previously rejected.
Richard Smith 5 месяцев назад
Родитель
Сommit
ec8c999bb1

+ 2 - 3
examples/interop/cpp/hello_world.carbon

@@ -27,10 +27,9 @@ fn HelloPutchar() {
 
 // Demonstrate passing a null-terminated string to a C++ function.
 fn HelloStdio() {
-  // TODO: Requires mapping from Optional(const char*) into C++.
   // TODO: There should be a better way to interact with functions that expect a
   // null-terminated string.
-  // Cpp.puts(Cpp.std.data("Hello world!\n\0"));
+  Cpp.puts(Cpp.std.data("Hello world!\0"));
 
   // TODO: Requires variadic function support.
   // Cpp.printf("Hello world!\n\0");
@@ -38,7 +37,7 @@ fn HelloStdio() {
 
 // Demonstrate passing a string as a void pointer to a C++ function.
 fn HelloWrite() {
-  // TODO: Requires mapping from Optional(const char*) into a C++ const void*.
+  // TODO: Requires conversion from `const char*` to `const void*`.
   // let s: str = "Hello world!\n";
   // Cpp.write(1, Cpp.std.data(s), Cpp.std.size(s));
 }

+ 111 - 56
toolchain/check/cpp/type_mapping.cpp

@@ -28,6 +28,25 @@
 
 namespace Carbon::Check {
 
+// A function that wraps a C++ type to form another C++ type. Note that this is
+// a raw function pointer; we don't currently use any lambda captures here. This
+// can be replaced by a `std::function` if captures are found to be needed.
+using WrapFn = auto (*)(Context& context, clang::QualType inner_type)
+    -> clang::QualType;
+
+// Represents a type that requires a subtype to be mapped into a Clang type
+// before it can be mapped.
+struct WrappedType {
+  // The type contained in this wrapped type.
+  SemIR::TypeId inner_type_id;
+  // A function to construct the wrapped type from the mapped unwrapped type.
+  WrapFn wrap_fn;
+};
+
+// Possible results from attempting to map a type. A null QualType indicates
+// that the type couldn't be mapped.
+using TryMapTypeResult = std::variant<clang::QualType, WrappedType>;
+
 // Find the bit width of an integer literal. Following the C++ standard rules
 // for assigning a type to a decimal integer literal, the first signed integer
 // in which the value could fit among bit widths of 32, 64 and 128 is selected.
@@ -44,9 +63,12 @@ static auto FindIntLiteralBitWidth(Context& context, SemIR::InstId arg_id)
     // TODO: Add tests for these cases.
     return IntId::None;
   }
-  auto arg = context.insts().GetAs<SemIR::IntValue>(
+  auto arg = context.insts().TryGetAs<SemIR::IntValue>(
       context.constant_values().GetInstId(arg_const_id));
-  llvm::APInt arg_val = context.ints().Get(arg.int_id);
+  if (!arg) {
+    return IntId::None;
+  }
+  llvm::APInt arg_val = context.ints().Get(arg->int_id);
   int arg_non_sign_bits = arg_val.getSignificantBits() - 1;
 
   if (arg_non_sign_bits >= 128) {
@@ -55,7 +77,7 @@ static auto FindIntLiteralBitWidth(Context& context, SemIR::InstId arg_id)
                       "integer type; requires {1} bits, but max is 128",
                       TypedInt, int);
     context.emitter().Emit(arg_id, IntTooLargeForCppType,
-                           {.type = arg.type_id, .value = arg_val},
+                           {.type = arg->type_id, .value = arg_val},
                            arg_non_sign_bits + 1);
     return IntId::None;
   }
@@ -103,7 +125,7 @@ static auto LookupCppType(
 // Maps a Carbon class type to a C++ type. Returns a null `QualType` if the
 // type is not supported.
 static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
-    -> clang::QualType {
+    -> TryMapTypeResult {
   clang::ASTContext& ast_context = context.ast_context();
 
   // If the class was imported from C++, return the original C++ type.
@@ -126,10 +148,10 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
       break;
     }
     case SemIR::RecognizedTypeInfo::Numeric: {
-      // Carbon supports large bit width beyond C++ builtins; we don't need to
-      // translate those.
+      // Carbon supports large bit width beyond C++ builtins; we don't translate
+      // those into integer types.
       if (!type_info.numeric.bit_width_id.is_embedded_value()) {
-        return clang::QualType();
+        break;
       }
       int bit_width = type_info.numeric.bit_width_id.AsValue();
 
@@ -164,6 +186,25 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
     case SemIR::RecognizedTypeInfo::CppVoidBase: {
       return ast_context.VoidTy;
     }
+    case SemIR::RecognizedTypeInfo::Optional: {
+      auto args = context.inst_blocks().GetOrEmpty(type_info.args_id);
+      if (args.size() == 1) {
+        auto arg_id = args[0];
+        if (auto facet = context.insts().TryGetAs<SemIR::FacetValue>(arg_id)) {
+          arg_id = facet->type_inst_id;
+        }
+        if (auto pointer_type =
+                context.insts().TryGetAs<SemIR::PointerType>(arg_id)) {
+          return WrappedType{
+              .inner_type_id = context.types().GetTypeIdForTypeInstId(
+                  pointer_type->pointee_id),
+              .wrap_fn = [](Context& context, clang::QualType inner_type) {
+                return context.ast_context().getPointerType(inner_type);
+              }};
+        }
+      }
+      break;
+    }
     case SemIR::RecognizedTypeInfo::Str: {
       return LookupCppType(context, {"std", "string_view"});
     }
@@ -175,12 +216,13 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
   return clang::QualType();
 }
 
-// Maps a non-wrapper (no const or pointer) Carbon type to a C++ type. Returns a
-// null QualType if the type is not supported.
+// Maps a Carbon type to a C++ type. Either returns the mapped type, a null type
+// as a placeholder indicating the type can't be mapped, or a `WrappedType`
+// representing a type that needs more work before it can be mapped.
 // TODO: Have both Carbon -> C++ and C++ -> Carbon mappings in a single place
 // to keep them in sync.
-static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id,
-                              SemIR::TypeId type_id) -> clang::QualType {
+static auto TryMapType(Context& context, SemIR::TypeId type_id)
+    -> TryMapTypeResult {
   auto type_inst = context.types().GetAsInst(type_id);
 
   CARBON_KIND_SWITCH(type_inst) {
@@ -193,67 +235,62 @@ static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id,
     case CARBON_KIND(SemIR::ClassType class_type): {
       return TryMapClassType(context, class_type);
     }
-    case SemIR::IntLiteralType::Kind: {
-      IntId bit_width_id = FindIntLiteralBitWidth(context, inst_id);
-      if (bit_width_id == IntId::None) {
-        return clang::QualType();
-      }
-      return context.ast_context().getIntTypeForBitwidth(bit_width_id.AsValue(),
-                                                         true);
+    case CARBON_KIND(SemIR::ConstType const_type): {
+      return WrappedType{
+          .inner_type_id =
+              context.types().GetTypeIdForTypeInstId(const_type.inner_id),
+          .wrap_fn = [](Context& /*context*/, clang::QualType inner_type) {
+            return inner_type.withConst();
+          }};
     }
     case SemIR::FloatLiteralType::Kind: {
       return context.ast_context().DoubleTy;
     }
+    case CARBON_KIND(SemIR::PointerType pointer_type): {
+      return WrappedType{
+          .inner_type_id =
+              context.types().GetTypeIdForTypeInstId(pointer_type.pointee_id),
+          .wrap_fn = [](Context& context, clang::QualType inner_type) {
+            auto pointer_type =
+                context.ast_context().getPointerType(inner_type);
+            return context.ast_context().getAttributedType(
+                clang::attr::TypeNonNull, pointer_type, pointer_type);
+          }};
+    }
+
     default: {
       return clang::QualType();
     }
   }
+
+  return clang::QualType();
 }
 
-// Maps a Carbon type to a C++ type. Accepts an InstId, representing a value
-// whose type is mapped to a C++ type. Returns `clang::QualType` if the mapping
+// Maps a Carbon type to a C++ type. Returns `clang::QualType` if the mapping
 // succeeds, or `clang::QualType::isNull()` if the type is not supported.
 // TODO: unify this with the C++ to Carbon type mapping function.
-static auto MapToCppType(Context& context, SemIR::InstId inst_id)
+static auto MapToCppType(Context& context, SemIR::TypeId type_id)
     -> clang::QualType {
-  auto type_id = context.insts().Get(inst_id).type_id();
-  llvm::SmallVector<SemIR::TypeId> wrapper_types;
+  llvm::SmallVector<WrapFn> wrap_fns;
   while (true) {
-    SemIR::TypeId orig_type_id = type_id;
-    if (auto const_type = context.types().TryGetAs<SemIR::ConstType>(type_id);
-        const_type) {
-      type_id = context.types().GetTypeIdForTypeInstId(const_type->inner_id);
-    } else if (auto pointer_type =
-                   context.types().TryGetAs<SemIR::PointerType>(type_id);
-               pointer_type) {
-      type_id =
-          context.types().GetTypeIdForTypeInstId(pointer_type->pointee_id);
-    } else {
-      break;
-    }
-    wrapper_types.push_back(orig_type_id);
-  }
-
-  clang::QualType mapped_type = MapNonWrapperType(context, inst_id, type_id);
-  if (mapped_type.isNull()) {
-    return mapped_type;
-  }
+    CARBON_KIND_SWITCH(TryMapType(context, type_id)) {
+      case CARBON_KIND(clang::QualType type): {
+        for (auto wrap_fn : llvm::reverse(wrap_fns)) {
+          if (type.isNull()) {
+            break;
+          }
+          type = wrap_fn(context, type);
+        }
+        return type;
+      }
 
-  for (auto wrapper_type_id : llvm::reverse(wrapper_types)) {
-    if (auto const_type =
-            context.types().TryGetAs<SemIR::ConstType>(wrapper_type_id);
-        const_type) {
-      mapped_type.addConst();
-    } else if (context.types().TryGetAs<SemIR::PointerType>(wrapper_type_id)) {
-      auto pointer_type = context.ast_context().getPointerType(mapped_type);
-      mapped_type = context.ast_context().getAttributedType(
-          clang::attr::TypeNonNull, pointer_type, pointer_type);
-    } else {
-      return clang::QualType();
+      case CARBON_KIND(WrappedType wrapped): {
+        wrap_fns.push_back(wrapped.wrap_fn);
+        type_id = wrapped.inner_type_id;
+        break;
+      }
     }
   }
-
-  return mapped_type;
 }
 
 auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr* {
@@ -296,7 +333,25 @@ auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr* {
     return nullptr;
   }
 
-  clang::QualType arg_cpp_type = MapToCppType(context, arg_id);
+  clang::QualType arg_cpp_type;
+
+  // Special case: if the argument is an integer literal, look at its value.
+  // TODO: Consider producing a `clang::IntegerLiteral` in this case instead, so
+  // that C++ overloads that behave differently for zero-valued int literals can
+  // recognize it.
+  auto type_id = context.insts().Get(arg_id).type_id();
+  if (context.types().Is<SemIR::IntLiteralType>(type_id)) {
+    IntId bit_width_id = FindIntLiteralBitWidth(context, arg_id);
+    if (bit_width_id != IntId::None) {
+      arg_cpp_type = context.ast_context().getIntTypeForBitwidth(
+          bit_width_id.AsValue(), true);
+    }
+  }
+
+  if (arg_cpp_type.isNull()) {
+    arg_cpp_type = MapToCppType(context, type_id);
+  }
+
   if (arg_cpp_type.isNull()) {
     CARBON_DIAGNOSTIC(CppCallArgTypeNotSupported, Error,
                       "call argument of type {0} is not supported",

+ 273 - 13
toolchain/check/testdata/interop/cpp/function/pointer.carbon

@@ -296,7 +296,9 @@ struct S {};
 
 auto foo(S*) -> void;
 
-// --- import_nullable_pointer_param.carbon
+auto get() -> S*;
+
+// --- non_nullable_pointer_arg_to_pointer_param.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -309,7 +311,7 @@ fn F() {
   //@dump-sem-ir-end
 }
 
-// --- fail_todo_import_null_pointer_param.carbon
+// --- null_pointer_arg_to_pointer_param.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -317,14 +319,35 @@ import Cpp library "nullable_pointer_param.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_null_pointer_param.carbon:[[@LINE+4]]:11: error: call argument of type `Core.Optional(Cpp.S* as Core.OptionalStorage)` is not supported [CppCallArgTypeNotSupported]
-  // CHECK:STDERR:   Cpp.foo(Core.Optional(Cpp.S*).None());
-  // CHECK:STDERR:           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
   Cpp.foo(Core.Optional(Cpp.S*).None());
   //@dump-sem-ir-end
 }
 
+// --- nonnull_pointer_arg_to_pointer_param.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "nullable_pointer_param.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  var s: Cpp.S = {};
+  Cpp.foo(Core.Optional(Cpp.S*).Some(&s));
+  //@dump-sem-ir-end
+}
+
+// --- forward_nullable_pointer.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "nullable_pointer_param.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  Cpp.foo(Cpp.get());
+  //@dump-sem-ir-end
+}
+
 // ============================================================================
 // Deduced pointer type as template argument
 // ============================================================================
@@ -1281,7 +1304,7 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- import_nullable_pointer_param.carbon
+// CHECK:STDOUT: --- non_nullable_pointer_arg_to_pointer_param.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
@@ -1403,9 +1426,10 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_null_pointer_param.carbon
+// CHECK:STDOUT: --- null_pointer_arg_to_pointer_param.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %foo.cpp_overload_set.type: type = cpp_overload_set_type @foo.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %foo.cpp_overload_set.value: %foo.cpp_overload_set.type = cpp_overload_set_value @foo.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %Optional.type: type = generic_class_type @Optional [concrete]
@@ -1421,17 +1445,25 @@ fn F() {
 // CHECK:STDOUT:   %MaybeUnformed.cff: type = class_type @MaybeUnformed, @MaybeUnformed(%ptr.4f0) [symbolic]
 // CHECK:STDOUT:   %ptr.as.OptionalStorage.impl.None.type.8ed: type = fn_type @ptr.as.OptionalStorage.impl.None, @ptr.as.OptionalStorage.impl(%T.d9f) [symbolic]
 // CHECK:STDOUT:   %ptr.as.OptionalStorage.impl.None.41a: %ptr.as.OptionalStorage.impl.None.type.8ed = struct_value () [symbolic]
+// CHECK:STDOUT:   %type_where: type = facet_type <type where .Self impls <CanDestroy>> [concrete]
+// CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
 // CHECK:STDOUT:   %OptionalStorage.impl_witness.bed: <witness> = impl_witness imports.%OptionalStorage.impl_witness_table.f52, @ptr.as.OptionalStorage.impl(%S) [concrete]
 // CHECK:STDOUT:   %OptionalStorage.facet: %OptionalStorage.type = facet_value %ptr.5c7, (%OptionalStorage.impl_witness.bed) [concrete]
 // CHECK:STDOUT:   %Optional.91c: type = class_type @Optional, @Optional(%OptionalStorage.facet) [concrete]
 // CHECK:STDOUT:   %Optional.None.type.410: type = fn_type @Optional.None, @Optional(%OptionalStorage.facet) [concrete]
 // CHECK:STDOUT:   %Optional.None.d01: %Optional.None.type.410 = struct_value () [concrete]
 // CHECK:STDOUT:   %Optional.None.specific_fn: <specific function> = specific_function %Optional.None.d01, @Optional.None(%OptionalStorage.facet) [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %facet_value.ef0: %type_where = facet_value %Optional.91c, () [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.e5d: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.ef0) [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.6b6: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.e5d = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
 // CHECK:STDOUT:     .Optional = %Core.Optional
+// CHECK:STDOUT:     .Destroy = %Core.Destroy
 // CHECK:STDOUT:     import Core//prelude
 // CHECK:STDOUT:     import Core//prelude/...
 // CHECK:STDOUT:   }
@@ -1450,24 +1482,252 @@ fn F() {
 // CHECK:STDOUT:   %Core.import_ref.6a9 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
 // CHECK:STDOUT:   %Core.import_ref.971 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
 // CHECK:STDOUT:   %OptionalStorage.impl_witness_table.f52 = impl_witness_table (%Core.import_ref.2fb, %Core.import_ref.1d4, %Core.import_ref.720, %Core.import_ref.6a9, %Core.import_ref.971), @ptr.as.OptionalStorage.impl [concrete]
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:     %.loc8_39.1: type = splice_block %Optional [concrete = constants.%Optional.91c] {
+// CHECK:STDOUT:       %OptionalStorage.facet: %OptionalStorage.type = facet_value constants.%ptr.5c7, (constants.%OptionalStorage.impl_witness.bed) [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:       %.loc8_39.2: %OptionalStorage.type = converted constants.%ptr.5c7, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:       %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.91c]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.Destroy: type = import_ref Core//prelude/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref.loc12_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Cpp.ref.loc8_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %foo.ref: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value]
 // CHECK:STDOUT:   %Core.ref: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
 // CHECK:STDOUT:   %Optional.ref: %Optional.type = name_ref Optional, imports.%Core.Optional [concrete = constants.%Optional.generic]
-// CHECK:STDOUT:   %Cpp.ref.loc12_25: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Cpp.ref.loc8_25: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %S.ref: type = name_ref S, imports.%S.decl [concrete = constants.%S]
 // CHECK:STDOUT:   %ptr: type = ptr_type %S.ref [concrete = constants.%ptr.5c7]
 // CHECK:STDOUT:   %OptionalStorage.facet: %OptionalStorage.type = facet_value %ptr, (constants.%OptionalStorage.impl_witness.bed) [concrete = constants.%OptionalStorage.facet]
-// CHECK:STDOUT:   %.loc12_31: %OptionalStorage.type = converted %ptr, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:   %.loc8_31: %OptionalStorage.type = converted %ptr, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
 // CHECK:STDOUT:   %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.91c]
-// CHECK:STDOUT:   %.loc12_32: %Optional.None.type.410 = specific_constant imports.%Core.import_ref.f1d, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.None.d01]
-// CHECK:STDOUT:   %None.ref: %Optional.None.type.410 = name_ref None, %.loc12_32 [concrete = constants.%Optional.None.d01]
+// CHECK:STDOUT:   %.loc8_32: %Optional.None.type.410 = specific_constant imports.%Core.import_ref.f1d, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.None.d01]
+// CHECK:STDOUT:   %None.ref: %Optional.None.type.410 = name_ref None, %.loc8_32 [concrete = constants.%Optional.None.d01]
 // CHECK:STDOUT:   %Optional.None.specific_fn: <specific function> = specific_function %None.ref, @Optional.None(constants.%OptionalStorage.facet) [concrete = constants.%Optional.None.specific_fn]
 // CHECK:STDOUT:   %Optional.None.call: init %Optional.91c = call %Optional.None.specific_fn()
+// CHECK:STDOUT:   %.loc8_38.1: ref %Optional.91c = temporary_storage
+// CHECK:STDOUT:   %.loc8_38.2: ref %Optional.91c = temporary %.loc8_38.1, %Optional.None.call
+// CHECK:STDOUT:   %.loc8_38.3: %Optional.91c = acquire_value %.loc8_38.2
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call imports.%foo.decl(%.loc8_38.3)
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc8_38.2, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.6b6
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc8_38.2, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%.loc8_38.2)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- nonnull_pointer_arg_to_pointer_param.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %pattern_type.7da: type = pattern_type %S [concrete]
+// CHECK:STDOUT:   %empty_struct: %empty_struct_type = struct_value () [concrete]
+// CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
+// CHECK:STDOUT:   %foo.cpp_overload_set.type: type = cpp_overload_set_type @foo.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %foo.cpp_overload_set.value: %foo.cpp_overload_set.type = cpp_overload_set_value @foo.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %Optional.type: type = generic_class_type @Optional [concrete]
+// CHECK:STDOUT:   %Optional.generic: %Optional.type = struct_value () [concrete]
+// CHECK:STDOUT:   %OptionalStorage.type: type = facet_type <@OptionalStorage> [concrete]
+// CHECK:STDOUT:   %T.3fe: %OptionalStorage.type = symbolic_binding T, 0 [symbolic]
+// CHECK:STDOUT:   %Optional.Some.type.32b: type = fn_type @Optional.Some, @Optional(%T.3fe) [symbolic]
+// CHECK:STDOUT:   %Optional.Some.249: %Optional.Some.type.32b = struct_value () [symbolic]
+// CHECK:STDOUT:   %ptr.5c7: type = ptr_type %S [concrete]
+// CHECK:STDOUT:   %T.d9f: type = symbolic_binding T, 0 [symbolic]
+// CHECK:STDOUT:   %ptr.4f0: type = ptr_type %T.d9f [symbolic]
+// CHECK:STDOUT:   %MaybeUnformed.cff: type = class_type @MaybeUnformed, @MaybeUnformed(%ptr.4f0) [symbolic]
+// CHECK:STDOUT:   %ptr.as.OptionalStorage.impl.Some.type.911: type = fn_type @ptr.as.OptionalStorage.impl.Some, @ptr.as.OptionalStorage.impl(%T.d9f) [symbolic]
+// CHECK:STDOUT:   %ptr.as.OptionalStorage.impl.Some.2a0: %ptr.as.OptionalStorage.impl.Some.type.911 = struct_value () [symbolic]
+// CHECK:STDOUT:   %type_where: type = facet_type <type where .Self impls <CanDestroy>> [concrete]
+// CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
+// CHECK:STDOUT:   %OptionalStorage.impl_witness.fef: <witness> = impl_witness imports.%OptionalStorage.impl_witness_table.236, @ptr.as.OptionalStorage.impl(%S) [concrete]
+// CHECK:STDOUT:   %OptionalStorage.facet: %OptionalStorage.type = facet_value %ptr.5c7, (%OptionalStorage.impl_witness.fef) [concrete]
+// CHECK:STDOUT:   %Optional.454: type = class_type @Optional, @Optional(%OptionalStorage.facet) [concrete]
+// CHECK:STDOUT:   %Optional.Some.type.602: type = fn_type @Optional.Some, @Optional(%OptionalStorage.facet) [concrete]
+// CHECK:STDOUT:   %Optional.Some.872: %Optional.Some.type.602 = struct_value () [concrete]
+// CHECK:STDOUT:   %Optional.Some.specific_fn: <specific function> = specific_function %Optional.Some.872, @Optional.Some(%OptionalStorage.facet) [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %facet_value.542: %type_where = facet_value %Optional.454, () [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.6e8: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.542) [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.de7: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.6e8 = struct_value () [concrete]
+// CHECK:STDOUT:   %facet_value.7bd: %type_where = facet_value %S, () [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.552: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.7bd) [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.572: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.552 = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     .Optional = %Core.Optional
+// CHECK:STDOUT:     .Destroy = %Core.Destroy
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .S = %S.decl
+// CHECK:STDOUT:     .foo = %foo.cpp_overload_set.value
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %foo.cpp_overload_set.value: %foo.cpp_overload_set.type = cpp_overload_set_value @foo.cpp_overload_set [concrete = constants.%foo.cpp_overload_set.value]
+// CHECK:STDOUT:   %Core.Optional: %Optional.type = import_ref Core//prelude/types/optional, Optional, loaded [concrete = constants.%Optional.generic]
+// CHECK:STDOUT:   %Core.import_ref.c9e: @Optional.%Optional.Some.type (%Optional.Some.type.32b) = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, loaded [symbolic = @Optional.%Optional.Some (constants.%Optional.Some.249)]
+// CHECK:STDOUT:   %Core.import_ref.2fb: type = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, loaded [symbolic = @ptr.as.OptionalStorage.impl.%MaybeUnformed (constants.%MaybeUnformed.cff)]
+// CHECK:STDOUT:   %Core.import_ref.a7c = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
+// CHECK:STDOUT:   %Core.import_ref.1b2: @ptr.as.OptionalStorage.impl.%ptr.as.OptionalStorage.impl.Some.type (%ptr.as.OptionalStorage.impl.Some.type.911) = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, loaded [symbolic = @ptr.as.OptionalStorage.impl.%ptr.as.OptionalStorage.impl.Some (constants.%ptr.as.OptionalStorage.impl.Some.2a0)]
+// CHECK:STDOUT:   %Core.import_ref.6a9 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
+// CHECK:STDOUT:   %Core.import_ref.971 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
+// CHECK:STDOUT:   %OptionalStorage.impl_witness_table.236 = impl_witness_table (%Core.import_ref.2fb, %Core.import_ref.a7c, %Core.import_ref.1b2, %Core.import_ref.6a9, %Core.import_ref.971), @ptr.as.OptionalStorage.impl [concrete]
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:     %.loc9_41.1: type = splice_block %Optional [concrete = constants.%Optional.454] {
+// CHECK:STDOUT:       %OptionalStorage.facet: %OptionalStorage.type = facet_value constants.%ptr.5c7, (constants.%OptionalStorage.impl_witness.fef) [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:       %.loc9_41.2: %OptionalStorage.type = converted constants.%ptr.5c7, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:       %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.454]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.Destroy: type = import_ref Core//prelude/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %s.patt: %pattern_type.7da = ref_binding_pattern s [concrete]
+// CHECK:STDOUT:     %s.var_patt: %pattern_type.7da = var_pattern %s.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %s.var: ref %S = var %s.var_patt
+// CHECK:STDOUT:   %.loc8_19.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.loc8_19.2: init %S = class_init (), %s.var [concrete = constants.%S.val]
+// CHECK:STDOUT:   %.loc8_3: init %S = converted %.loc8_19.1, %.loc8_19.2 [concrete = constants.%S.val]
+// CHECK:STDOUT:   assign %s.var, %.loc8_3
+// CHECK:STDOUT:   %.loc8_13: type = splice_block %S.ref.loc8 [concrete = constants.%S] {
+// CHECK:STDOUT:     %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %S.ref.loc8: type = name_ref S, imports.%S.decl [concrete = constants.%S]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %s: ref %S = ref_binding s, %s.var
+// CHECK:STDOUT:   %Cpp.ref.loc9_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value]
+// CHECK:STDOUT:   %Core.ref: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:   %Optional.ref: %Optional.type = name_ref Optional, imports.%Core.Optional [concrete = constants.%Optional.generic]
+// CHECK:STDOUT:   %Cpp.ref.loc9_25: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %S.ref.loc9: type = name_ref S, imports.%S.decl [concrete = constants.%S]
+// CHECK:STDOUT:   %ptr: type = ptr_type %S.ref.loc9 [concrete = constants.%ptr.5c7]
+// CHECK:STDOUT:   %OptionalStorage.facet.loc9_31: %OptionalStorage.type = facet_value %ptr, (constants.%OptionalStorage.impl_witness.fef) [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:   %.loc9_31: %OptionalStorage.type = converted %ptr, %OptionalStorage.facet.loc9_31 [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:   %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.454]
+// CHECK:STDOUT:   %.loc9_32: %Optional.Some.type.602 = specific_constant imports.%Core.import_ref.c9e, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.Some.872]
+// CHECK:STDOUT:   %Some.ref: %Optional.Some.type.602 = name_ref Some, %.loc9_32 [concrete = constants.%Optional.Some.872]
+// CHECK:STDOUT:   %s.ref: ref %S = name_ref s, %s
+// CHECK:STDOUT:   %addr: %ptr.5c7 = addr_of %s.ref
+// CHECK:STDOUT:   %OptionalStorage.facet.loc9_40.1: %OptionalStorage.type = facet_value constants.%ptr.5c7, (constants.%OptionalStorage.impl_witness.fef) [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:   %.loc9_40.1: %OptionalStorage.type = converted constants.%ptr.5c7, %OptionalStorage.facet.loc9_40.1 [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:   %OptionalStorage.facet.loc9_40.2: %OptionalStorage.type = facet_value constants.%ptr.5c7, (constants.%OptionalStorage.impl_witness.fef) [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:   %.loc9_40.2: %OptionalStorage.type = converted constants.%ptr.5c7, %OptionalStorage.facet.loc9_40.2 [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:   %Optional.Some.specific_fn: <specific function> = specific_function %Some.ref, @Optional.Some(constants.%OptionalStorage.facet) [concrete = constants.%Optional.Some.specific_fn]
+// CHECK:STDOUT:   %Optional.Some.call: init %Optional.454 = call %Optional.Some.specific_fn(%addr)
+// CHECK:STDOUT:   %.loc9_40.3: ref %Optional.454 = temporary_storage
+// CHECK:STDOUT:   %.loc9_40.4: ref %Optional.454 = temporary %.loc9_40.3, %Optional.Some.call
+// CHECK:STDOUT:   %.loc9_40.5: %Optional.454 = acquire_value %.loc9_40.4
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call imports.%foo.decl(%.loc9_40.5)
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound.loc9: <bound method> = bound_method %.loc9_40.4, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.de7
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc9: <bound method> = bound_method %.loc9_40.4, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn.1
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call.loc9: init %empty_tuple.type = call %bound_method.loc9(%.loc9_40.4)
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound.loc8: <bound method> = bound_method %s.var, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.572
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc8: <bound method> = bound_method %s.var, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn.2
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call.loc8: init %empty_tuple.type = call %bound_method.loc8(%s.var)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- forward_nullable_pointer.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %foo.cpp_overload_set.type: type = cpp_overload_set_type @foo.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %foo.cpp_overload_set.value: %foo.cpp_overload_set.type = cpp_overload_set_value @foo.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %get.cpp_overload_set.type: type = cpp_overload_set_type @get.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %get.cpp_overload_set.value: %get.cpp_overload_set.type = cpp_overload_set_value @get.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %ptr.5c7: type = ptr_type %S [concrete]
+// CHECK:STDOUT:   %OptionalStorage.type: type = facet_type <@OptionalStorage> [concrete]
+// CHECK:STDOUT:   %T.d9f: type = symbolic_binding T, 0 [symbolic]
+// CHECK:STDOUT:   %ptr.4f0: type = ptr_type %T.d9f [symbolic]
+// CHECK:STDOUT:   %MaybeUnformed.cff: type = class_type @MaybeUnformed, @MaybeUnformed(%ptr.4f0) [symbolic]
+// CHECK:STDOUT:   %type_where: type = facet_type <type where .Self impls <CanDestroy>> [concrete]
+// CHECK:STDOUT:   %OptionalStorage.impl_witness.81d: <witness> = impl_witness imports.%OptionalStorage.impl_witness_table.2f8, @ptr.as.OptionalStorage.impl(%S) [concrete]
+// CHECK:STDOUT:   %OptionalStorage.facet: %OptionalStorage.type = facet_value %ptr.5c7, (%OptionalStorage.impl_witness.81d) [concrete]
+// CHECK:STDOUT:   %Optional.4c2: type = class_type @Optional, @Optional(%OptionalStorage.facet) [concrete]
+// CHECK:STDOUT:   %get.type: type = fn_type @get [concrete]
+// CHECK:STDOUT:   %get: %get.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %facet_value.69a: %type_where = facet_value %Optional.4c2, () [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.fc8: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.69a) [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.da5: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.fc8 = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = %foo.cpp_overload_set.value
+// CHECK:STDOUT:     .get = %get.cpp_overload_set.value
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %foo.cpp_overload_set.value: %foo.cpp_overload_set.type = cpp_overload_set_value @foo.cpp_overload_set [concrete = constants.%foo.cpp_overload_set.value]
+// CHECK:STDOUT:   %get.cpp_overload_set.value: %get.cpp_overload_set.type = cpp_overload_set_value @get.cpp_overload_set [concrete = constants.%get.cpp_overload_set.value]
+// CHECK:STDOUT:   %Core.import_ref.2fb: type = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, loaded [symbolic = @ptr.as.OptionalStorage.impl.%MaybeUnformed (constants.%MaybeUnformed.cff)]
+// CHECK:STDOUT:   %Core.import_ref.a7c = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
+// CHECK:STDOUT:   %Core.import_ref.720 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
+// CHECK:STDOUT:   %Core.import_ref.6a9 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
+// CHECK:STDOUT:   %Core.import_ref.971 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
+// CHECK:STDOUT:   %OptionalStorage.impl_witness_table.2f8 = impl_witness_table (%Core.import_ref.2fb, %Core.import_ref.a7c, %Core.import_ref.720, %Core.import_ref.6a9, %Core.import_ref.971), @ptr.as.OptionalStorage.impl [concrete]
+// CHECK:STDOUT:   %get.decl: %get.type = fn_decl @get [concrete = constants.%get] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %OptionalStorage.facet: %OptionalStorage.type = facet_value constants.%ptr.5c7, (constants.%OptionalStorage.impl_witness.81d) [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:     %.loc8: %OptionalStorage.type = converted constants.%ptr.5c7, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:     %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.4c2]
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:     %.loc8_20.1: type = splice_block %Optional [concrete = constants.%Optional.4c2] {
+// CHECK:STDOUT:       %OptionalStorage.facet: %OptionalStorage.type = facet_value constants.%ptr.5c7, (constants.%OptionalStorage.impl_witness.81d) [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:       %.loc8_20.2: %OptionalStorage.type = converted constants.%ptr.5c7, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:       %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.4c2]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc8_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value]
+// CHECK:STDOUT:   %Cpp.ref.loc8_11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %get.ref: %get.cpp_overload_set.type = name_ref get, imports.%get.cpp_overload_set.value [concrete = constants.%get.cpp_overload_set.value]
+// CHECK:STDOUT:   %get.call: init %Optional.4c2 = call imports.%get.decl()
+// CHECK:STDOUT:   %.loc8_19.1: ref %Optional.4c2 = temporary_storage
+// CHECK:STDOUT:   %.loc8_19.2: ref %Optional.4c2 = temporary %.loc8_19.1, %get.call
+// CHECK:STDOUT:   %.loc8_19.3: %Optional.4c2 = acquire_value %.loc8_19.2
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call imports.%foo.decl(%.loc8_19.3)
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc8_19.2, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.da5
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc8_19.2, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%.loc8_19.2)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 36 - 11
toolchain/check/testdata/interop/cpp/function/void_pointer.carbon

@@ -52,7 +52,7 @@ fn F(input: Cpp.void*) {
   //@dump-sem-ir-end
 }
 
-// --- fail_todo_null_param.carbon
+// --- null_param.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -62,10 +62,6 @@ auto foo(void* x) -> void;
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_null_param.carbon:[[@LINE+4]]:11: error: call argument of type `Core.Optional(Cpp.void* as Core.OptionalStorage)` is not supported [CppCallArgTypeNotSupported]
-  // CHECK:STDERR:   Cpp.foo(Core.Optional(Cpp.void*).None());
-  // CHECK:STDERR:           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
   Cpp.foo(Core.Optional(Cpp.void*).None());
   //@dump-sem-ir-end
 }
@@ -291,9 +287,10 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_null_param.carbon
+// CHECK:STDOUT: --- null_param.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %foo.cpp_overload_set.type: type = cpp_overload_set_type @foo.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %foo.cpp_overload_set.value: %foo.cpp_overload_set.type = cpp_overload_set_value @foo.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %Optional.type: type = generic_class_type @Optional [concrete]
@@ -309,18 +306,26 @@ fn F() {
 // CHECK:STDOUT:   %MaybeUnformed.cff: type = class_type @MaybeUnformed, @MaybeUnformed(%ptr.4f0) [symbolic]
 // CHECK:STDOUT:   %ptr.as.OptionalStorage.impl.None.type.8ed: type = fn_type @ptr.as.OptionalStorage.impl.None, @ptr.as.OptionalStorage.impl(%T.d9f) [symbolic]
 // CHECK:STDOUT:   %ptr.as.OptionalStorage.impl.None.41a: %ptr.as.OptionalStorage.impl.None.type.8ed = struct_value () [symbolic]
+// CHECK:STDOUT:   %type_where: type = facet_type <type where .Self impls <CanDestroy>> [concrete]
+// CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
 // CHECK:STDOUT:   %OptionalStorage.impl_witness.ab6: <witness> = impl_witness imports.%OptionalStorage.impl_witness_table.f52, @ptr.as.OptionalStorage.impl(%Cpp.void) [concrete]
 // CHECK:STDOUT:   %OptionalStorage.facet: %OptionalStorage.type = facet_value %ptr.874, (%OptionalStorage.impl_witness.ab6) [concrete]
 // CHECK:STDOUT:   %Optional.082: type = class_type @Optional, @Optional(%OptionalStorage.facet) [concrete]
 // CHECK:STDOUT:   %Optional.None.type.fe0: type = fn_type @Optional.None, @Optional(%OptionalStorage.facet) [concrete]
 // CHECK:STDOUT:   %Optional.None.a64: %Optional.None.type.fe0 = struct_value () [concrete]
 // CHECK:STDOUT:   %Optional.None.specific_fn: <specific function> = specific_function %Optional.None.a64, @Optional.None(%OptionalStorage.facet) [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %facet_value.377: %type_where = facet_value %Optional.082, () [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.8df: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.377) [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.fca: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.8df = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
 // CHECK:STDOUT:     .Optional = %Core.Optional
 // CHECK:STDOUT:     .CppCompat = %CppCompat.c59
+// CHECK:STDOUT:     .Destroy = %Core.Destroy
 // CHECK:STDOUT:     import Core//prelude
 // CHECK:STDOUT:     import Core//prelude/...
 // CHECK:STDOUT:   }
@@ -345,24 +350,44 @@ fn F() {
 // CHECK:STDOUT:   %Core.import_ref.6a9 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
 // CHECK:STDOUT:   %Core.import_ref.971 = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, unloaded
 // CHECK:STDOUT:   %OptionalStorage.impl_witness_table.f52 = impl_witness_table (%Core.import_ref.2fb, %Core.import_ref.1d4, %Core.import_ref.720, %Core.import_ref.6a9, %Core.import_ref.971), @ptr.as.OptionalStorage.impl [concrete]
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:     %.loc10_42.1: type = splice_block %Optional [concrete = constants.%Optional.082] {
+// CHECK:STDOUT:       %OptionalStorage.facet: %OptionalStorage.type = facet_value constants.%ptr.874, (constants.%OptionalStorage.impl_witness.ab6) [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:       %.loc10_42.2: %OptionalStorage.type = converted constants.%ptr.874, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:       %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.082]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.Destroy: type = import_ref Core//prelude/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref.loc14_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Cpp.ref.loc10_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %foo.ref: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value]
 // CHECK:STDOUT:   %Core.ref: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
 // CHECK:STDOUT:   %Optional.ref: %Optional.type = name_ref Optional, imports.%Core.Optional [concrete = constants.%Optional.generic]
-// CHECK:STDOUT:   %Cpp.ref.loc14_25: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Cpp.ref.loc10_25: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %void.ref: type = name_ref void, constants.%Cpp.void [concrete = constants.%Cpp.void]
 // CHECK:STDOUT:   %ptr: type = ptr_type %void.ref [concrete = constants.%ptr.874]
 // CHECK:STDOUT:   %OptionalStorage.facet: %OptionalStorage.type = facet_value %ptr, (constants.%OptionalStorage.impl_witness.ab6) [concrete = constants.%OptionalStorage.facet]
-// CHECK:STDOUT:   %.loc14_34: %OptionalStorage.type = converted %ptr, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
+// CHECK:STDOUT:   %.loc10_34: %OptionalStorage.type = converted %ptr, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet]
 // CHECK:STDOUT:   %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.082]
-// CHECK:STDOUT:   %.loc14_35: %Optional.None.type.fe0 = specific_constant imports.%Core.import_ref.f1d, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.None.a64]
-// CHECK:STDOUT:   %None.ref: %Optional.None.type.fe0 = name_ref None, %.loc14_35 [concrete = constants.%Optional.None.a64]
+// CHECK:STDOUT:   %.loc10_35: %Optional.None.type.fe0 = specific_constant imports.%Core.import_ref.f1d, @Optional(constants.%OptionalStorage.facet) [concrete = constants.%Optional.None.a64]
+// CHECK:STDOUT:   %None.ref: %Optional.None.type.fe0 = name_ref None, %.loc10_35 [concrete = constants.%Optional.None.a64]
 // CHECK:STDOUT:   %Optional.None.specific_fn: <specific function> = specific_function %None.ref, @Optional.None(constants.%OptionalStorage.facet) [concrete = constants.%Optional.None.specific_fn]
 // CHECK:STDOUT:   %Optional.None.call: init %Optional.082 = call %Optional.None.specific_fn()
+// CHECK:STDOUT:   %.loc10_41.1: ref %Optional.082 = temporary_storage
+// CHECK:STDOUT:   %.loc10_41.2: ref %Optional.082 = temporary %.loc10_41.1, %Optional.None.call
+// CHECK:STDOUT:   %.loc10_41.3: %Optional.082 = acquire_value %.loc10_41.2
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call imports.%foo.decl(%.loc10_41.3)
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc10_41.2, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.fca
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc10_41.2, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%.loc10_41.2)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 6 - 3
toolchain/sem_ir/inst_namer.cpp

@@ -871,10 +871,13 @@ auto InstNamer::NamingContext::NameInst() -> void {
     case CARBON_KIND(ClassType inst): {
       if (auto type_info = RecognizedTypeInfo::ForType(sem_ir(), inst);
           type_info.is_valid()) {
-        AddInstName(type_info.GetLiteralAsString(sem_ir()));
-      } else {
-        AddEntityNameAndMaybePush(inst.class_id);
+        RawStringOstream out;
+        if (type_info.PrintLiteral(sem_ir(), out)) {
+          AddInstName(out.TakeStr());
+          return;
+        }
       }
+      AddEntityNameAndMaybePush(inst.class_id);
       return;
     }
     case CompleteTypeWitness::Kind: {

+ 6 - 4
toolchain/sem_ir/stringify.cpp

@@ -302,8 +302,9 @@ class Stringifier {
     const auto& class_info = sem_ir_->classes().Get(inst.class_id);
     if (auto type_info = RecognizedTypeInfo::ForType(*sem_ir_, inst);
         type_info.is_valid()) {
-      type_info.PrintLiteral(*sem_ir_, *out_);
-      return;
+      if (type_info.PrintLiteral(*sem_ir_, *out_)) {
+        return;
+      }
     }
     step_stack_->PushEntityName(class_info, inst.specific_id);
   }
@@ -793,8 +794,9 @@ auto StringifySpecific(const File& sem_ir, SpecificId specific_id)
                                 .specific_id = specific_id});
           type_info.is_valid()) {
         RawStringOstream out;
-        type_info.PrintLiteral(sem_ir, out);
-        return out.TakeStr();
+        if (type_info.PrintLiteral(sem_ir, out)) {
+          return out.TakeStr();
+        }
       }
       step_stack.PushEntityName(class_info, specific_id);
       break;

+ 30 - 22
toolchain/sem_ir/type_info.cpp

@@ -126,21 +126,22 @@ auto NumericTypeLiteralInfo::PrintLiteral(const File& file,
   file.ints().Get(bit_width_id).print(out, /*isSigned=*/false);
 }
 
-auto NumericTypeLiteralInfo::GetLiteralAsString(const File& file) const
-    -> std::string {
-  RawStringOstream out;
-  PrintLiteral(file, out);
-  return out.TakeStr();
+// Returns whether this kind of recognized type should have a generic argument
+// list.
+static auto ExpectsArgs(RecognizedTypeInfo::Kind kind) -> bool {
+  return kind == RecognizedTypeInfo::Optional;
 }
 
 auto RecognizedTypeInfo::ForType(const File& file, ClassType class_type)
     -> RecognizedTypeInfo {
+  auto args_id = SemIR::InstBlockId::None;
+
   if (class_type.specific_id.has_value()) {
     auto numeric = NumericTypeLiteralInfo::ForType(file, class_type);
     if (numeric.is_valid()) {
       return {.kind = Numeric, .numeric = numeric};
     }
-    return {.kind = None};
+    args_id = file.specifics().Get(class_type.specific_id).args_id;
   }
 
   // The class must be declared in the `Core` package. We check for up to one
@@ -167,9 +168,12 @@ auto RecognizedTypeInfo::ForType(const File& file, ClassType class_type)
   if (!parent_scope_name_id.has_value()) {
     Kind kind = llvm::StringSwitch<Kind>(*name_ident)
                     .Case("Char", Char)
+                    .Case("Optional", Optional)
                     .Case("String", Str)
                     .Default(None);
-    return {.kind = kind};
+    if (ExpectsArgs(kind) == args_id.has_value()) {
+      return {.kind = kind, .args_id = args_id};
+    }
   }
 
   auto parent_name_ident =
@@ -180,51 +184,55 @@ auto RecognizedTypeInfo::ForType(const File& file, ClassType class_type)
                     .Case("NullptrT", CppNullptrT)
                     .Case("VoidBase", CppVoidBase)
                     .Default(None);
-    return {.kind = kind};
+    if (ExpectsArgs(kind) == args_id.has_value()) {
+      return {.kind = kind, .args_id = args_id};
+    }
   }
 
   return {.kind = None};
 }
 
 auto RecognizedTypeInfo::PrintLiteral(const File& file,
-                                      llvm::raw_ostream& out) const -> void {
+                                      llvm::raw_ostream& out) const -> bool {
   switch (kind) {
     case None:
       CARBON_FATAL("Printing invalid type literal");
     case Numeric:
       numeric.PrintLiteral(file, out);
-      break;
+      return true;
     case Char:
       out << "char";
-      break;
+      return true;
     case CppLong32:
       if (file.clang_ast_unit()) {
         const clang::ASTContext& ast_context =
             file.clang_ast_unit()->getASTContext();
         if (ast_context.getIntWidth(ast_context.LongTy) == 32) {
           out << "Cpp.long";
-          break;
+          return true;
         }
       }
-      out << "Core.CppCompat.Long32";
       break;
     case CppNullptrT:
-      out << "Cpp.nullptr_t";
+      if (file.clang_ast_unit()) {
+        out << "Cpp.nullptr_t";
+        return true;
+      }
       break;
     case CppVoidBase:
-      out << "Cpp.void";
+      if (file.clang_ast_unit()) {
+        out << "Cpp.void";
+        return true;
+      }
+      break;
+    case Optional:
       break;
     case Str:
       out << "str";
-      break;
+      return true;
   }
-}
 
-auto RecognizedTypeInfo::GetLiteralAsString(const File& file) const
-    -> std::string {
-  RawStringOstream out;
-  PrintLiteral(file, out);
-  return out.TakeStr();
+  return false;
 }
 
 }  // namespace Carbon::SemIR

+ 8 - 8
toolchain/sem_ir/type_info.h

@@ -215,9 +215,6 @@ struct NumericTypeLiteralInfo {
   // Prints the numeric type literal that corresponds to this type.
   auto PrintLiteral(const File& file, llvm::raw_ostream& out) const -> void;
 
-  // Gets a string containing the literal.
-  auto GetLiteralAsString(const File& file) const -> std::string;
-
   // Returns whether this is a valid numeric type literal.
   auto is_valid() const -> bool { return kind != None; }
 
@@ -242,6 +239,8 @@ struct RecognizedTypeInfo {
     CppNullptrT,
     // `Cpp.void` / `Core.CppCompat.VoidBase`.
     CppVoidBase,
+    // `Core.Optional(...)`.
+    Optional,
     // `str` / `Core.String`.
     // TODO: Rename `Core.String` to `Core.Str`.
     Str,
@@ -251,11 +250,10 @@ struct RecognizedTypeInfo {
   static auto ForType(const File& file, ClassType class_type)
       -> RecognizedTypeInfo;
 
-  // Prints the type literal that corresponds to this type.
-  auto PrintLiteral(const File& file, llvm::raw_ostream& out) const -> void;
-
-  // Gets a string containing the literal.
-  auto GetLiteralAsString(const File& file) const -> std::string;
+  // Prints the type literal or special type name that corresponds to this type,
+  // if there is one. Returns true if the type was printed, or false if this
+  // type doesn't have special syntax and should be printed directly.
+  auto PrintLiteral(const File& file, llvm::raw_ostream& out) const -> bool;
 
   // Returns whether this is a valid type literal.
   auto is_valid() const -> bool { return kind != None; }
@@ -264,6 +262,8 @@ struct RecognizedTypeInfo {
   Kind kind;
   // If this is a numeric literal, additional information about the literal.
   NumericTypeLiteralInfo numeric = NumericTypeLiteralInfo::Invalid;
+  // If this is a generic type, the arguments.
+  InstBlockId args_id = InstBlockId::None;
 };
 
 inline constexpr NumericTypeLiteralInfo NumericTypeLiteralInfo::Invalid = {