Parcourir la source

Interop support for `nullptr` and `nullptr_t`. (#6353)

Add a `Core.CppCompat.NullptrT` type that C++'s `nullptr_t` maps into.
Map `nullptr` to an uninitialized constant of that type -- `nullptr`
doesn't actually have any defined bits within it, despite having the
same representation as `void*`.
Richard Smith il y a 5 mois
Parent
commit
86b02ee8af

+ 1 - 0
core/prelude/types.carbon

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

+ 49 - 0
core/prelude/types/cpp/nullptr.carbon

@@ -0,0 +1,49 @@
+// 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/cpp/nullptr";
+
+import library "prelude/copy";
+import library "prelude/destroy";
+import library "prelude/operators/as";
+import library "prelude/types/optional";
+import library "prelude/types/maybe_unformed";
+
+namespace CppCompat;
+
+// C++ `std::nullptr_t` as a Carbon type.
+//
+// The C++ type `decltype(nullptr)`, also known by the library-provided alias
+// `std::nullptr_t`, is a fundamental type, not a class type, so it needs a
+// custom mapping as part of C++ interoperability.  We map it to this class
+// type. After a suitable import, this class can be named as
+// `Cpp.std.nullptr_t`. This is also the type of the constant `Cpp.nullptr`.
+//
+// This type supports implicit conversion to any optional pointer type, and
+// produces the `None` value of that type.
+class CppCompat.NullptrT {
+  // nullptr_t has the same size and alignment as a pointer, but the
+  // corresponding pointer is always unformed.
+  // TODO: Give this type a custom empty value representation.
+  adapt MaybeUnformed(()*);
+
+  fn Make() -> Self {
+    returned var s: Self;
+    return var;
+  }
+
+  impl as Copy {
+    fn Op[self: Self]() -> Self {
+      return Make();
+    }
+  }
+
+  // TODO: impl as EqWith(Self)
+
+  impl forall [T:! type] as ImplicitAs(Optional(T*)) {
+    fn Convert[self: Self]() -> Optional(T*) {
+      return Optional(T*).None();
+    }
+  }
+}

+ 26 - 11
toolchain/check/cpp/import.cpp

@@ -46,6 +46,7 @@
 #include "toolchain/check/import.h"
 #include "toolchain/check/import.h"
 #include "toolchain/check/inst.h"
 #include "toolchain/check/inst.h"
 #include "toolchain/check/literal.h"
 #include "toolchain/check/literal.h"
+#include "toolchain/check/member_access.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/operator.h"
 #include "toolchain/check/operator.h"
 #include "toolchain/check/pattern.h"
 #include "toolchain/check/pattern.h"
@@ -1106,6 +1107,12 @@ static auto MapBuiltinIntegerType(Context& context, SemIR::LocId loc_id,
   return TypeExpr::None;
   return TypeExpr::None;
 }
 }
 
 
+static auto MapNullptrType(Context& context, SemIR::LocId loc_id) -> TypeExpr {
+  return ExprAsType(
+      context, loc_id,
+      LookupNameInCore(context, loc_id, {"CppCompat", "NullptrT"}));
+}
+
 // Maps a C++ builtin type to a Carbon type.
 // Maps a C++ builtin type to a Carbon type.
 // TODO: Support more builtin types.
 // TODO: Support more builtin types.
 static auto MapBuiltinType(Context& context, SemIR::LocId loc_id,
 static auto MapBuiltinType(Context& context, SemIR::LocId loc_id,
@@ -1134,6 +1141,8 @@ static auto MapBuiltinType(Context& context, SemIR::LocId loc_id,
   } else if (type.isVoidType()) {
   } else if (type.isVoidType()) {
     return ExprAsType(context, Parse::NodeId::None,
     return ExprAsType(context, Parse::NodeId::None,
                       SemIR::CppVoidType::TypeInstId);
                       SemIR::CppVoidType::TypeInstId);
+  } else if (type.isNullPtrType()) {
+    return MapNullptrType(context, loc_id);
   }
   }
 
 
   return TypeExpr::None;
   return TypeExpr::None;
@@ -2097,10 +2106,10 @@ static auto IsTopCppScope(Context& context, SemIR::NameScopeId scope_id)
   return name_scope.parent_scope_id() == SemIR::NameScopeId::Package;
   return name_scope.parent_scope_id() == SemIR::NameScopeId::Package;
 }
 }
 
 
-// For builtin names like `Cpp.long`, return the associated types.
-static auto LookupBuiltinTypes(Context& context, SemIR::LocId loc_id,
-                               SemIR::NameScopeId scope_id,
-                               SemIR::NameId name_id) -> SemIR::InstId {
+// For a builtin name like `Cpp.long`, returns the associated type.
+static auto LookupBuiltinName(Context& context, SemIR::LocId loc_id,
+                              SemIR::NameScopeId scope_id,
+                              SemIR::NameId name_id) -> SemIR::InstId {
   if (!IsTopCppScope(context, scope_id)) {
   if (!IsTopCppScope(context, scope_id)) {
     return SemIR::InstId::None;
     return SemIR::InstId::None;
   }
   }
@@ -2132,6 +2141,12 @@ static auto LookupBuiltinTypes(Context& context, SemIR::LocId loc_id,
           .Case("void", ast_context.VoidTy)
           .Case("void", ast_context.VoidTy)
           .Default(clang::QualType());
           .Default(clang::QualType());
   if (builtin_type.isNull()) {
   if (builtin_type.isNull()) {
+    if (*name == "nullptr") {
+      // Map `Cpp.nullptr` to an uninitialized value of type `Core.CppNullptrT`.
+      auto type_id = MapNullptrType(context, loc_id).type_id;
+      return GetOrAddInst<SemIR::UninitializedValue>(
+          context, SemIR::LocId::None, {.type_id = type_id});
+    }
     return SemIR::InstId::None;
     return SemIR::InstId::None;
   }
   }
 
 
@@ -2227,14 +2242,14 @@ static auto ImportConstructorsIntoScope(Context& context, SemIR::LocId loc_id,
                                     naming_class, std::move(overload_set));
                                     naming_class, std::move(overload_set));
 }
 }
 
 
-// Imports a builtin type from Clang to Carbon and adds the name into the
-// scope.
-static auto ImportBuiltinTypesIntoScope(Context& context, SemIR::LocId loc_id,
-                                        SemIR::NameScopeId scope_id,
-                                        SemIR::NameId name_id)
+// Attempts to import a builtin name from Clang to Carbon and adds the name into
+// the scope.
+static auto ImportBuiltinNameIntoScope(Context& context, SemIR::LocId loc_id,
+                                       SemIR::NameScopeId scope_id,
+                                       SemIR::NameId name_id)
     -> SemIR::ScopeLookupResult {
     -> SemIR::ScopeLookupResult {
   SemIR::InstId builtin_inst_id =
   SemIR::InstId builtin_inst_id =
-      LookupBuiltinTypes(context, loc_id, scope_id, name_id);
+      LookupBuiltinName(context, loc_id, scope_id, name_id);
   if (builtin_inst_id.has_value()) {
   if (builtin_inst_id.has_value()) {
     AddNameToScope(context, scope_id, name_id, SemIR::AccessKind::Public,
     AddNameToScope(context, scope_id, name_id, SemIR::AccessKind::Public,
                    builtin_inst_id);
                    builtin_inst_id);
@@ -2351,7 +2366,7 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
   }
   }
   auto lookup = ClangLookupName(context, scope_id, name_id);
   auto lookup = ClangLookupName(context, scope_id, name_id);
   if (!lookup) {
   if (!lookup) {
-    return ImportBuiltinTypesIntoScope(context, loc_id, scope_id, name_id);
+    return ImportBuiltinNameIntoScope(context, loc_id, scope_id, name_id);
   }
   }
   // Access checks are performed separately by the Carbon name lookup logic.
   // Access checks are performed separately by the Carbon name lookup logic.
   lookup->suppressAccessDiagnostics();
   lookup->suppressAccessDiagnostics();

+ 3 - 0
toolchain/check/cpp/type_mapping.cpp

@@ -149,6 +149,9 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
     case SemIR::TypeLiteralInfo::Char: {
     case SemIR::TypeLiteralInfo::Char: {
       return context.ast_context().CharTy;
       return context.ast_context().CharTy;
     }
     }
+    case SemIR::TypeLiteralInfo::CppNullptrT: {
+      return context.ast_context().NullPtrTy;
+    }
     case SemIR::TypeLiteralInfo::Str: {
     case SemIR::TypeLiteralInfo::Str: {
       return LookupCppType(context, {"std", "string_view"});
       return LookupCppType(context, {"std", "string_view"});
     }
     }

+ 41 - 18
toolchain/check/name_lookup.cpp

@@ -15,6 +15,7 @@
 #include "toolchain/check/type_completion.h"
 #include "toolchain/check/type_completion.h"
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/generic.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/name_scope.h"
 #include "toolchain/sem_ir/name_scope.h"
 
 
 namespace Carbon::Check {
 namespace Carbon::Check {
@@ -496,7 +497,8 @@ auto LookupQualifiedName(Context& context, SemIR::LocId loc_id,
 // TODO: Consider tracking the Core package in SemIR so we don't need to use
 // TODO: Consider tracking the Core package in SemIR so we don't need to use
 // name lookup to find it.
 // name lookup to find it.
 static auto GetCorePackage(Context& context, SemIR::LocId loc_id,
 static auto GetCorePackage(Context& context, SemIR::LocId loc_id,
-                           llvm::StringRef name) -> SemIR::NameScopeId {
+                           llvm::ArrayRef<llvm::StringRef> names)
+    -> SemIR::NameScopeId {
   auto packaging = context.parse_tree().packaging_decl();
   auto packaging = context.parse_tree().packaging_decl();
   if (packaging && packaging->names.package_id == PackageNameId::Core) {
   if (packaging && packaging->names.package_id == PackageNameId::Core) {
     return SemIR::NameScopeId::Package;
     return SemIR::NameScopeId::Package;
@@ -520,33 +522,54 @@ static auto GetCorePackage(Context& context, SemIR::LocId loc_id,
       CoreNotFound, Error,
       CoreNotFound, Error,
       "`Core.{0}` implicitly referenced here, but package `Core` not found",
       "`Core.{0}` implicitly referenced here, but package `Core` not found",
       std::string);
       std::string);
-  context.emitter().Emit(loc_id, CoreNotFound, name.str());
+  context.emitter().Emit(loc_id, CoreNotFound, llvm::join(names, "."));
   return SemIR::NameScopeId::None;
   return SemIR::NameScopeId::None;
 }
 }
 
 
 auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
 auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
-                      llvm::StringRef name) -> SemIR::InstId {
-  auto core_package_id = GetCorePackage(context, loc_id, name);
+                      llvm::ArrayRef<llvm::StringRef> names) -> SemIR::InstId {
+  CARBON_CHECK(!names.empty());
+
+  auto core_package_id = GetCorePackage(context, loc_id, names);
   if (!core_package_id.has_value()) {
   if (!core_package_id.has_value()) {
     return SemIR::ErrorInst::InstId;
     return SemIR::ErrorInst::InstId;
   }
   }
 
 
-  auto name_id = SemIR::NameId::ForIdentifier(context.identifiers().Add(name));
-  auto scope_result =
-      LookupNameInExactScope(context, loc_id, name_id, core_package_id,
-                             context.name_scopes().Get(core_package_id));
-  if (!scope_result.is_found()) {
-    CARBON_DIAGNOSTIC(
-        CoreNameNotFound, Error,
-        "name `Core.{0}` implicitly referenced here, but not found",
-        SemIR::NameId);
-    context.emitter().Emit(loc_id, CoreNameNotFound, name_id);
-    return SemIR::ErrorInst::InstId;
+  auto inst_id = SemIR::InstId::None;
+  for (auto name : names) {
+    auto name_id =
+        SemIR::NameId::ForIdentifier(context.identifiers().Add(name));
+
+    auto scope_id = SemIR::NameScopeId::None;
+    if (inst_id.has_value()) {
+      auto namespace_inst = context.insts().TryGetAs<SemIR::Namespace>(inst_id);
+      if (namespace_inst) {
+        scope_id = namespace_inst->name_scope_id;
+      }
+    } else {
+      scope_id = core_package_id;
+    }
+
+    auto scope_result =
+        scope_id.has_value()
+            ? LookupNameInExactScope(context, loc_id, name_id, scope_id,
+                                     context.name_scopes().Get(scope_id))
+            : SemIR::ScopeLookupResult::MakeNotFound();
+    if (!scope_result.is_found()) {
+      CARBON_DIAGNOSTIC(
+          CoreNameNotFound, Error,
+          "name `Core.{0}` implicitly referenced here, but not found",
+          std::string);
+      context.emitter().Emit(loc_id, CoreNameNotFound, llvm::join(names, "."));
+      return SemIR::ErrorInst::InstId;
+    }
+
+    // Look through import_refs and aliases.
+    inst_id = context.constant_values().GetConstantInstId(
+        scope_result.target_inst_id());
   }
   }
 
 
-  // Look through import_refs and aliases.
-  return context.constant_values().GetConstantInstId(
-      scope_result.target_inst_id());
+  return inst_id;
 }
 }
 
 
 auto DiagnoseDuplicateName(Context& context, SemIR::NameId name_id,
 auto DiagnoseDuplicateName(Context& context, SemIR::NameId name_id,

+ 9 - 2
toolchain/check/name_lookup.h

@@ -94,10 +94,17 @@ auto LookupQualifiedName(Context& context, SemIR::LocId loc_id,
                          std::optional<AccessInfo> access_info = std::nullopt)
                          std::optional<AccessInfo> access_info = std::nullopt)
     -> LookupResult;
     -> LookupResult;
 
 
+// Returns the `InstId` corresponding to a qualified name in the core package,
+// or BuiltinErrorInst if not found.
+auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
+                      llvm::ArrayRef<llvm::StringRef> names) -> SemIR::InstId;
+
 // Returns the `InstId` corresponding to a name in the core package, or
 // Returns the `InstId` corresponding to a name in the core package, or
 // BuiltinErrorInst if not found.
 // BuiltinErrorInst if not found.
-auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
-                      llvm::StringRef name) -> SemIR::InstId;
+inline auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
+                             llvm::StringRef name) -> SemIR::InstId {
+  return LookupNameInCore(context, loc_id, llvm::ArrayRef{name});
+}
 
 
 // Checks whether a name is accessible in the given access context. Produces a
 // Checks whether a name is accessible in the given access context. Produces a
 // diagnostic if not.
 // diagnostic if not.

+ 114 - 86
toolchain/check/testdata/interop/cpp/function/decayed_param.carbon

@@ -29,6 +29,8 @@ fn F() {
   //@dump-sem-ir-begin
   //@dump-sem-ir-begin
   var n: array(i32, 42);
   var n: array(i32, 42);
   Cpp.TakesArray(&n[0]);
   Cpp.TakesArray(&n[0]);
+
+  Cpp.TakesArray(Cpp.nullptr);
   //@dump-sem-ir-end
   //@dump-sem-ir-end
 }
 }
 
 
@@ -42,12 +44,6 @@ fn G(n: i32) -> i32;
 
 
 fn F() {
 fn F() {
   //@dump-sem-ir-begin
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE+4]]:18: error: member name `nullptr` not found in `Cpp` [MemberNameNotFoundInInstScope]
-  // CHECK:STDERR:   Cpp.TakesArray(Cpp.nullptr);
-  // CHECK:STDERR:                  ^~~~~~~~~~~
-  // CHECK:STDERR:
-  Cpp.TakesArray(Cpp.nullptr);
-
   // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE+4]]:21: error: call argument of type `<type of G>` is not supported [CppCallArgTypeNotSupported]
   // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE+4]]:21: error: call argument of type `<type of G>` is not supported [CppCallArgTypeNotSupported]
   // CHECK:STDERR:   Cpp.TakesFunction(G);
   // CHECK:STDERR:   Cpp.TakesFunction(G);
   // CHECK:STDERR:                     ^
   // CHECK:STDERR:                     ^
@@ -62,18 +58,18 @@ fn F() {
 
 
   var n: array(i32, 42);
   var n: array(i32, 42);
   // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE+8]]:26: error: no matching function for call to 'TakesFunction' [CppInteropParseError]
   // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE+8]]:26: error: no matching function for call to 'TakesFunction' [CppInteropParseError]
-  // CHECK:STDERR:    37 |   Cpp.TakesFunction(&n[0]);
+  // CHECK:STDERR:    31 |   Cpp.TakesFunction(&n[0]);
   // CHECK:STDERR:       |                          ^
   // CHECK:STDERR:       |                          ^
-  // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE-28]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE-22]]:10: in file included here [InCppInclude]
   // CHECK:STDERR: ./params.h:3:6: note: candidate function not viable: no known conversion from 'int * _Nonnull' to 'int (*)(int)' for 1st argument [CppInteropParseNote]
   // CHECK:STDERR: ./params.h:3:6: note: candidate function not viable: no known conversion from 'int * _Nonnull' to 'int (*)(int)' for 1st argument [CppInteropParseNote]
   // CHECK:STDERR:     3 | void TakesFunction(int f(int));
   // CHECK:STDERR:     3 | void TakesFunction(int f(int));
   // CHECK:STDERR:       |      ^             ~~~~~~~~~~
   // CHECK:STDERR:       |      ^             ~~~~~~~~~~
   // CHECK:STDERR:
   // CHECK:STDERR:
   Cpp.TakesFunction(&n[0]);
   Cpp.TakesFunction(&n[0]);
 
 
-  // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE+4]]:21: error: member name `nullptr` not found in `Cpp` [MemberNameNotFoundInInstScope]
+  // CHECK:STDERR: fail_todo_call_params_2.carbon:[[@LINE+4]]:3: error: semantics TODO: `Unsupported: parameter type: int (*)(int)` [SemanticsTodo]
   // CHECK:STDERR:   Cpp.TakesFunction(Cpp.nullptr);
   // CHECK:STDERR:   Cpp.TakesFunction(Cpp.nullptr);
-  // CHECK:STDERR:                     ^~~~~~~~~~~
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   // CHECK:STDERR:
   Cpp.TakesFunction(Cpp.nullptr);
   Cpp.TakesFunction(Cpp.nullptr);
   //@dump-sem-ir-end
   //@dump-sem-ir-end
@@ -105,42 +101,56 @@ fn F() {
 // CHECK:STDOUT:   %.322: type = fn_type_with_self_type %ImplicitAs.Convert.type.1b6, %ImplicitAs.facet.3ad [concrete]
 // CHECK:STDOUT:   %.322: type = fn_type_with_self_type %ImplicitAs.Convert.type.1b6, %ImplicitAs.facet.3ad [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: <bound method> = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: <bound method> = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
-// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
+// CHECK:STDOUT:   %bound_method.9be: <bound method> = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %int_0.6a9: %i32 = int_value 0 [concrete]
 // CHECK:STDOUT:   %int_0.6a9: %i32 = int_value 0 [concrete]
 // CHECK:STDOUT:   %ptr.235: type = ptr_type %i32 [concrete]
 // CHECK:STDOUT:   %ptr.235: type = ptr_type %i32 [concrete]
 // CHECK:STDOUT:   %OptionalStorage.type: type = facet_type <@OptionalStorage> [concrete]
 // CHECK:STDOUT:   %OptionalStorage.type: type = facet_type <@OptionalStorage> [concrete]
 // CHECK:STDOUT:   %T.3fe: %OptionalStorage.type = symbolic_binding T, 0 [symbolic]
 // CHECK:STDOUT:   %T.3fe: %OptionalStorage.type = symbolic_binding T, 0 [symbolic]
 // CHECK:STDOUT:   %ptr.4f0: type = ptr_type %T.d9f [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:   %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:   %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.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:   %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:   %type_where: type = facet_type <type where .Self impls <CanDestroy>> [concrete]
-// CHECK:STDOUT:   %OptionalStorage.impl_witness.2cf: <witness> = impl_witness imports.%OptionalStorage.impl_witness_table.236, @ptr.as.OptionalStorage.impl(%i32) [concrete]
-// CHECK:STDOUT:   %OptionalStorage.facet.0a3: %OptionalStorage.type = facet_value %ptr.235, (%OptionalStorage.impl_witness.2cf) [concrete]
-// CHECK:STDOUT:   %Optional.18c: type = class_type @Optional, @Optional(%OptionalStorage.facet.0a3) [concrete]
+// CHECK:STDOUT:   %OptionalStorage.impl_witness.d16: <witness> = impl_witness imports.%OptionalStorage.impl_witness_table.f03, @ptr.as.OptionalStorage.impl(%i32) [concrete]
+// CHECK:STDOUT:   %OptionalStorage.facet.083: %OptionalStorage.type = facet_value %ptr.235, (%OptionalStorage.impl_witness.d16) [concrete]
+// CHECK:STDOUT:   %Optional.97d: type = class_type @Optional, @Optional(%OptionalStorage.facet.083) [concrete]
 // CHECK:STDOUT:   %TakesArray.type: type = fn_type @TakesArray [concrete]
 // CHECK:STDOUT:   %TakesArray.type: type = fn_type @TakesArray [concrete]
 // CHECK:STDOUT:   %TakesArray: %TakesArray.type = struct_value () [concrete]
 // CHECK:STDOUT:   %TakesArray: %TakesArray.type = struct_value () [concrete]
-// CHECK:STDOUT:   %ImplicitAs.type.4f4: type = facet_type <@ImplicitAs, @ImplicitAs(%Optional.18c)> [concrete]
-// CHECK:STDOUT:   %ImplicitAs.Convert.type.7ef: type = fn_type @ImplicitAs.Convert, @ImplicitAs(%Optional.18c) [concrete]
+// CHECK:STDOUT:   %ImplicitAs.type.534: type = facet_type <@ImplicitAs, @ImplicitAs(%Optional.97d)> [concrete]
+// CHECK:STDOUT:   %ImplicitAs.Convert.type.8fe: type = fn_type @ImplicitAs.Convert, @ImplicitAs(%Optional.97d) [concrete]
 // CHECK:STDOUT:   %OptionalAs.type.593: type = facet_type <@OptionalAs, @OptionalAs(%T.3fe)> [symbolic]
 // CHECK:STDOUT:   %OptionalAs.type.593: type = facet_type <@OptionalAs, @OptionalAs(%T.3fe)> [symbolic]
 // CHECK:STDOUT:   %U.ec3: %OptionalAs.type.593 = symbolic_binding U, 1 [symbolic]
 // CHECK:STDOUT:   %U.ec3: %OptionalAs.type.593 = symbolic_binding U, 1 [symbolic]
 // CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.type.855: type = fn_type @U.binding.as_type.as.ImplicitAs.impl.Convert.2, @U.binding.as_type.as.ImplicitAs.impl.ea7(%T.3fe, %U.ec3) [symbolic]
 // CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.type.855: type = fn_type @U.binding.as_type.as.ImplicitAs.impl.Convert.2, @U.binding.as_type.as.ImplicitAs.impl.ea7(%T.3fe, %U.ec3) [symbolic]
 // CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.337: %U.binding.as_type.as.ImplicitAs.impl.Convert.type.855 = struct_value () [symbolic]
 // CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.337: %U.binding.as_type.as.ImplicitAs.impl.Convert.type.855 = struct_value () [symbolic]
-// CHECK:STDOUT:   %OptionalAs.type.75b: type = facet_type <@OptionalAs, @OptionalAs(%OptionalStorage.facet.0a3)> [concrete]
+// CHECK:STDOUT:   %OptionalAs.type.07b: type = facet_type <@OptionalAs, @OptionalAs(%OptionalStorage.facet.083)> [concrete]
 // CHECK:STDOUT:   %T.binding.as_type.as.OptionalAs.impl.Convert.type.8c6: type = fn_type @T.binding.as_type.as.OptionalAs.impl.Convert, @T.binding.as_type.as.OptionalAs.impl(%T.3fe) [symbolic]
 // CHECK:STDOUT:   %T.binding.as_type.as.OptionalAs.impl.Convert.type.8c6: type = fn_type @T.binding.as_type.as.OptionalAs.impl.Convert, @T.binding.as_type.as.OptionalAs.impl(%T.3fe) [symbolic]
 // CHECK:STDOUT:   %T.binding.as_type.as.OptionalAs.impl.Convert.180: %T.binding.as_type.as.OptionalAs.impl.Convert.type.8c6 = struct_value () [symbolic]
 // CHECK:STDOUT:   %T.binding.as_type.as.OptionalAs.impl.Convert.180: %T.binding.as_type.as.OptionalAs.impl.Convert.type.8c6 = struct_value () [symbolic]
-// CHECK:STDOUT:   %OptionalAs.impl_witness.06b: <witness> = impl_witness imports.%OptionalAs.impl_witness_table.8ee, @T.binding.as_type.as.OptionalAs.impl(%OptionalStorage.facet.0a3) [concrete]
-// CHECK:STDOUT:   %OptionalAs.facet: %OptionalAs.type.75b = facet_value %ptr.235, (%OptionalAs.impl_witness.06b) [concrete]
-// CHECK:STDOUT:   %ImplicitAs.impl_witness.f51: <witness> = impl_witness imports.%ImplicitAs.impl_witness_table.da6, @U.binding.as_type.as.ImplicitAs.impl.ea7(%OptionalStorage.facet.0a3, %OptionalAs.facet) [concrete]
-// CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.type.f95: type = fn_type @U.binding.as_type.as.ImplicitAs.impl.Convert.2, @U.binding.as_type.as.ImplicitAs.impl.ea7(%OptionalStorage.facet.0a3, %OptionalAs.facet) [concrete]
-// CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.b59: %U.binding.as_type.as.ImplicitAs.impl.Convert.type.f95 = struct_value () [concrete]
-// CHECK:STDOUT:   %ImplicitAs.facet.59b: %ImplicitAs.type.4f4 = facet_value %ptr.235, (%ImplicitAs.impl_witness.f51) [concrete]
-// CHECK:STDOUT:   %.4d2: type = fn_type_with_self_type %ImplicitAs.Convert.type.7ef, %ImplicitAs.facet.59b [concrete]
-// CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %U.binding.as_type.as.ImplicitAs.impl.Convert.b59, @U.binding.as_type.as.ImplicitAs.impl.Convert.2(%OptionalStorage.facet.0a3, %OptionalAs.facet) [concrete]
-// CHECK:STDOUT:   %facet_value.888: %type_where = facet_value %Optional.18c, () [concrete]
-// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.b81: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.888) [concrete]
-// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.55c: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.b81 = struct_value () [concrete]
-// CHECK:STDOUT:   %ptr.a30: type = ptr_type %Optional.18c [concrete]
+// CHECK:STDOUT:   %OptionalAs.impl_witness.d8d: <witness> = impl_witness imports.%OptionalAs.impl_witness_table.8ee, @T.binding.as_type.as.OptionalAs.impl(%OptionalStorage.facet.083) [concrete]
+// CHECK:STDOUT:   %OptionalAs.facet: %OptionalAs.type.07b = facet_value %ptr.235, (%OptionalAs.impl_witness.d8d) [concrete]
+// CHECK:STDOUT:   %ImplicitAs.impl_witness.a9a: <witness> = impl_witness imports.%ImplicitAs.impl_witness_table.da6, @U.binding.as_type.as.ImplicitAs.impl.ea7(%OptionalStorage.facet.083, %OptionalAs.facet) [concrete]
+// CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.type.93b: type = fn_type @U.binding.as_type.as.ImplicitAs.impl.Convert.2, @U.binding.as_type.as.ImplicitAs.impl.ea7(%OptionalStorage.facet.083, %OptionalAs.facet) [concrete]
+// CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.1c9: %U.binding.as_type.as.ImplicitAs.impl.Convert.type.93b = struct_value () [concrete]
+// CHECK:STDOUT:   %ImplicitAs.facet.461: %ImplicitAs.type.534 = facet_value %ptr.235, (%ImplicitAs.impl_witness.a9a) [concrete]
+// CHECK:STDOUT:   %.4ad: type = fn_type_with_self_type %ImplicitAs.Convert.type.8fe, %ImplicitAs.facet.461 [concrete]
+// CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %U.binding.as_type.as.ImplicitAs.impl.Convert.1c9, @U.binding.as_type.as.ImplicitAs.impl.Convert.2(%OptionalStorage.facet.083, %OptionalAs.facet) [concrete]
+// CHECK:STDOUT:   %Cpp.nullptr_t: type = class_type @NullptrT [concrete]
+// CHECK:STDOUT:   %uninit: %Cpp.nullptr_t = uninitialized_value [concrete]
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.type.d80: type = fn_type @Cpp.nullptr_t.as.ImplicitAs.impl.Convert, @Cpp.nullptr_t.as.ImplicitAs.impl(%T.d9f) [symbolic]
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.2ce: %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.type.d80 = struct_value () [symbolic]
+// CHECK:STDOUT:   %ImplicitAs.impl_witness.213: <witness> = impl_witness imports.%ImplicitAs.impl_witness_table.87f, @Cpp.nullptr_t.as.ImplicitAs.impl(%i32) [concrete]
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.type.87f: type = fn_type @Cpp.nullptr_t.as.ImplicitAs.impl.Convert, @Cpp.nullptr_t.as.ImplicitAs.impl(%i32) [concrete]
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.83e: %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.type.87f = struct_value () [concrete]
+// CHECK:STDOUT:   %ImplicitAs.facet.5e9: %ImplicitAs.type.534 = facet_value %Cpp.nullptr_t, (%ImplicitAs.impl_witness.213) [concrete]
+// CHECK:STDOUT:   %.28d: type = fn_type_with_self_type %ImplicitAs.Convert.type.8fe, %ImplicitAs.facet.5e9 [concrete]
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.bound: <bound method> = bound_method %uninit, %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.83e [concrete]
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.83e, @Cpp.nullptr_t.as.ImplicitAs.impl.Convert(%i32) [concrete]
+// CHECK:STDOUT:   %bound_method.ed9: <bound method> = bound_method %uninit, %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.specific_fn [concrete]
+// CHECK:STDOUT:   %facet_value.c1c: %type_where = facet_value %Optional.97d, () [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.b8d: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.c1c) [concrete]
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.9b6: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.b8d = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.334: type = ptr_type %Optional.97d [concrete]
 // CHECK:STDOUT:   %facet_value.5b8: %type_where = facet_value %array_type, () [concrete]
 // CHECK:STDOUT:   %facet_value.5b8: %type_where = facet_value %array_type, () [concrete]
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.1d8: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.5b8) [concrete]
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.1d8: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value.5b8) [concrete]
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.f36: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.1d8 = struct_value () [concrete]
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.f36: %DestroyT.binding.as_type.as.Destroy.impl.Op.type.1d8 = struct_value () [concrete]
@@ -149,26 +159,27 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .TakesArray = %TakesArray.cpp_overload_set.value
 // CHECK:STDOUT:     .TakesArray = %TakesArray.cpp_overload_set.value
+// CHECK:STDOUT:     .nullptr = @F.%uninit
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %TakesArray.cpp_overload_set.value: %TakesArray.cpp_overload_set.type = cpp_overload_set_value @TakesArray.cpp_overload_set [concrete = constants.%TakesArray.cpp_overload_set.value]
 // CHECK:STDOUT:   %TakesArray.cpp_overload_set.value: %TakesArray.cpp_overload_set.type = cpp_overload_set_value @TakesArray.cpp_overload_set [concrete = constants.%TakesArray.cpp_overload_set.value]
 // CHECK:STDOUT:   %Core.import_ref.e24: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1)]
 // CHECK:STDOUT:   %Core.import_ref.e24: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1)]
 // CHECK:STDOUT:   %ImplicitAs.impl_witness_table.132 = impl_witness_table (%Core.import_ref.e24), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
 // CHECK:STDOUT:   %ImplicitAs.impl_witness_table.132 = impl_witness_table (%Core.import_ref.e24), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
 // 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.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.1d4: @ptr.as.OptionalStorage.impl.%ptr.as.OptionalStorage.impl.None.type (%ptr.as.OptionalStorage.impl.None.type.8ed) = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, loaded [symbolic = @ptr.as.OptionalStorage.impl.%ptr.as.OptionalStorage.impl.None (constants.%ptr.as.OptionalStorage.impl.None.41a)]
 // 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.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.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:   %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:   %OptionalStorage.impl_witness_table.f03 = impl_witness_table (%Core.import_ref.2fb, %Core.import_ref.1d4, %Core.import_ref.1b2, %Core.import_ref.6a9, %Core.import_ref.971), @ptr.as.OptionalStorage.impl [concrete]
 // CHECK:STDOUT:   %TakesArray.decl: %TakesArray.type = fn_decl @TakesArray [concrete = constants.%TakesArray] {
 // CHECK:STDOUT:   %TakesArray.decl: %TakesArray.type = fn_decl @TakesArray [concrete = constants.%TakesArray] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:     <elided>
-// CHECK:STDOUT:     %.loc11_23.1: type = splice_block %Optional [concrete = constants.%Optional.18c] {
+// CHECK:STDOUT:     %.loc11_23.1: type = splice_block %Optional [concrete = constants.%Optional.97d] {
 // CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       <elided>
-// CHECK:STDOUT:       %OptionalStorage.facet: %OptionalStorage.type = facet_value constants.%ptr.235, (constants.%OptionalStorage.impl_witness.2cf) [concrete = constants.%OptionalStorage.facet.0a3]
-// CHECK:STDOUT:       %.loc11_23.2: %OptionalStorage.type = converted constants.%ptr.235, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet.0a3]
-// CHECK:STDOUT:       %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet.0a3) [concrete = constants.%Optional.18c]
+// CHECK:STDOUT:       %OptionalStorage.facet: %OptionalStorage.type = facet_value constants.%ptr.235, (constants.%OptionalStorage.impl_witness.d16) [concrete = constants.%OptionalStorage.facet.083]
+// CHECK:STDOUT:       %.loc11_23.2: %OptionalStorage.type = converted constants.%ptr.235, %OptionalStorage.facet [concrete = constants.%OptionalStorage.facet.083]
+// CHECK:STDOUT:       %Optional: type = class_type @Optional, @Optional(constants.%OptionalStorage.facet.083) [concrete = constants.%Optional.97d]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
@@ -176,6 +187,8 @@ fn F() {
 // CHECK:STDOUT:   %ImplicitAs.impl_witness_table.da6 = impl_witness_table (%Core.import_ref.ec9), @U.binding.as_type.as.ImplicitAs.impl.ea7 [concrete]
 // CHECK:STDOUT:   %ImplicitAs.impl_witness_table.da6 = impl_witness_table (%Core.import_ref.ec9), @U.binding.as_type.as.ImplicitAs.impl.ea7 [concrete]
 // CHECK:STDOUT:   %Core.import_ref.7fb: @T.binding.as_type.as.OptionalAs.impl.%T.binding.as_type.as.OptionalAs.impl.Convert.type (%T.binding.as_type.as.OptionalAs.impl.Convert.type.8c6) = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, loaded [symbolic = @T.binding.as_type.as.OptionalAs.impl.%T.binding.as_type.as.OptionalAs.impl.Convert (constants.%T.binding.as_type.as.OptionalAs.impl.Convert.180)]
 // CHECK:STDOUT:   %Core.import_ref.7fb: @T.binding.as_type.as.OptionalAs.impl.%T.binding.as_type.as.OptionalAs.impl.Convert.type (%T.binding.as_type.as.OptionalAs.impl.Convert.type.8c6) = import_ref Core//prelude/types/optional, loc{{\d+_\d+}}, loaded [symbolic = @T.binding.as_type.as.OptionalAs.impl.%T.binding.as_type.as.OptionalAs.impl.Convert (constants.%T.binding.as_type.as.OptionalAs.impl.Convert.180)]
 // CHECK:STDOUT:   %OptionalAs.impl_witness_table.8ee = impl_witness_table (%Core.import_ref.7fb), @T.binding.as_type.as.OptionalAs.impl [concrete]
 // CHECK:STDOUT:   %OptionalAs.impl_witness_table.8ee = impl_witness_table (%Core.import_ref.7fb), @T.binding.as_type.as.OptionalAs.impl [concrete]
+// CHECK:STDOUT:   %Core.import_ref.54b: @Cpp.nullptr_t.as.ImplicitAs.impl.%Cpp.nullptr_t.as.ImplicitAs.impl.Convert.type (%Cpp.nullptr_t.as.ImplicitAs.impl.Convert.type.d80) = import_ref Core//prelude/types/cpp/nullptr, loc{{\d+_\d+}}, loaded [symbolic = @Cpp.nullptr_t.as.ImplicitAs.impl.%Cpp.nullptr_t.as.ImplicitAs.impl.Convert (constants.%Cpp.nullptr_t.as.ImplicitAs.impl.Convert.2ce)]
+// CHECK:STDOUT:   %ImplicitAs.impl_witness_table.87f = impl_witness_table (%Core.import_ref.54b), @Cpp.nullptr_t.as.ImplicitAs.impl [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: fn @F() {
@@ -192,8 +205,8 @@ fn F() {
 // CHECK:STDOUT:     %array_type: type = array_type %int_42, %i32.loc10 [concrete = constants.%array_type]
 // CHECK:STDOUT:     %array_type: type = array_type %int_42, %i32.loc10 [concrete = constants.%array_type]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %n: ref %array_type = ref_binding n, %n.var
 // CHECK:STDOUT:   %n: ref %array_type = ref_binding n, %n.var
-// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %TakesArray.ref: %TakesArray.cpp_overload_set.type = name_ref TakesArray, imports.%TakesArray.cpp_overload_set.value [concrete = constants.%TakesArray.cpp_overload_set.value]
+// CHECK:STDOUT:   %Cpp.ref.loc11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %TakesArray.ref.loc11: %TakesArray.cpp_overload_set.type = name_ref TakesArray, imports.%TakesArray.cpp_overload_set.value [concrete = constants.%TakesArray.cpp_overload_set.value]
 // CHECK:STDOUT:   %n.ref: ref %array_type = name_ref n, %n
 // CHECK:STDOUT:   %n.ref: ref %array_type = name_ref n, %n
 // CHECK:STDOUT:   %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6]
 // CHECK:STDOUT:   %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6]
 // CHECK:STDOUT:   %int_32.loc11: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
 // CHECK:STDOUT:   %int_32.loc11: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
@@ -201,30 +214,50 @@ fn F() {
 // CHECK:STDOUT:   %impl.elem0.loc11_21: %.322 = impl_witness_access constants.%ImplicitAs.impl_witness.bc9, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b]
 // CHECK:STDOUT:   %impl.elem0.loc11_21: %.322 = impl_witness_access constants.%ImplicitAs.impl_witness.bc9, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b]
 // CHECK:STDOUT:   %bound_method.loc11_21.1: <bound method> = bound_method %int_0, %impl.elem0.loc11_21 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
 // CHECK:STDOUT:   %bound_method.loc11_21.1: <bound method> = bound_method %int_0, %impl.elem0.loc11_21 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
 // CHECK:STDOUT:   %specific_fn.loc11_21: <specific function> = specific_function %impl.elem0.loc11_21, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %specific_fn.loc11_21: <specific function> = specific_function %impl.elem0.loc11_21, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
-// CHECK:STDOUT:   %bound_method.loc11_21.2: <bound method> = bound_method %int_0, %specific_fn.loc11_21 [concrete = constants.%bound_method]
+// CHECK:STDOUT:   %bound_method.loc11_21.2: <bound method> = bound_method %int_0, %specific_fn.loc11_21 [concrete = constants.%bound_method.9be]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc11_21.2(%int_0) [concrete = constants.%int_0.6a9]
 // CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc11_21.2(%int_0) [concrete = constants.%int_0.6a9]
 // CHECK:STDOUT:   %.loc11_21.1: %i32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_0.6a9]
 // CHECK:STDOUT:   %.loc11_21.1: %i32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_0.6a9]
 // CHECK:STDOUT:   %.loc11_21.2: %i32 = converted %int_0, %.loc11_21.1 [concrete = constants.%int_0.6a9]
 // CHECK:STDOUT:   %.loc11_21.2: %i32 = converted %int_0, %.loc11_21.1 [concrete = constants.%int_0.6a9]
 // CHECK:STDOUT:   %.loc11_22: ref %i32 = array_index %n.ref, %.loc11_21.2
 // CHECK:STDOUT:   %.loc11_22: ref %i32 = array_index %n.ref, %.loc11_21.2
 // CHECK:STDOUT:   %addr.loc11_18.1: %ptr.235 = addr_of %.loc11_22
 // CHECK:STDOUT:   %addr.loc11_18.1: %ptr.235 = addr_of %.loc11_22
-// CHECK:STDOUT:   %impl.elem0.loc11_18: %.4d2 = impl_witness_access constants.%ImplicitAs.impl_witness.f51, element0 [concrete = constants.%U.binding.as_type.as.ImplicitAs.impl.Convert.b59]
+// CHECK:STDOUT:   %impl.elem0.loc11_18: %.4ad = impl_witness_access constants.%ImplicitAs.impl_witness.a9a, element0 [concrete = constants.%U.binding.as_type.as.ImplicitAs.impl.Convert.1c9]
 // CHECK:STDOUT:   %bound_method.loc11_18.1: <bound method> = bound_method %addr.loc11_18.1, %impl.elem0.loc11_18
 // CHECK:STDOUT:   %bound_method.loc11_18.1: <bound method> = bound_method %addr.loc11_18.1, %impl.elem0.loc11_18
-// CHECK:STDOUT:   %specific_fn.loc11_18: <specific function> = specific_function %impl.elem0.loc11_18, @U.binding.as_type.as.ImplicitAs.impl.Convert.2(constants.%OptionalStorage.facet.0a3, constants.%OptionalAs.facet) [concrete = constants.%U.binding.as_type.as.ImplicitAs.impl.Convert.specific_fn]
+// CHECK:STDOUT:   %specific_fn.loc11_18: <specific function> = specific_function %impl.elem0.loc11_18, @U.binding.as_type.as.ImplicitAs.impl.Convert.2(constants.%OptionalStorage.facet.083, constants.%OptionalAs.facet) [concrete = constants.%U.binding.as_type.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %bound_method.loc11_18.2: <bound method> = bound_method %addr.loc11_18.1, %specific_fn.loc11_18
 // CHECK:STDOUT:   %bound_method.loc11_18.2: <bound method> = bound_method %addr.loc11_18.1, %specific_fn.loc11_18
-// CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.call: init %Optional.18c = call %bound_method.loc11_18.2(%addr.loc11_18.1)
-// CHECK:STDOUT:   %.loc11_18.1: init %Optional.18c = converted %addr.loc11_18.1, %U.binding.as_type.as.ImplicitAs.impl.Convert.call
-// CHECK:STDOUT:   %.loc11_18.2: ref %Optional.18c = temporary_storage
-// CHECK:STDOUT:   %.loc11_18.3: ref %Optional.18c = temporary %.loc11_18.2, %.loc11_18.1
-// CHECK:STDOUT:   %.loc11_18.4: %Optional.18c = acquire_value %.loc11_18.3
-// CHECK:STDOUT:   %TakesArray.call: init %empty_tuple.type = call imports.%TakesArray.decl(%.loc11_18.4)
-// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound.loc11: <bound method> = bound_method %.loc11_18.3, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.55c
+// CHECK:STDOUT:   %U.binding.as_type.as.ImplicitAs.impl.Convert.call: init %Optional.97d = call %bound_method.loc11_18.2(%addr.loc11_18.1)
+// CHECK:STDOUT:   %.loc11_18.1: init %Optional.97d = converted %addr.loc11_18.1, %U.binding.as_type.as.ImplicitAs.impl.Convert.call
+// CHECK:STDOUT:   %.loc11_18.2: ref %Optional.97d = temporary_storage
+// CHECK:STDOUT:   %.loc11_18.3: ref %Optional.97d = temporary %.loc11_18.2, %.loc11_18.1
+// CHECK:STDOUT:   %.loc11_18.4: %Optional.97d = acquire_value %.loc11_18.3
+// CHECK:STDOUT:   %TakesArray.call.loc11: init %empty_tuple.type = call imports.%TakesArray.decl(%.loc11_18.4)
+// CHECK:STDOUT:   %Cpp.ref.loc13_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %TakesArray.ref.loc13: %TakesArray.cpp_overload_set.type = name_ref TakesArray, imports.%TakesArray.cpp_overload_set.value [concrete = constants.%TakesArray.cpp_overload_set.value]
+// CHECK:STDOUT:   %Cpp.ref.loc13_18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %nullptr.ref: %Cpp.nullptr_t = name_ref nullptr, %uninit [concrete = constants.%uninit]
+// CHECK:STDOUT:   %impl.elem0.loc13: %.28d = impl_witness_access constants.%ImplicitAs.impl_witness.213, element0 [concrete = constants.%Cpp.nullptr_t.as.ImplicitAs.impl.Convert.83e]
+// CHECK:STDOUT:   %bound_method.loc13_21.1: <bound method> = bound_method %nullptr.ref, %impl.elem0.loc13 [concrete = constants.%Cpp.nullptr_t.as.ImplicitAs.impl.Convert.bound]
+// CHECK:STDOUT:   %specific_fn.loc13: <specific function> = specific_function %impl.elem0.loc13, @Cpp.nullptr_t.as.ImplicitAs.impl.Convert(constants.%i32) [concrete = constants.%Cpp.nullptr_t.as.ImplicitAs.impl.Convert.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc13_21.2: <bound method> = bound_method %nullptr.ref, %specific_fn.loc13 [concrete = constants.%bound_method.ed9]
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call: init %Optional.97d = call %bound_method.loc13_21.2(%nullptr.ref)
+// CHECK:STDOUT:   %.loc13_21.1: init %Optional.97d = converted %nullptr.ref, %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call
+// CHECK:STDOUT:   %.loc13_21.2: ref %Optional.97d = temporary_storage
+// CHECK:STDOUT:   %.loc13_21.3: ref %Optional.97d = temporary %.loc13_21.2, %.loc13_21.1
+// CHECK:STDOUT:   %.loc13_21.4: %Optional.97d = acquire_value %.loc13_21.3
+// CHECK:STDOUT:   %TakesArray.call.loc13: init %empty_tuple.type = call imports.%TakesArray.decl(%.loc13_21.4)
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound.loc13: <bound method> = bound_method %.loc13_21.3, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.9b6
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %bound_method.loc11_18.3: <bound method> = bound_method %.loc11_18.3, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn.1
-// CHECK:STDOUT:   %addr.loc11_18.2: %ptr.a30 = addr_of %.loc11_18.3
+// CHECK:STDOUT:   %bound_method.loc13_21.3: <bound method> = bound_method %.loc13_21.3, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn.1
+// CHECK:STDOUT:   %addr.loc13: %ptr.334 = addr_of %.loc13_21.3
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call.loc13: init %empty_tuple.type = call %bound_method.loc13_21.3(%addr.loc13)
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound.loc11: <bound method> = bound_method %.loc11_18.3, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.9b6
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc11_18.3: <bound method> = bound_method %.loc11_18.3, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn.2
+// CHECK:STDOUT:   %addr.loc11_18.2: %ptr.334 = addr_of %.loc11_18.3
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call.loc11: init %empty_tuple.type = call %bound_method.loc11_18.3(%addr.loc11_18.2)
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call.loc11: init %empty_tuple.type = call %bound_method.loc11_18.3(%addr.loc11_18.2)
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound.loc10: <bound method> = bound_method %n.var, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.f36
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound.loc10: <bound method> = bound_method %n.var, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.f36
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %bound_method.loc10: <bound method> = bound_method %n.var, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn.2
+// CHECK:STDOUT:   %bound_method.loc10: <bound method> = bound_method %n.var, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn.3
 // CHECK:STDOUT:   %addr.loc10: %ptr.830 = addr_of %n.var
 // CHECK:STDOUT:   %addr.loc10: %ptr.830 = addr_of %n.var
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call.loc10: init %empty_tuple.type = call %bound_method.loc10(%addr.loc10)
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call.loc10: init %empty_tuple.type = call %bound_method.loc10(%addr.loc10)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   <elided>
@@ -238,8 +271,6 @@ fn F() {
 // CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
 // CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
-// CHECK:STDOUT:   %TakesArray.cpp_overload_set.type: type = cpp_overload_set_type @TakesArray.cpp_overload_set [concrete]
-// CHECK:STDOUT:   %TakesArray.cpp_overload_set.value: %TakesArray.cpp_overload_set.type = cpp_overload_set_value @TakesArray.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %TakesFunction.cpp_overload_set.type: type = cpp_overload_set_type @TakesFunction.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %TakesFunction.cpp_overload_set.type: type = cpp_overload_set_type @TakesFunction.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %TakesFunction.cpp_overload_set.value: %TakesFunction.cpp_overload_set.type = cpp_overload_set_value @TakesFunction.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %TakesFunction.cpp_overload_set.value: %TakesFunction.cpp_overload_set.type = cpp_overload_set_value @TakesFunction.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %Function.cpp_overload_set.type: type = cpp_overload_set_type @Function.cpp_overload_set [concrete]
 // CHECK:STDOUT:   %Function.cpp_overload_set.type: type = cpp_overload_set_type @Function.cpp_overload_set [concrete]
@@ -264,6 +295,8 @@ fn F() {
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %bound_method: <bound method> = bound_method %int_0.5c6, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
 // CHECK:STDOUT:   %int_0.6a9: %i32 = int_value 0 [concrete]
 // CHECK:STDOUT:   %int_0.6a9: %i32 = int_value 0 [concrete]
 // CHECK:STDOUT:   %ptr.235: type = ptr_type %i32 [concrete]
 // CHECK:STDOUT:   %ptr.235: type = ptr_type %i32 [concrete]
+// CHECK:STDOUT:   %Cpp.nullptr_t: type = class_type @NullptrT [concrete]
+// CHECK:STDOUT:   %uninit: %Cpp.nullptr_t = uninitialized_value [concrete]
 // CHECK:STDOUT:   %type_where: type = facet_type <type where .Self impls <CanDestroy>> [concrete]
 // CHECK:STDOUT:   %type_where: type = facet_type <type where .Self impls <CanDestroy>> [concrete]
 // CHECK:STDOUT:   %facet_value: %type_where = facet_value %array_type, () [concrete]
 // CHECK:STDOUT:   %facet_value: %type_where = facet_value %array_type, () [concrete]
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.1d8: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value) [concrete]
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.type.1d8: type = fn_type @DestroyT.binding.as_type.as.Destroy.impl.Op, @DestroyT.binding.as_type.as.Destroy.impl(%facet_value) [concrete]
@@ -272,13 +305,11 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .TakesArray = %TakesArray.cpp_overload_set.value
-// CHECK:STDOUT:     .nullptr = <poisoned>
 // CHECK:STDOUT:     .TakesFunction = %TakesFunction.cpp_overload_set.value
 // CHECK:STDOUT:     .TakesFunction = %TakesFunction.cpp_overload_set.value
 // CHECK:STDOUT:     .Function = %Function.cpp_overload_set.value
 // CHECK:STDOUT:     .Function = %Function.cpp_overload_set.value
+// CHECK:STDOUT:     .nullptr = @F.%uninit
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %TakesArray.cpp_overload_set.value: %TakesArray.cpp_overload_set.type = cpp_overload_set_value @TakesArray.cpp_overload_set [concrete = constants.%TakesArray.cpp_overload_set.value]
 // CHECK:STDOUT:   %TakesFunction.cpp_overload_set.value: %TakesFunction.cpp_overload_set.type = cpp_overload_set_value @TakesFunction.cpp_overload_set [concrete = constants.%TakesFunction.cpp_overload_set.value]
 // CHECK:STDOUT:   %TakesFunction.cpp_overload_set.value: %TakesFunction.cpp_overload_set.type = cpp_overload_set_value @TakesFunction.cpp_overload_set [concrete = constants.%TakesFunction.cpp_overload_set.value]
 // CHECK:STDOUT:   %Function.cpp_overload_set.value: %Function.cpp_overload_set.type = cpp_overload_set_value @Function.cpp_overload_set [concrete = constants.%Function.cpp_overload_set.value]
 // CHECK:STDOUT:   %Function.cpp_overload_set.value: %Function.cpp_overload_set.type = cpp_overload_set_value @Function.cpp_overload_set [concrete = constants.%Function.cpp_overload_set.value]
 // CHECK:STDOUT:   %Core.import_ref.e24: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1)]
 // CHECK:STDOUT:   %Core.import_ref.e24: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f51) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.2a1)]
@@ -287,53 +318,50 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref.loc14_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %TakesArray.ref: %TakesArray.cpp_overload_set.type = name_ref TakesArray, imports.%TakesArray.cpp_overload_set.value [concrete = constants.%TakesArray.cpp_overload_set.value]
-// CHECK:STDOUT:   %Cpp.ref.loc14_18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %nullptr.ref.loc14: <error> = name_ref nullptr, <error> [concrete = <error>]
-// CHECK:STDOUT:   %Cpp.ref.loc20: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %TakesFunction.ref.loc20: %TakesFunction.cpp_overload_set.type = name_ref TakesFunction, imports.%TakesFunction.cpp_overload_set.value [concrete = constants.%TakesFunction.cpp_overload_set.value]
+// CHECK:STDOUT:   %Cpp.ref.loc14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %TakesFunction.ref.loc14: %TakesFunction.cpp_overload_set.type = name_ref TakesFunction, imports.%TakesFunction.cpp_overload_set.value [concrete = constants.%TakesFunction.cpp_overload_set.value]
 // CHECK:STDOUT:   %G.ref: %G.type = name_ref G, file.%G.decl [concrete = constants.%G]
 // CHECK:STDOUT:   %G.ref: %G.type = name_ref G, file.%G.decl [concrete = constants.%G]
-// CHECK:STDOUT:   %Cpp.ref.loc26_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %TakesFunction.ref.loc26: %TakesFunction.cpp_overload_set.type = name_ref TakesFunction, imports.%TakesFunction.cpp_overload_set.value [concrete = constants.%TakesFunction.cpp_overload_set.value]
-// CHECK:STDOUT:   %Cpp.ref.loc26_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Cpp.ref.loc20_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %TakesFunction.ref.loc20: %TakesFunction.cpp_overload_set.type = name_ref TakesFunction, imports.%TakesFunction.cpp_overload_set.value [concrete = constants.%TakesFunction.cpp_overload_set.value]
+// CHECK:STDOUT:   %Cpp.ref.loc20_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %Function.ref: %Function.cpp_overload_set.type = name_ref Function, imports.%Function.cpp_overload_set.value [concrete = constants.%Function.cpp_overload_set.value]
 // CHECK:STDOUT:   %Function.ref: %Function.cpp_overload_set.type = name_ref Function, imports.%Function.cpp_overload_set.value [concrete = constants.%Function.cpp_overload_set.value]
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:   name_binding_decl {
 // CHECK:STDOUT:     %n.patt: %pattern_type.b6e = ref_binding_pattern n [concrete]
 // CHECK:STDOUT:     %n.patt: %pattern_type.b6e = ref_binding_pattern n [concrete]
 // CHECK:STDOUT:     %n.var_patt: %pattern_type.b6e = var_pattern %n.patt [concrete]
 // CHECK:STDOUT:     %n.var_patt: %pattern_type.b6e = var_pattern %n.patt [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %n.var: ref %array_type = var %n.var_patt
 // CHECK:STDOUT:   %n.var: ref %array_type = var %n.var_patt
-// CHECK:STDOUT:   %.loc28: type = splice_block %array_type [concrete = constants.%array_type] {
-// CHECK:STDOUT:     %int_32.loc28: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
-// CHECK:STDOUT:     %i32.loc28: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %.loc22: type = splice_block %array_type [concrete = constants.%array_type] {
+// CHECK:STDOUT:     %int_32.loc22: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc22: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
 // CHECK:STDOUT:     %int_42: Core.IntLiteral = int_value 42 [concrete = constants.%int_42]
 // CHECK:STDOUT:     %int_42: Core.IntLiteral = int_value 42 [concrete = constants.%int_42]
-// CHECK:STDOUT:     %array_type: type = array_type %int_42, %i32.loc28 [concrete = constants.%array_type]
+// CHECK:STDOUT:     %array_type: type = array_type %int_42, %i32.loc22 [concrete = constants.%array_type]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %n: ref %array_type = ref_binding n, %n.var
 // CHECK:STDOUT:   %n: ref %array_type = ref_binding n, %n.var
-// CHECK:STDOUT:   %Cpp.ref.loc37: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %TakesFunction.ref.loc37: %TakesFunction.cpp_overload_set.type = name_ref TakesFunction, imports.%TakesFunction.cpp_overload_set.value [concrete = constants.%TakesFunction.cpp_overload_set.value]
+// CHECK:STDOUT:   %Cpp.ref.loc31: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %TakesFunction.ref.loc31: %TakesFunction.cpp_overload_set.type = name_ref TakesFunction, imports.%TakesFunction.cpp_overload_set.value [concrete = constants.%TakesFunction.cpp_overload_set.value]
 // CHECK:STDOUT:   %n.ref: ref %array_type = name_ref n, %n
 // CHECK:STDOUT:   %n.ref: ref %array_type = name_ref n, %n
 // CHECK:STDOUT:   %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6]
 // CHECK:STDOUT:   %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6]
-// CHECK:STDOUT:   %int_32.loc37: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
-// CHECK:STDOUT:   %i32.loc37: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %int_32.loc31: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.loc31: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
 // CHECK:STDOUT:   %impl.elem0: %.322 = impl_witness_access constants.%ImplicitAs.impl_witness.bc9, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b]
 // CHECK:STDOUT:   %impl.elem0: %.322 = impl_witness_access constants.%ImplicitAs.impl_witness.bc9, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.e9b]
-// CHECK:STDOUT:   %bound_method.loc37_24.1: <bound method> = bound_method %int_0, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
+// CHECK:STDOUT:   %bound_method.loc31_24.1: <bound method> = bound_method %int_0, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
 // CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
 // CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
-// CHECK:STDOUT:   %bound_method.loc37_24.2: <bound method> = bound_method %int_0, %specific_fn [concrete = constants.%bound_method]
-// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc37_24.2(%int_0) [concrete = constants.%int_0.6a9]
-// CHECK:STDOUT:   %.loc37_24.1: %i32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_0.6a9]
-// CHECK:STDOUT:   %.loc37_24.2: %i32 = converted %int_0, %.loc37_24.1 [concrete = constants.%int_0.6a9]
-// CHECK:STDOUT:   %.loc37_25: ref %i32 = array_index %n.ref, %.loc37_24.2
-// CHECK:STDOUT:   %addr.loc37: %ptr.235 = addr_of %.loc37_25
-// CHECK:STDOUT:   %Cpp.ref.loc43_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %TakesFunction.ref.loc43: %TakesFunction.cpp_overload_set.type = name_ref TakesFunction, imports.%TakesFunction.cpp_overload_set.value [concrete = constants.%TakesFunction.cpp_overload_set.value]
-// CHECK:STDOUT:   %Cpp.ref.loc43_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %nullptr.ref.loc43: <error> = name_ref nullptr, <error> [concrete = <error>]
+// CHECK:STDOUT:   %bound_method.loc31_24.2: <bound method> = bound_method %int_0, %specific_fn [concrete = constants.%bound_method]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc31_24.2(%int_0) [concrete = constants.%int_0.6a9]
+// CHECK:STDOUT:   %.loc31_24.1: %i32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_0.6a9]
+// CHECK:STDOUT:   %.loc31_24.2: %i32 = converted %int_0, %.loc31_24.1 [concrete = constants.%int_0.6a9]
+// CHECK:STDOUT:   %.loc31_25: ref %i32 = array_index %n.ref, %.loc31_24.2
+// CHECK:STDOUT:   %addr.loc31: %ptr.235 = addr_of %.loc31_25
+// CHECK:STDOUT:   %Cpp.ref.loc37_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %TakesFunction.ref.loc37: %TakesFunction.cpp_overload_set.type = name_ref TakesFunction, imports.%TakesFunction.cpp_overload_set.value [concrete = constants.%TakesFunction.cpp_overload_set.value]
+// CHECK:STDOUT:   %Cpp.ref.loc37_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %nullptr.ref: %Cpp.nullptr_t = name_ref nullptr, %uninit [concrete = constants.%uninit]
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound: <bound method> = bound_method %n.var, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.f36
 // CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.bound: <bound method> = bound_method %n.var, constants.%DestroyT.binding.as_type.as.Destroy.impl.Op.f36
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %bound_method.loc28: <bound method> = bound_method %n.var, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn
-// CHECK:STDOUT:   %addr.loc28: %ptr.830 = addr_of %n.var
-// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method.loc28(%addr.loc28)
+// CHECK:STDOUT:   %bound_method.loc22: <bound method> = bound_method %n.var, %DestroyT.binding.as_type.as.Destroy.impl.Op.specific_fn
+// CHECK:STDOUT:   %addr.loc22: %ptr.830 = addr_of %n.var
+// CHECK:STDOUT:   %DestroyT.binding.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method.loc22(%addr.loc22)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:

+ 5 - 0
toolchain/lower/constant.cpp

@@ -283,6 +283,11 @@ static auto EmitAsConstant(ConstantContext& context, SemIR::StringLiteral inst)
           /*name=*/"", /*address_space=*/0, &context.llvm_module());
           /*name=*/"", /*address_space=*/0, &context.llvm_module());
 }
 }
 
 
+static auto EmitAsConstant(ConstantContext& context,
+                           SemIR::UninitializedValue inst) -> llvm::Constant* {
+  return llvm::PoisonValue::get(context.GetType(inst.type_id));
+}
+
 static auto EmitAsConstant(ConstantContext& context, SemIR::VarStorage inst)
 static auto EmitAsConstant(ConstantContext& context, SemIR::VarStorage inst)
     -> llvm::Constant* {
     -> llvm::Constant* {
   // Create the corresponding global variable declaration.
   // Create the corresponding global variable declaration.

+ 222 - 0
toolchain/lower/testdata/interop/cpp/nullptr.carbon

@@ -0,0 +1,222 @@
+// 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/full.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/nullptr.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/nullptr.carbon
+
+// --- simple.carbon
+
+import Cpp inline '''
+using nullptr_t = decltype(nullptr);
+
+void TakePtr(int *p);
+void TakeNullptr(nullptr_t n);
+
+nullptr_t ReturnNullptr();
+''';
+
+fn PassNullptr() {
+  Cpp.TakePtr(Cpp.nullptr);
+
+  Cpp.TakeNullptr(Cpp.nullptr);
+}
+
+fn ReturnNullptr() -> Cpp.nullptr_t {
+  return Cpp.nullptr;
+}
+
+fn ReturnNullptrCopy() -> Cpp.nullptr_t {
+  var a: Cpp.nullptr_t = Cpp.nullptr;
+  return a;
+}
+
+fn ReturnConvertedNullptrIndirectly() -> Core.Optional(i32*) {
+  var a: Cpp.nullptr_t = Cpp.ReturnNullptr();
+  return a;
+}
+
+fn ReturnConvertedNullptrDirectly() -> Core.Optional(i32*) {
+  return Cpp.ReturnNullptr();
+}
+
+fn ConvertNullptrConstant() -> Core.Optional(i32*) {
+  return Cpp.nullptr;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'simple.carbon'
+// CHECK:STDOUT: source_filename = "simple.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define void @_CPassNullptr.Main() #0 !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc12_18.2.temp = alloca ptr, align 8, !dbg !10
+// CHECK:STDOUT:   %.loc14_22.1.temp = alloca ptr, align 8, !dbg !11
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call = call ptr @"_CConvert.NullptrT.CppCompat.Core:ImplicitAs.Core.b88d1103f417c6d4"(ptr poison), !dbg !10
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc12_18.2.temp), !dbg !10
+// CHECK:STDOUT:   store ptr %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call, ptr %.loc12_18.2.temp, align 8, !dbg !10
+// CHECK:STDOUT:   %.loc12_18.4 = load ptr, ptr %.loc12_18.2.temp, align 8, !dbg !10
+// CHECK:STDOUT:   call void @_Z7TakePtrPi(ptr %.loc12_18.4), !dbg !12
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc14_22.1.temp), !dbg !11
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.Copy.impl.Op.call = call ptr @"_COp.NullptrT.CppCompat.Core:Copy.Core"(ptr poison), !dbg !11
+// CHECK:STDOUT:   store ptr %Cpp.nullptr_t.as.Copy.impl.Op.call, ptr %.loc14_22.1.temp, align 8, !dbg !11
+// CHECK:STDOUT:   call void @_Z11TakeNullptrDn.carbon_thunk(ptr %.loc14_22.1.temp), !dbg !13
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_Z7TakePtrPi(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare ptr @"_COp.NullptrT.CppCompat.Core:Copy.Core"(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define ptr @_CReturnNullptr.Main() #0 !dbg !15 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.Copy.impl.Op.call = call ptr @"_COp.NullptrT.CppCompat.Core:Copy.Core"(ptr poison), !dbg !16
+// CHECK:STDOUT:   ret ptr %Cpp.nullptr_t.as.Copy.impl.Op.call, !dbg !17
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define ptr @_CReturnNullptrCopy.Main() #0 !dbg !18 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %a.var = alloca ptr, align 8, !dbg !19
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %a.var), !dbg !19
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.Copy.impl.Op.call.loc22 = call ptr @"_COp.NullptrT.CppCompat.Core:Copy.Core"(ptr poison), !dbg !20
+// CHECK:STDOUT:   store ptr %Cpp.nullptr_t.as.Copy.impl.Op.call.loc22, ptr %a.var, align 8, !dbg !19
+// CHECK:STDOUT:   %.loc23 = load ptr, ptr %a.var, align 8, !dbg !21
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.Copy.impl.Op.call.loc23 = call ptr @"_COp.NullptrT.CppCompat.Core:Copy.Core"(ptr %.loc23), !dbg !21
+// CHECK:STDOUT:   ret ptr %Cpp.nullptr_t.as.Copy.impl.Op.call.loc23, !dbg !22
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define ptr @_CReturnConvertedNullptrIndirectly.Main() #0 !dbg !23 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %a.var = alloca ptr, align 8, !dbg !24
+// CHECK:STDOUT:   %.loc27_44.1.temp = alloca ptr, align 8, !dbg !25
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %a.var), !dbg !24
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc27_44.1.temp), !dbg !25
+// CHECK:STDOUT:   call void @_Z13ReturnNullptrv.carbon_thunk(ptr %.loc27_44.1.temp), !dbg !25
+// CHECK:STDOUT:   %.loc27_44.2 = load ptr, ptr %.loc27_44.1.temp, align 8, !dbg !25
+// CHECK:STDOUT:   store ptr %.loc27_44.2, ptr %a.var, align 8, !dbg !24
+// CHECK:STDOUT:   %.loc28_10 = load ptr, ptr %a.var, align 8, !dbg !26
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call = call ptr @"_CConvert.NullptrT.CppCompat.Core:ImplicitAs.Core.b88d1103f417c6d4"(ptr %.loc28_10), !dbg !27
+// CHECK:STDOUT:   ret ptr %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call, !dbg !27
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define ptr @_CReturnConvertedNullptrDirectly.Main() #0 !dbg !28 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc32_28.1.temp = alloca ptr, align 8, !dbg !29
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc32_28.1.temp), !dbg !29
+// CHECK:STDOUT:   call void @_Z13ReturnNullptrv.carbon_thunk(ptr %.loc32_28.1.temp), !dbg !29
+// CHECK:STDOUT:   %.loc32_28.2 = load ptr, ptr %.loc32_28.1.temp, align 8, !dbg !29
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call = call ptr @"_CConvert.NullptrT.CppCompat.Core:ImplicitAs.Core.b88d1103f417c6d4"(ptr %.loc32_28.2), !dbg !30
+// CHECK:STDOUT:   ret ptr %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call, !dbg !30
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define ptr @_CConvertNullptrConstant.Main() #0 !dbg !31 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call = call ptr @"_CConvert.NullptrT.CppCompat.Core:ImplicitAs.Core.b88d1103f417c6d4"(ptr poison), !dbg !32
+// CHECK:STDOUT:   ret ptr %Cpp.nullptr_t.as.ImplicitAs.impl.Convert.call, !dbg !32
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define linkonce_odr ptr @"_CConvert.NullptrT.CppCompat.Core:ImplicitAs.Core.b88d1103f417c6d4"(ptr %self) #0 !dbg !33 {
+// CHECK:STDOUT:   %1 = call ptr @_CNone.Optional.Core.e49c05a5c9dc9c86(), !dbg !35
+// CHECK:STDOUT:   ret ptr %1, !dbg !36
+// 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)) #1
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare ptr @_CMake.NullptrT.CppCompat.Core()
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nounwind
+// CHECK:STDOUT: define linkonce_odr ptr @_CNone.Optional.Core.e49c05a5c9dc9c86() #0 !dbg !37 {
+// CHECK:STDOUT:   ret ptr null, !dbg !39
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress
+// CHECK:STDOUT: define dso_local void @_Z11TakeNullptrDn.carbon_thunk(ptr %n) #2 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %n.addr = alloca ptr, align 8
+// CHECK:STDOUT:   store ptr %n, ptr %n.addr, align 8
+// CHECK:STDOUT:   %0 = load ptr, ptr %n.addr, align 8
+// CHECK:STDOUT:   call void @_Z11TakeNullptrDn(ptr null)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_Z11TakeNullptrDn(ptr) #3
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress
+// CHECK:STDOUT: define dso_local void @_Z13ReturnNullptrv.carbon_thunk(ptr %return) #2 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %return.addr = alloca ptr, align 8
+// CHECK:STDOUT:   store ptr %return, ptr %return.addr, align 8
+// CHECK:STDOUT:   %0 = load ptr, ptr %return.addr, align 8
+// CHECK:STDOUT:   %call = call ptr @_Z13ReturnNullptrv()
+// CHECK:STDOUT:   store ptr %call, ptr %0, align 8
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare ptr @_Z13ReturnNullptrv() #3
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @"_CConvert.NullptrT.CppCompat.Core:ImplicitAs.Core.b88d1103f417c6d4", { 3, 2, 1, 0 }
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 5, 4, 3, 2, 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nounwind }
+// CHECK:STDOUT: attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #2 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT: attributes #3 = { "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "simple.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "PassNullptr", linkageName: "_CPassNullptr.Main", scope: null, file: !6, line: 11, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 12, column: 15, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 14, column: 19, scope: !7)
+// CHECK:STDOUT: !12 = !DILocation(line: 12, column: 3, scope: !7)
+// CHECK:STDOUT: !13 = !DILocation(line: 14, column: 3, scope: !7)
+// CHECK:STDOUT: !14 = !DILocation(line: 11, column: 1, scope: !7)
+// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "ReturnNullptr", linkageName: "_CReturnNullptr.Main", scope: null, file: !6, line: 17, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !16 = !DILocation(line: 18, column: 10, scope: !15)
+// CHECK:STDOUT: !17 = !DILocation(line: 18, column: 3, scope: !15)
+// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "ReturnNullptrCopy", linkageName: "_CReturnNullptrCopy.Main", scope: null, file: !6, line: 21, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !19 = !DILocation(line: 22, column: 3, scope: !18)
+// CHECK:STDOUT: !20 = !DILocation(line: 22, column: 26, scope: !18)
+// CHECK:STDOUT: !21 = !DILocation(line: 23, column: 10, scope: !18)
+// CHECK:STDOUT: !22 = !DILocation(line: 23, column: 3, scope: !18)
+// CHECK:STDOUT: !23 = distinct !DISubprogram(name: "ReturnConvertedNullptrIndirectly", linkageName: "_CReturnConvertedNullptrIndirectly.Main", scope: null, file: !6, line: 26, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !24 = !DILocation(line: 27, column: 3, scope: !23)
+// CHECK:STDOUT: !25 = !DILocation(line: 27, column: 26, scope: !23)
+// CHECK:STDOUT: !26 = !DILocation(line: 28, column: 10, scope: !23)
+// CHECK:STDOUT: !27 = !DILocation(line: 28, column: 3, scope: !23)
+// CHECK:STDOUT: !28 = distinct !DISubprogram(name: "ReturnConvertedNullptrDirectly", linkageName: "_CReturnConvertedNullptrDirectly.Main", scope: null, file: !6, line: 31, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !29 = !DILocation(line: 32, column: 10, scope: !28)
+// CHECK:STDOUT: !30 = !DILocation(line: 32, column: 3, scope: !28)
+// CHECK:STDOUT: !31 = distinct !DISubprogram(name: "ConvertNullptrConstant", linkageName: "_CConvertNullptrConstant.Main", scope: null, file: !6, line: 35, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !32 = !DILocation(line: 36, column: 3, scope: !31)
+// CHECK:STDOUT: !33 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.NullptrT.CppCompat.Core:ImplicitAs.Core.b88d1103f417c6d4", scope: null, file: !34, line: 45, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !34 = !DIFile(filename: "{{.*}}/prelude/types/cpp/nullptr.carbon", directory: "")
+// CHECK:STDOUT: !35 = !DILocation(line: 46, column: 14, scope: !33)
+// CHECK:STDOUT: !36 = !DILocation(line: 46, column: 7, scope: !33)
+// CHECK:STDOUT: !37 = distinct !DISubprogram(name: "None", linkageName: "_CNone.Optional.Core.e49c05a5c9dc9c86", scope: null, file: !38, line: 26, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !38 = !DIFile(filename: "{{.*}}/prelude/types/optional.carbon", directory: "")
+// CHECK:STDOUT: !39 = !DILocation(line: 27, column: 5, scope: !37)

+ 1 - 0
toolchain/sem_ir/expr_info.cpp

@@ -166,6 +166,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case TypeType::Kind:
       case TypeType::Kind:
       case UnaryOperatorNot::Kind:
       case UnaryOperatorNot::Kind:
       case UnboundElementType::Kind:
       case UnboundElementType::Kind:
+      case UninitializedValue::Kind:
       case ValueOfInitializer::Kind:
       case ValueOfInitializer::Kind:
       case ValueParam::Kind:
       case ValueParam::Kind:
       case VtableType::Kind:
       case VtableType::Kind:

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -147,6 +147,7 @@ CARBON_SEM_IR_INST_KIND(TypeOfInst)
 CARBON_SEM_IR_INST_KIND(TypeType)
 CARBON_SEM_IR_INST_KIND(TypeType)
 CARBON_SEM_IR_INST_KIND(UnaryOperatorNot)
 CARBON_SEM_IR_INST_KIND(UnaryOperatorNot)
 CARBON_SEM_IR_INST_KIND(UnboundElementType)
 CARBON_SEM_IR_INST_KIND(UnboundElementType)
+CARBON_SEM_IR_INST_KIND(UninitializedValue)
 CARBON_SEM_IR_INST_KIND(ValueAsRef)
 CARBON_SEM_IR_INST_KIND(ValueAsRef)
 CARBON_SEM_IR_INST_KIND(ValueBinding)
 CARBON_SEM_IR_INST_KIND(ValueBinding)
 CARBON_SEM_IR_INST_KIND(ValueBindingPattern)
 CARBON_SEM_IR_INST_KIND(ValueBindingPattern)

+ 4 - 0
toolchain/sem_ir/inst_namer.cpp

@@ -1286,6 +1286,10 @@ auto InstNamer::NamingContext::NameInst() -> void {
       }
       }
       return;
       return;
     }
     }
+    case UninitializedValue::Kind: {
+      AddInstName("uninit");
+      return;
+    }
     case VarPattern::Kind: {
     case VarPattern::Kind: {
       AddInstNameId(GetPrettyNameFromPatternId(sem_ir(), inst_id_),
       AddInstNameId(GetPrettyNameFromPatternId(sem_ir(), inst_id_),
                     ".var_patt");
                     ".var_patt");

+ 32 - 7
toolchain/sem_ir/type_info.cpp

@@ -143,10 +143,19 @@ auto TypeLiteralInfo::ForType(const File& file, ClassType class_type)
     return {.kind = None};
     return {.kind = None};
   }
   }
 
 
-  // The class must be declared in the `Core` package.
+  // The class must be declared in the `Core` package. We check for up to one
+  // level of enclosing namespace.
   const auto& class_info = file.classes().Get(class_type.class_id);
   const auto& class_info = file.classes().Get(class_type.class_id);
+  auto parent_scope_name_id = SemIR::NameId::None;
   if (!file.name_scopes().IsInCorePackageRoot(class_info.scope_id)) {
   if (!file.name_scopes().IsInCorePackageRoot(class_info.scope_id)) {
-    return {.kind = None};
+    if (!file.name_scopes().IsInCorePackageRoot(class_info.parent_scope_id)) {
+      return {.kind = None};
+    }
+    parent_scope_name_id =
+        file.name_scopes().Get(class_info.parent_scope_id).name_id();
+    if (!parent_scope_name_id.has_value()) {
+      return {.kind = None};
+    }
   }
   }
 
 
   // The class's name must be the name corresponding to a type literal.
   // The class's name must be the name corresponding to a type literal.
@@ -155,11 +164,24 @@ auto TypeLiteralInfo::ForType(const File& file, ClassType class_type)
     return {.kind = None};
     return {.kind = None};
   }
   }
 
 
-  Kind kind = llvm::StringSwitch<Kind>(*name_ident)
-                  .Case("Char", Char)
-                  .Case("String", Str)
-                  .Default(None);
-  return {.kind = kind};
+  if (!parent_scope_name_id.has_value()) {
+    Kind kind = llvm::StringSwitch<Kind>(*name_ident)
+                    .Case("Char", Char)
+                    .Case("String", Str)
+                    .Default(None);
+    return {.kind = kind};
+  }
+
+  auto parent_name_ident =
+      file.names().GetAsStringIfIdentifier(parent_scope_name_id);
+  if (parent_name_ident == "CppCompat") {
+    Kind kind = llvm::StringSwitch<Kind>(*name_ident)
+                    .Case("NullptrT", CppNullptrT)
+                    .Default(None);
+    return {.kind = kind};
+  }
+
+  return {.kind = None};
 }
 }
 
 
 auto TypeLiteralInfo::PrintLiteral(const File& file,
 auto TypeLiteralInfo::PrintLiteral(const File& file,
@@ -173,6 +195,9 @@ auto TypeLiteralInfo::PrintLiteral(const File& file,
     case Char:
     case Char:
       out << "char";
       out << "char";
       break;
       break;
+    case CppNullptrT:
+      out << "Cpp.nullptr_t";
+      break;
     case Str:
     case Str:
       out << "str";
       out << "str";
       break;
       break;

+ 3 - 0
toolchain/sem_ir/type_info.h

@@ -235,6 +235,9 @@ struct TypeLiteralInfo {
     Numeric,
     Numeric,
     // `char` / `Core.Char`.
     // `char` / `Core.Char`.
     Char,
     Char,
+    // `Cpp.nullptr_t` / `Core.CppCompat.NullptrT`.
+    // TODO: This isn't a type literal.
+    CppNullptrT,
     // `str` / `Core.String`.
     // `str` / `Core.String`.
     // TODO: Rename `Core.String` to `Core.Str`.
     // TODO: Rename `Core.String` to `Core.Str`.
     Str,
     Str,

+ 10 - 0
toolchain/sem_ir/typed_insts.h

@@ -1836,6 +1836,16 @@ struct UnboundElementType {
   TypeInstId element_type_inst_id;
   TypeInstId element_type_inst_id;
 };
 };
 
 
+// An uninitialized constant value.
+struct UninitializedValue {
+  static constexpr auto Kind =
+      InstKind::UninitializedValue.Define<Parse::NodeId>(
+          {.ir_name = "uninitialized_value",
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+};
+
 // Converts from a value expression to an ephemeral reference expression, in
 // Converts from a value expression to an ephemeral reference expression, in
 // the case where the value representation of the type is a pointer. For
 // the case where the value representation of the type is a pointer. For
 // example, when indexing a value expression of array type, this is used to
 // example, when indexing a value expression of array type, this is used to