Переглянути джерело

Improve support for qualification conversions. (#5999)

* Treat `MaybeUnformed` and `partial` as qualifiers, like `const`.
* Allow pointer conversions to add qualifiers.
* Allow unsafe pointer conversions to remove qualifiers.
* Allow conversions on non-reference expressions to drop `const`.
* Allow unsafe conversions on any expression to drop `const`.
* Allow unsafe conversions on non-initializing expressions to drop
  `partial`. For initializing expressions, we should initialize the
  vptr when dropping `partial`; this is not yet supported so we reject.
* Allow conversions on reference expressions to add `MaybeUnformed`.
* Allow unsafe conversions on reference expressions to drop
  `MaybeUnformed`. For non-reference expressions, additional work is
  required, because the value / initializing representation may not
  match between `T` and `MaybeUnformed(T)`, so those are rejected for
  now.
Richard Smith 8 місяців тому
батько
коміт
cb5e2e1597

+ 103 - 33
toolchain/check/convert.cpp

@@ -31,6 +31,7 @@
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst.h"
+#include "toolchain/sem_ir/type.h"
 #include "toolchain/sem_ir/typed_insts.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 
 // TODO: This contains a lot of recursion. Consider removing it in order to
 // TODO: This contains a lot of recursion. Consider removing it in order to
@@ -767,6 +768,58 @@ static auto CanUseValueOfInitializer(const SemIR::File& sem_ir,
   return InitReprIsCopyOfValueRepr(sem_ir, type_id);
   return InitReprIsCopyOfValueRepr(sem_ir, type_id);
 }
 }
 
 
+// Determine whether the given set of qualifiers can be added by a conversion
+// of an expression of the given category.
+static auto CanAddQualifiers(SemIR::TypeQualifiers quals,
+                             SemIR::ExprCategory cat) -> bool {
+  if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed) &&
+      !SemIR::IsRefCategory(cat)) {
+    // `MaybeUnformed(T)` may have a different value representation or
+    // initializing representation from `T`, so only allow it to be added for a
+    // reference expression.
+    // TODO: We should allow converting an initializing expression of type `T`
+    // to `MaybeUnformed(T)`. `PerformBuiltinConversion` will need to generate
+    // an `InPlaceInit` instruction when needed.
+    // NOLINTNEXTLINE(readability-simplify-boolean-expr)
+    return false;
+  }
+
+  // `const` and `partial` can always be added.
+  return true;
+}
+
+// Determine whether the given set of qualifiers can be removed by a conversion
+// of an expression of the given category.
+static auto CanRemoveQualifiers(SemIR::TypeQualifiers quals,
+                                SemIR::ExprCategory cat, bool allow_unsafe)
+    -> bool {
+  if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Const) && !allow_unsafe &&
+      SemIR::IsRefCategory(cat)) {
+    // Removing `const` is an unsafe conversion for a reference expression.
+    return false;
+  }
+
+  if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Partial) &&
+      (!allow_unsafe || cat == SemIR::ExprCategory::Initializing)) {
+    // TODO: Allow removing `partial` for initializing expressions as a safe
+    // conversion. `PerformBuiltinConversion` will need to initialize the vptr
+    // as part of the conversion.
+    return false;
+  }
+
+  if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed) &&
+      (!allow_unsafe || !SemIR::IsRefCategory(cat))) {
+    // As an unsafe conversion, `MaybeUnformed` can be removed from a reference
+    // expression.
+    // TODO: We should allow this for any kind of expression, and convert the
+    // result as needed if the representation of `T` differs from that of
+    // `MaybeUnformed(T)`.
+    return false;
+  }
+
+  return true;
+}
+
 static auto DiagnoseConversionFailureToConstraintValue(
 static auto DiagnoseConversionFailureToConstraintValue(
     Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
     Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
     SemIR::TypeId target_type_id) -> void {
     SemIR::TypeId target_type_id) -> void {
@@ -914,24 +967,32 @@ static auto PerformBuiltinConversion(
     }
     }
   }
   }
 
 
-  // T explicitly converts to U if T is compatible with U.
+  // T explicitly converts to U if T is compatible with U, and we're allowed to
+  // remove / add any qualifiers that differ.
   if (target.is_explicit_as() && target.type_id != value_type_id) {
   if (target.is_explicit_as() && target.type_id != value_type_id) {
-    auto target_foundation_id =
-        context.types().GetTransitiveAdaptedType(target.type_id);
-    auto value_foundation_id =
-        context.types().GetTransitiveAdaptedType(value_type_id);
+    auto [target_foundation_id, target_quals] =
+        context.types().GetTransitiveUnqualifiedAdaptedType(target.type_id);
+    auto [value_foundation_id, value_quals] =
+        context.types().GetTransitiveUnqualifiedAdaptedType(value_type_id);
     if (target_foundation_id == value_foundation_id) {
     if (target_foundation_id == value_foundation_id) {
-      // For a struct or tuple literal, perform a category conversion if
-      // necessary.
-      if (SemIR::GetExprCategory(context.sem_ir(), value_id) ==
-          SemIR::ExprCategory::Mixed) {
-        value_id = PerformBuiltinConversion(context, loc_id, value_id,
-                                            {.kind = ConversionTarget::Value,
-                                             .type_id = value_type_id,
-                                             .diagnose = target.diagnose});
+      auto category = SemIR::GetExprCategory(context.sem_ir(), value_id);
+      if (CanAddQualifiers(target_quals & ~value_quals, category) &&
+          CanRemoveQualifiers(
+              value_quals & ~target_quals, category,
+              target.kind == ConversionTarget::ExplicitUnsafeAs)) {
+        // For a struct or tuple literal, perform a category conversion if
+        // necessary.
+        if (category == SemIR::ExprCategory::Mixed) {
+          value_id = PerformBuiltinConversion(context, loc_id, value_id,
+                                              {.kind = ConversionTarget::Value,
+                                               .type_id = value_type_id,
+                                               .diagnose = target.diagnose});
+        }
+
+        return AddInst<SemIR::AsCompatible>(
+            context, loc_id,
+            {.type_id = target.type_id, .source_id = value_id});
       }
       }
-      return AddInst<SemIR::AsCompatible>(
-          context, loc_id, {.type_id = target.type_id, .source_id = value_id});
     }
     }
   }
   }
 
 
@@ -996,19 +1057,23 @@ static auto PerformBuiltinConversion(
     }
     }
   }
   }
 
 
-  // A pointer T* converts to [const] U* if T is the same as U, or is a class
-  // derived from U.
+  // A pointer T* converts to [qualified] U* if T is the same as U, or is a
+  // class derived from U.
   if (auto target_pointer_type = target_type_inst.TryAs<SemIR::PointerType>()) {
   if (auto target_pointer_type = target_type_inst.TryAs<SemIR::PointerType>()) {
     if (auto src_pointer_type =
     if (auto src_pointer_type =
             sem_ir.types().TryGetAs<SemIR::PointerType>(value_type_id)) {
             sem_ir.types().TryGetAs<SemIR::PointerType>(value_type_id)) {
+      auto target_pointee_id = context.types().GetTypeIdForTypeInstId(
+          target_pointer_type->pointee_id);
+      auto src_pointee_id =
+          context.types().GetTypeIdForTypeInstId(src_pointer_type->pointee_id);
+      // Try to complete the pointee types so that we can walk through adapters
+      // to their adapted types.
+      TryToCompleteType(context, target_pointee_id, loc_id);
+      TryToCompleteType(context, src_pointee_id, loc_id);
       auto [unqual_target_pointee_type_id, target_quals] =
       auto [unqual_target_pointee_type_id, target_quals] =
-          sem_ir.types().GetUnqualifiedTypeAndQualifiers(
-              context.types().GetTypeIdForTypeInstId(
-                  target_pointer_type->pointee_id));
+          sem_ir.types().GetTransitiveUnqualifiedAdaptedType(target_pointee_id);
       auto [unqual_src_pointee_type_id, src_quals] =
       auto [unqual_src_pointee_type_id, src_quals] =
-          sem_ir.types().GetUnqualifiedTypeAndQualifiers(
-              context.types().GetTypeIdForTypeInstId(
-                  src_pointer_type->pointee_id));
+          sem_ir.types().GetTransitiveUnqualifiedAdaptedType(src_pointee_id);
 
 
       // If the qualifiers are incompatible, we can't perform a conversion,
       // If the qualifiers are incompatible, we can't perform a conversion,
       // except with `unsafe as`.
       // except with `unsafe as`.
@@ -1366,25 +1431,30 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
       if (!target.diagnose) {
       if (!target.diagnose) {
         return context.emitter().BuildSuppressed();
         return context.emitter().BuildSuppressed();
       }
       }
+      int target_kind_for_diag =
+          target.kind == ConversionTarget::ExplicitAs         ? 1
+          : target.kind == ConversionTarget::ExplicitUnsafeAs ? 2
+                                                              : 0;
       if (target.type_id == SemIR::TypeType::TypeId ||
       if (target.type_id == SemIR::TypeType::TypeId ||
           sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {
           sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {
         CARBON_DIAGNOSTIC(
         CARBON_DIAGNOSTIC(
             ConversionFailureNonTypeToFacet, Error,
             ConversionFailureNonTypeToFacet, Error,
-            "cannot{0:| implicitly} convert non-type value of type {1} "
-            "{2:to|into type implementing} {3}{0: with `as`|}",
-            Diagnostics::BoolAsSelect, TypeOfInstId, Diagnostics::BoolAsSelect,
+            "cannot{0:=0: implicitly|:} convert non-type value of type {1} "
+            "{2:to|into type implementing} {3}"
+            "{0:=1: with `as`|=2: with `unsafe as`|:}",
+            Diagnostics::IntAsSelect, TypeOfInstId, Diagnostics::BoolAsSelect,
             SemIR::TypeId);
             SemIR::TypeId);
         return context.emitter().Build(
         return context.emitter().Build(
-            loc_id, ConversionFailureNonTypeToFacet, target.is_explicit_as(),
+            loc_id, ConversionFailureNonTypeToFacet, target_kind_for_diag,
             expr_id, target.type_id == SemIR::TypeType::TypeId, target.type_id);
             expr_id, target.type_id == SemIR::TypeType::TypeId, target.type_id);
       } else {
       } else {
-        CARBON_DIAGNOSTIC(ConversionFailure, Error,
-                          "cannot{0:| implicitly} convert expression of type "
-                          "{1} to {2}{0: with `as`|}",
-                          Diagnostics::BoolAsSelect, TypeOfInstId,
-                          SemIR::TypeId);
+        CARBON_DIAGNOSTIC(
+            ConversionFailure, Error,
+            "cannot{0:=0: implicitly|:} convert expression of type "
+            "{1} to {2}{0:=1: with `as`|=2: with `unsafe as`|:}",
+            Diagnostics::IntAsSelect, TypeOfInstId, SemIR::TypeId);
         return context.emitter().Build(loc_id, ConversionFailure,
         return context.emitter().Build(loc_id, ConversionFailure,
-                                       target.is_explicit_as(), expr_id,
+                                       target_kind_for_diag, expr_id,
                                        target.type_id);
                                        target.type_id);
       }
       }
     });
     });

+ 12 - 3
toolchain/check/import_ref.cpp

@@ -26,6 +26,7 @@
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/import_ir.h"
 #include "toolchain/sem_ir/import_ir.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst.h"
+#include "toolchain/sem_ir/inst_categories.h"
 #include "toolchain/sem_ir/inst_kind.h"
 #include "toolchain/sem_ir/inst_kind.h"
 #include "toolchain/sem_ir/type_info.h"
 #include "toolchain/sem_ir/type_info.h"
 #include "toolchain/sem_ir/typed_insts.h"
 #include "toolchain/sem_ir/typed_insts.h"
@@ -1823,14 +1824,16 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
                  .object_repr_type_inst_id = object_repr_type_inst_id});
                  .object_repr_type_inst_id = object_repr_type_inst_id});
 }
 }
 
 
-static auto TryResolveTypedInst(ImportRefResolver& resolver,
-                                SemIR::ConstType inst) -> ResolveResult {
+template <typename InstT>
+  requires SemIR::Internal::HasInstCategory<SemIR::AnyQualifiedType, InstT>
+static auto TryResolveTypedInst(ImportRefResolver& resolver, InstT inst)
+    -> ResolveResult {
   CARBON_CHECK(inst.type_id == SemIR::TypeType::TypeId);
   CARBON_CHECK(inst.type_id == SemIR::TypeType::TypeId);
   auto inner_id = GetLocalTypeInstId(resolver, inst.inner_id);
   auto inner_id = GetLocalTypeInstId(resolver, inst.inner_id);
   if (resolver.HasNewWork()) {
   if (resolver.HasNewWork()) {
     return ResolveResult::Retry();
     return ResolveResult::Retry();
   }
   }
-  return ResolveAsDeduplicated<SemIR::ConstType>(
+  return ResolveAsDeduplicated<InstT>(
       resolver, {.type_id = SemIR::TypeType::TypeId, .inner_id = inner_id});
       resolver, {.type_id = SemIR::TypeType::TypeId, .inner_id = inner_id});
 }
 }
 
 
@@ -3183,12 +3186,18 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver,
     case CARBON_KIND(SemIR::IntType inst): {
     case CARBON_KIND(SemIR::IntType inst): {
       return TryResolveTypedInst(resolver, inst);
       return TryResolveTypedInst(resolver, inst);
     }
     }
+    case CARBON_KIND(SemIR::MaybeUnformedType inst): {
+      return TryResolveTypedInst(resolver, inst);
+    }
     case CARBON_KIND(SemIR::Namespace inst): {
     case CARBON_KIND(SemIR::Namespace inst): {
       return TryResolveTypedInst(resolver, inst, inst_id);
       return TryResolveTypedInst(resolver, inst, inst_id);
     }
     }
     case CARBON_KIND(SemIR::OutParamPattern inst): {
     case CARBON_KIND(SemIR::OutParamPattern inst): {
       return TryResolveTypedInst(resolver, inst, inst_id);
       return TryResolveTypedInst(resolver, inst, inst_id);
     }
     }
+    case CARBON_KIND(SemIR::PartialType inst): {
+      return TryResolveTypedInst(resolver, inst);
+    }
     case CARBON_KIND(SemIR::PatternType inst): {
     case CARBON_KIND(SemIR::PatternType inst): {
       return TryResolveTypedInst(resolver, inst);
       return TryResolveTypedInst(resolver, inst);
     }
     }

+ 303 - 0
toolchain/check/testdata/as/const.carbon

@@ -0,0 +1,303 @@
+// 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/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/as/const.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/as/const.carbon
+
+// --- add_const.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+fn Init() -> X;
+let value: X = Init();
+var reference: X;
+let ptr: X* = &reference;
+
+fn Use() {
+  // TODO: Should some of these be valid without the `as`?
+  //@dump-sem-ir-begin
+  var i: const X = Init() as const X;
+  let v: const X = value as const X;
+  let a: const X* = &(reference as const X);
+  let b: const X* = ptr as const X*;
+  //@dump-sem-ir-end
+}
+
+// --- remove_const.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+fn Init() -> const X;
+let value: const X = Init();
+
+fn Use() {
+  // TODO: Should some of these be valid without the `as`?
+  //@dump-sem-ir-begin
+  var i: X = Init() as X;
+  let v: X = value as X;
+  //@dump-sem-ir-end
+}
+
+// --- fail_cannot_remove_const.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+var reference: const X;
+let ptr: const X* = &reference;
+
+fn Use() {
+  // CHECK:STDERR: fail_cannot_remove_const.carbon:[[@LINE+7]]:17: error: cannot convert expression of type `const X` to `X` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let a: X* = &(reference as X);
+  // CHECK:STDERR:                 ^~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_const.carbon:[[@LINE+4]]:17: note: type `const X` does not implement interface `Core.As(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let a: X* = &(reference as X);
+  // CHECK:STDERR:                 ^~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  let a: X* = &(reference as X);
+  // CHECK:STDERR: fail_cannot_remove_const.carbon:[[@LINE+7]]:15: error: cannot convert expression of type `const X*` to `X*` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let b: X* = ptr as X*;
+  // CHECK:STDERR:               ^~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_const.carbon:[[@LINE+4]]:15: note: type `const X*` does not implement interface `Core.As(X*)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let b: X* = ptr as X*;
+  // CHECK:STDERR:               ^~~~~~~~~
+  // CHECK:STDERR:
+  let b: X* = ptr as X*;
+}
+
+// --- unsafe_remove_const.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+var reference: const X;
+let ptr: const X* = &reference;
+
+fn Use() {
+  //@dump-sem-ir-begin
+  let a: X* = &(reference unsafe as X);
+  let b: X* = ptr unsafe as X*;
+  //@dump-sem-ir-end
+}
+
+// CHECK:STDOUT: --- add_const.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %Init.type: type = fn_type @Init [concrete]
+// CHECK:STDOUT:   %Init: %Init.type = struct_value () [concrete]
+// CHECK:STDOUT:   %const: type = const_type %X [concrete]
+// CHECK:STDOUT:   %pattern_type.d7f: type = pattern_type %const [concrete]
+// CHECK:STDOUT:   %ptr.cbd: type = ptr_type %const [concrete]
+// CHECK:STDOUT:   %pattern_type.855: type = pattern_type %ptr.cbd [concrete]
+// CHECK:STDOUT:   %reference.var: ref %const = var file.%reference.var_patt [concrete]
+// CHECK:STDOUT:   %addr.0c5: %ptr.cbd = addr_of %reference.var [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.type.f3e: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%const) [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.4e8: %T.as.Destroy.impl.Op.type.f3e = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %i.patt: %pattern_type.d7f = binding_pattern i [concrete]
+// CHECK:STDOUT:     %i.var_patt: %pattern_type.d7f = var_pattern %i.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %i.var: ref %const = var %i.var_patt
+// CHECK:STDOUT:   %Init.ref: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc14_3: ref %const = splice_block %i.var {}
+// CHECK:STDOUT:   %Init.call: init %X = call %Init.ref() to %.loc14_3
+// CHECK:STDOUT:   %X.ref.loc14_36: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %const.loc14_30: type = const_type %X.ref.loc14_36 [concrete = constants.%const]
+// CHECK:STDOUT:   %.loc14_27.1: init %const = as_compatible %Init.call
+// CHECK:STDOUT:   %.loc14_27.2: init %const = converted %Init.call, %.loc14_27.1
+// CHECK:STDOUT:   assign %i.var, %.loc14_27.2
+// CHECK:STDOUT:   %.loc14_10: type = splice_block %const.loc14_10 [concrete = constants.%const] {
+// CHECK:STDOUT:     %X.ref.loc14_16: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %const.loc14_10: type = const_type %X.ref.loc14_16 [concrete = constants.%const]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %i: ref %const = bind_name i, %i.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %v.patt: %pattern_type.d7f = binding_pattern v [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %value.ref: %X = name_ref value, file.%value
+// CHECK:STDOUT:   %X.ref.loc15_35: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %const.loc15_29: type = const_type %X.ref.loc15_35 [concrete = constants.%const]
+// CHECK:STDOUT:   %.loc15_26.1: %const = as_compatible %value.ref
+// CHECK:STDOUT:   %.loc15_26.2: %const = converted %value.ref, %.loc15_26.1
+// CHECK:STDOUT:   %.loc15_10: type = splice_block %const.loc15_10 [concrete = constants.%const] {
+// CHECK:STDOUT:     %X.ref.loc15_16: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %const.loc15_10: type = const_type %X.ref.loc15_16 [concrete = constants.%const]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %v: %const = bind_name v, %.loc15_26.2
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.855 = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %reference.ref: ref %X = name_ref reference, file.%reference [concrete = file.%reference.var]
+// CHECK:STDOUT:   %X.ref.loc16_42: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %const.loc16_36: type = const_type %X.ref.loc16_42 [concrete = constants.%const]
+// CHECK:STDOUT:   %.loc16_33.1: ref %const = as_compatible %reference.ref [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %.loc16_33.2: ref %const = converted %reference.ref, %.loc16_33.1 [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %addr.loc16: %ptr.cbd = addr_of %.loc16_33.2 [concrete = constants.%addr.0c5]
+// CHECK:STDOUT:   %.loc16_17: type = splice_block %ptr.loc16 [concrete = constants.%ptr.cbd] {
+// CHECK:STDOUT:     %X.ref.loc16_16: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %const.loc16_10: type = const_type %X.ref.loc16_16 [concrete = constants.%const]
+// CHECK:STDOUT:     %ptr.loc16: type = ptr_type %const.loc16_10 [concrete = constants.%ptr.cbd]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %ptr.cbd = bind_name a, %addr.loc16
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.855 = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ptr.ref: %ptr.d17 = name_ref ptr, file.%ptr.loc9_5
+// CHECK:STDOUT:   %X.ref.loc17_34: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %const.loc17_28: type = const_type %X.ref.loc17_34 [concrete = constants.%const]
+// CHECK:STDOUT:   %ptr.loc17_35: type = ptr_type %const.loc17_28 [concrete = constants.%ptr.cbd]
+// CHECK:STDOUT:   %.loc17_25.1: %ptr.cbd = as_compatible %ptr.ref
+// CHECK:STDOUT:   %.loc17_25.2: %ptr.cbd = converted %ptr.ref, %.loc17_25.1
+// CHECK:STDOUT:   %.loc17_17: type = splice_block %ptr.loc17_17 [concrete = constants.%ptr.cbd] {
+// CHECK:STDOUT:     %X.ref.loc17_16: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %const.loc17_10: type = const_type %X.ref.loc17_16 [concrete = constants.%const]
+// CHECK:STDOUT:     %ptr.loc17_17: type = ptr_type %const.loc17_10 [concrete = constants.%ptr.cbd]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %ptr.cbd = bind_name b, %.loc17_25.2
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc14_3.1: <bound method> = bound_method %.loc14_3, constants.%T.as.Destroy.impl.Op.4e8
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc14_3.1: <bound method> = bound_method %.loc14_3, %T.as.Destroy.impl.Op.specific_fn.1
+// CHECK:STDOUT:   %addr.loc14_3.1: %ptr.cbd = addr_of %.loc14_3
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc14_3.1: init %empty_tuple.type = call %bound_method.loc14_3.1(%addr.loc14_3.1)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc14_3.2: <bound method> = bound_method %i.var, constants.%T.as.Destroy.impl.Op.4e8
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc14_3.2: <bound method> = bound_method %i.var, %T.as.Destroy.impl.Op.specific_fn.2
+// CHECK:STDOUT:   %addr.loc14_3.2: %ptr.cbd = addr_of %i.var
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc14_3.2: init %empty_tuple.type = call %bound_method.loc14_3.2(%addr.loc14_3.2)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- remove_const.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.type: type = fn_type @X.as.Destroy.impl.Op [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op: %X.as.Destroy.impl.Op.type = struct_value () [concrete]
+// CHECK:STDOUT:   %const: type = const_type %X [concrete]
+// CHECK:STDOUT:   %Init.type: type = fn_type @Init [concrete]
+// CHECK:STDOUT:   %Init: %Init.type = struct_value () [concrete]
+// CHECK:STDOUT:   %pattern_type.019: type = pattern_type %X [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %i.patt: %pattern_type.019 = binding_pattern i [concrete]
+// CHECK:STDOUT:     %i.var_patt: %pattern_type.019 = var_pattern %i.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %i.var: ref %X = var %i.var_patt
+// CHECK:STDOUT:   %Init.ref: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc12_3: ref %X = splice_block %i.var {}
+// CHECK:STDOUT:   %Init.call: init %const = call %Init.ref() to %.loc12_3
+// CHECK:STDOUT:   %X.ref.loc12_24: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc12_21.1: init %X = as_compatible %Init.call
+// CHECK:STDOUT:   %.loc12_21.2: init %X = converted %Init.call, %.loc12_21.1
+// CHECK:STDOUT:   assign %i.var, %.loc12_21.2
+// CHECK:STDOUT:   %X.ref.loc12_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %i: ref %X = bind_name i, %i.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %v.patt: %pattern_type.019 = binding_pattern v [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %value.ref: %const = name_ref value, file.%value
+// CHECK:STDOUT:   %X.ref.loc13_23: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc13_20.1: %X = as_compatible %value.ref
+// CHECK:STDOUT:   %.loc13_20.2: %X = converted %value.ref, %.loc13_20.1
+// CHECK:STDOUT:   %X.ref.loc13_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %v: %X = bind_name v, %.loc13_20.2
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.bound.loc12_3.1: <bound method> = bound_method %.loc12_3, constants.%X.as.Destroy.impl.Op
+// CHECK:STDOUT:   %addr.loc12_3.1: %ptr.d17 = addr_of %.loc12_3
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.call.loc12_3.1: init %empty_tuple.type = call %X.as.Destroy.impl.Op.bound.loc12_3.1(%addr.loc12_3.1)
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.bound.loc12_3.2: <bound method> = bound_method %i.var, constants.%X.as.Destroy.impl.Op
+// CHECK:STDOUT:   %addr.loc12_3.2: %ptr.d17 = addr_of %i.var
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.call.loc12_3.2: init %empty_tuple.type = call %X.as.Destroy.impl.Op.bound.loc12_3.2(%addr.loc12_3.2)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- unsafe_remove_const.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %pattern_type.1c6: type = pattern_type %ptr.d17 [concrete]
+// CHECK:STDOUT:   %const: type = const_type %X [concrete]
+// CHECK:STDOUT:   %ptr.cbd: type = ptr_type %const [concrete]
+// CHECK:STDOUT:   %reference.var: ref %X = var file.%reference.var_patt [concrete]
+// CHECK:STDOUT:   %addr.373: %ptr.d17 = addr_of %reference.var [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.1c6 = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %reference.ref: ref %const = name_ref reference, file.%reference [concrete = file.%reference.var]
+// CHECK:STDOUT:   %X.ref.loc11_37: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc11_34.1: ref %X = as_compatible %reference.ref [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %.loc11_34.2: ref %X = converted %reference.ref, %.loc11_34.1 [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %addr: %ptr.d17 = addr_of %.loc11_34.2 [concrete = constants.%addr.373]
+// CHECK:STDOUT:   %.loc11_11: type = splice_block %ptr.loc11 [concrete = constants.%ptr.d17] {
+// CHECK:STDOUT:     %X.ref.loc11_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %ptr.loc11: type = ptr_type %X.ref.loc11_10 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %ptr.d17 = bind_name a, %addr
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.1c6 = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ptr.ref: %ptr.cbd = name_ref ptr, file.%ptr.loc7_5
+// CHECK:STDOUT:   %X.ref.loc12_29: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %ptr.loc12_30: type = ptr_type %X.ref.loc12_29 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   %.loc12_26.1: %ptr.d17 = as_compatible %ptr.ref
+// CHECK:STDOUT:   %.loc12_26.2: %ptr.d17 = converted %ptr.ref, %.loc12_26.1
+// CHECK:STDOUT:   %.loc12_11: type = splice_block %ptr.loc12_11 [concrete = constants.%ptr.d17] {
+// CHECK:STDOUT:     %X.ref.loc12_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %ptr.loc12_11: type = ptr_type %X.ref.loc12_10 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %ptr.d17 = bind_name b, %.loc12_26.2
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 366 - 0
toolchain/check/testdata/as/maybe_unformed.carbon

@@ -0,0 +1,366 @@
+// 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/unformed.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/as/maybe_unformed.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/as/maybe_unformed.carbon
+
+// --- add_unformed.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+var reference: X;
+let ptr: X* = &reference;
+
+fn Use() {
+  // TODO: Should some of these be valid without the `as`?
+  //@dump-sem-ir-begin
+  let a: Core.MaybeUnformed(X)* = &(reference as Core.MaybeUnformed(X));
+  let b: Core.MaybeUnformed(X)* = ptr as Core.MaybeUnformed(X)*;
+  //@dump-sem-ir-end
+}
+
+// --- fail_todo_add_unformed_nonref.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+fn Init() -> X;
+let value: X = Init();
+
+fn Use() {
+  // TODO: These should probably be valid.
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_add_unformed_nonref.carbon:[[@LINE+7]]:34: error: cannot convert expression of type `X` to `Core.MaybeUnformed(X)` with `as` [ConversionFailure]
+  // CHECK:STDERR:   var i: Core.MaybeUnformed(X) = Init() as Core.MaybeUnformed(X);
+  // CHECK:STDERR:                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_todo_add_unformed_nonref.carbon:[[@LINE+4]]:34: note: type `X` does not implement interface `Core.As(Core.MaybeUnformed(X))` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   var i: Core.MaybeUnformed(X) = Init() as Core.MaybeUnformed(X);
+  // CHECK:STDERR:                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var i: Core.MaybeUnformed(X) = Init() as Core.MaybeUnformed(X);
+  // CHECK:STDERR: fail_todo_add_unformed_nonref.carbon:[[@LINE+7]]:34: error: cannot convert expression of type `X` to `Core.MaybeUnformed(X)` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let v: Core.MaybeUnformed(X) = value as Core.MaybeUnformed(X);
+  // CHECK:STDERR:                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_todo_add_unformed_nonref.carbon:[[@LINE+4]]:34: note: type `X` does not implement interface `Core.As(Core.MaybeUnformed(X))` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let v: Core.MaybeUnformed(X) = value as Core.MaybeUnformed(X);
+  // CHECK:STDERR:                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  let v: Core.MaybeUnformed(X) = value as Core.MaybeUnformed(X);
+  //@dump-sem-ir-end
+}
+
+// --- fail_cannot_remove_unformed.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+fn Init() -> Core.MaybeUnformed(X);
+let value: Core.MaybeUnformed(X) = Init();
+var reference: Core.MaybeUnformed(X);
+let ptr: Core.MaybeUnformed(X)* = &reference;
+
+fn Use() {
+  // CHECK:STDERR: fail_cannot_remove_unformed.carbon:[[@LINE+7]]:14: error: cannot convert expression of type `Core.MaybeUnformed(X)` to `X` with `as` [ConversionFailure]
+  // CHECK:STDERR:   var i: X = Init() as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_unformed.carbon:[[@LINE+4]]:14: note: type `Core.MaybeUnformed(X)` does not implement interface `Core.As(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   var i: X = Init() as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~
+  // CHECK:STDERR:
+  var i: X = Init() as X;
+  // CHECK:STDERR: fail_cannot_remove_unformed.carbon:[[@LINE+7]]:14: error: cannot convert expression of type `Core.MaybeUnformed(X)` to `X` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let v: X = value as X;
+  // CHECK:STDERR:              ^~~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_unformed.carbon:[[@LINE+4]]:14: note: type `Core.MaybeUnformed(X)` does not implement interface `Core.As(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let v: X = value as X;
+  // CHECK:STDERR:              ^~~~~~~~~~
+  // CHECK:STDERR:
+  let v: X = value as X;
+  // CHECK:STDERR: fail_cannot_remove_unformed.carbon:[[@LINE+7]]:17: error: cannot convert expression of type `Core.MaybeUnformed(X)` to `X` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let a: X* = &(reference as X);
+  // CHECK:STDERR:                 ^~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_unformed.carbon:[[@LINE+4]]:17: note: type `Core.MaybeUnformed(X)` does not implement interface `Core.As(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let a: X* = &(reference as X);
+  // CHECK:STDERR:                 ^~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  let a: X* = &(reference as X);
+  // CHECK:STDERR: fail_cannot_remove_unformed.carbon:[[@LINE+7]]:15: error: cannot convert expression of type `Core.MaybeUnformed(X)*` to `X*` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let b: X* = ptr as X*;
+  // CHECK:STDERR:               ^~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_unformed.carbon:[[@LINE+4]]:15: note: type `Core.MaybeUnformed(X)*` does not implement interface `Core.As(X*)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let b: X* = ptr as X*;
+  // CHECK:STDERR:               ^~~~~~~~~
+  // CHECK:STDERR:
+  let b: X* = ptr as X*;
+}
+
+// --- unsafe_remove_unformed.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+var reference: Core.MaybeUnformed(X);
+let ptr: Core.MaybeUnformed(X)* = &reference;
+
+fn Use() {
+  //@dump-sem-ir-begin
+  let a: X* = &(reference unsafe as X);
+  let b: X* = ptr unsafe as X*;
+  //@dump-sem-ir-end
+}
+
+// --- fail_todo_remove_unformed_unsafe_notref.carbon
+
+library "[[@TEST_NAME]]";
+
+class X {}
+
+fn Init() -> Core.MaybeUnformed(X);
+let value: Core.MaybeUnformed(X) = Init();
+
+fn Use() {
+  // TODO: These should probably be valid.
+  // CHECK:STDERR: fail_todo_remove_unformed_unsafe_notref.carbon:[[@LINE+7]]:14: error: cannot convert expression of type `Core.MaybeUnformed(X)` to `X` with `unsafe as` [ConversionFailure]
+  // CHECK:STDERR:   var i: X = Init() unsafe as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_todo_remove_unformed_unsafe_notref.carbon:[[@LINE+4]]:14: note: type `Core.MaybeUnformed(X)` does not implement interface `Core.UnsafeAs(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   var i: X = Init() unsafe as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var i: X = Init() unsafe as X;
+  // CHECK:STDERR: fail_todo_remove_unformed_unsafe_notref.carbon:[[@LINE+7]]:14: error: cannot convert expression of type `Core.MaybeUnformed(X)` to `X` with `unsafe as` [ConversionFailure]
+  // CHECK:STDERR:   let v: X = value unsafe as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_todo_remove_unformed_unsafe_notref.carbon:[[@LINE+4]]:14: note: type `Core.MaybeUnformed(X)` does not implement interface `Core.UnsafeAs(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let v: X = value unsafe as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  let v: X = value unsafe as X;
+}
+
+// CHECK:STDOUT: --- add_unformed.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.type: type = generic_class_type @MaybeUnformed [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.generic: %MaybeUnformed.type = struct_value () [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.275: type = class_type @MaybeUnformed, @MaybeUnformed(%X) [concrete]
+// CHECK:STDOUT:   %ptr.58e: type = ptr_type %MaybeUnformed.275 [concrete]
+// CHECK:STDOUT:   %pattern_type.460: type = pattern_type %ptr.58e [concrete]
+// CHECK:STDOUT:   %reference.var: ref %MaybeUnformed.275 = var file.%reference.var_patt [concrete]
+// CHECK:STDOUT:   %addr.10a: %ptr.58e = addr_of %reference.var [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     .Destroy = %Core.Destroy
+// CHECK:STDOUT:     .MaybeUnformed = %Core.MaybeUnformed
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
+// CHECK:STDOUT:   %Core.MaybeUnformed: %MaybeUnformed.type = import_ref Core//prelude/parts/maybe_unformed, MaybeUnformed, loaded [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.460 = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %reference.ref: ref %X = name_ref reference, file.%reference [concrete = file.%reference.var]
+// CHECK:STDOUT:   %Core.ref.loc12_50: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:   %MaybeUnformed.ref.loc12_54: %MaybeUnformed.type = name_ref MaybeUnformed, imports.%Core.MaybeUnformed [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:   %X.ref.loc12_69: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %MaybeUnformed.loc12_70: type = class_type @MaybeUnformed, @MaybeUnformed(constants.%X) [concrete = constants.%MaybeUnformed.275]
+// CHECK:STDOUT:   %.loc12_47.1: ref %MaybeUnformed.275 = as_compatible %reference.ref [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %.loc12_47.2: ref %MaybeUnformed.275 = converted %reference.ref, %.loc12_47.1 [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %addr: %ptr.58e = addr_of %.loc12_47.2 [concrete = constants.%addr.10a]
+// CHECK:STDOUT:   %.loc12_31: type = splice_block %ptr.loc12 [concrete = constants.%ptr.58e] {
+// CHECK:STDOUT:     %Core.ref.loc12_10: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:     %MaybeUnformed.ref.loc12_14: %MaybeUnformed.type = name_ref MaybeUnformed, imports.%Core.MaybeUnformed [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:     %X.ref.loc12_29: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %MaybeUnformed.loc12_30: type = class_type @MaybeUnformed, @MaybeUnformed(constants.%X) [concrete = constants.%MaybeUnformed.275]
+// CHECK:STDOUT:     %ptr.loc12: type = ptr_type %MaybeUnformed.loc12_30 [concrete = constants.%ptr.58e]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %ptr.58e = bind_name a, %addr
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.460 = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ptr.ref: %ptr.d17 = name_ref ptr, file.%ptr.loc7_5
+// CHECK:STDOUT:   %Core.ref.loc13_42: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:   %MaybeUnformed.ref.loc13_46: %MaybeUnformed.type = name_ref MaybeUnformed, imports.%Core.MaybeUnformed [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:   %X.ref.loc13_61: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %MaybeUnformed.loc13_62: type = class_type @MaybeUnformed, @MaybeUnformed(constants.%X) [concrete = constants.%MaybeUnformed.275]
+// CHECK:STDOUT:   %ptr.loc13_63: type = ptr_type %MaybeUnformed.loc13_62 [concrete = constants.%ptr.58e]
+// CHECK:STDOUT:   %.loc13_39.1: %ptr.58e = as_compatible %ptr.ref
+// CHECK:STDOUT:   %.loc13_39.2: %ptr.58e = converted %ptr.ref, %.loc13_39.1
+// CHECK:STDOUT:   %.loc13_31: type = splice_block %ptr.loc13_31 [concrete = constants.%ptr.58e] {
+// CHECK:STDOUT:     %Core.ref.loc13_10: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:     %MaybeUnformed.ref.loc13_14: %MaybeUnformed.type = name_ref MaybeUnformed, imports.%Core.MaybeUnformed [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:     %X.ref.loc13_29: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %MaybeUnformed.loc13_30: type = class_type @MaybeUnformed, @MaybeUnformed(constants.%X) [concrete = constants.%MaybeUnformed.275]
+// CHECK:STDOUT:     %ptr.loc13_31: type = ptr_type %MaybeUnformed.loc13_30 [concrete = constants.%ptr.58e]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %ptr.58e = bind_name b, %.loc13_39.2
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_add_unformed_nonref.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %Destroy.type: type = facet_type <@Destroy> [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.type: type = fn_type @X.as.Destroy.impl.Op [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op: %X.as.Destroy.impl.Op.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Init.type: type = fn_type @Init [concrete]
+// CHECK:STDOUT:   %Init: %Init.type = struct_value () [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.type: type = generic_class_type @MaybeUnformed [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.generic: %MaybeUnformed.type = struct_value () [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.275: type = class_type @MaybeUnformed, @MaybeUnformed(%X) [concrete]
+// CHECK:STDOUT:   %pattern_type.eb8: type = pattern_type %MaybeUnformed.275 [concrete]
+// CHECK:STDOUT:   %As.type.90f: type = generic_interface_type @As [concrete]
+// CHECK:STDOUT:   %As.generic: %As.type.90f = struct_value () [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.as.Destroy.impl.Op.type.3bf: type = fn_type @MaybeUnformed.as.Destroy.impl.Op, @MaybeUnformed.as.Destroy.impl(%X) [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.as.Destroy.impl.Op.b85: %MaybeUnformed.as.Destroy.impl.Op.type.3bf = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.58e: type = ptr_type %MaybeUnformed.275 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     .Destroy = %Core.Destroy
+// CHECK:STDOUT:     .MaybeUnformed = %Core.MaybeUnformed
+// CHECK:STDOUT:     .As = %Core.As
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
+// CHECK:STDOUT:   %Core.MaybeUnformed: %MaybeUnformed.type = import_ref Core//prelude/parts/maybe_unformed, MaybeUnformed, loaded [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:   %Core.As: %As.type.90f = import_ref Core//prelude/parts/as, As, loaded [concrete = constants.%As.generic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %i.patt: %pattern_type.eb8 = binding_pattern i [concrete]
+// CHECK:STDOUT:     %i.var_patt: %pattern_type.eb8 = var_pattern %i.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %i.var: ref %MaybeUnformed.275 = var %i.var_patt
+// CHECK:STDOUT:   %Init.ref: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc19_39: ref %X = temporary_storage
+// CHECK:STDOUT:   %Init.call: init %X = call %Init.ref() to %.loc19_39
+// CHECK:STDOUT:   %Core.ref.loc19_44: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:   %MaybeUnformed.ref.loc19_48: %MaybeUnformed.type = name_ref MaybeUnformed, imports.%Core.MaybeUnformed [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:   %X.ref.loc19_63: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %MaybeUnformed.loc19_64: type = class_type @MaybeUnformed, @MaybeUnformed(constants.%X) [concrete = constants.%MaybeUnformed.275]
+// CHECK:STDOUT:   %.loc19_41: %MaybeUnformed.275 = converted %Init.call, <error> [concrete = <error>]
+// CHECK:STDOUT:   assign %i.var, <error>
+// CHECK:STDOUT:   %.loc19_30: type = splice_block %MaybeUnformed.loc19_30 [concrete = constants.%MaybeUnformed.275] {
+// CHECK:STDOUT:     %Core.ref.loc19_10: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:     %MaybeUnformed.ref.loc19_14: %MaybeUnformed.type = name_ref MaybeUnformed, imports.%Core.MaybeUnformed [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:     %X.ref.loc19_29: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %MaybeUnformed.loc19_30: type = class_type @MaybeUnformed, @MaybeUnformed(constants.%X) [concrete = constants.%MaybeUnformed.275]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %i: ref %MaybeUnformed.275 = bind_name i, %i.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %v.patt: %pattern_type.eb8 = binding_pattern v [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %value.ref: %X = name_ref value, file.%value
+// CHECK:STDOUT:   %Core.ref.loc27_43: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:   %MaybeUnformed.ref.loc27_47: %MaybeUnformed.type = name_ref MaybeUnformed, imports.%Core.MaybeUnformed [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:   %X.ref.loc27_62: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %MaybeUnformed.loc27_63: type = class_type @MaybeUnformed, @MaybeUnformed(constants.%X) [concrete = constants.%MaybeUnformed.275]
+// CHECK:STDOUT:   %.loc27_40: %MaybeUnformed.275 = converted %value.ref, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc27_30: type = splice_block %MaybeUnformed.loc27_30 [concrete = constants.%MaybeUnformed.275] {
+// CHECK:STDOUT:     %Core.ref.loc27_10: <namespace> = name_ref Core, imports.%Core [concrete = imports.%Core]
+// CHECK:STDOUT:     %MaybeUnformed.ref.loc27_14: %MaybeUnformed.type = name_ref MaybeUnformed, imports.%Core.MaybeUnformed [concrete = constants.%MaybeUnformed.generic]
+// CHECK:STDOUT:     %X.ref.loc27_29: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %MaybeUnformed.loc27_30: type = class_type @MaybeUnformed, @MaybeUnformed(constants.%X) [concrete = constants.%MaybeUnformed.275]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %v: %MaybeUnformed.275 = bind_name v, <error> [concrete = <error>]
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc19_39, constants.%X.as.Destroy.impl.Op
+// CHECK:STDOUT:   %addr.loc19_39: %ptr.d17 = addr_of %.loc19_39
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.call: init %empty_tuple.type = call %X.as.Destroy.impl.Op.bound(%addr.loc19_39)
+// CHECK:STDOUT:   %MaybeUnformed.as.Destroy.impl.Op.bound: <bound method> = bound_method %i.var, constants.%MaybeUnformed.as.Destroy.impl.Op.b85
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %i.var, %MaybeUnformed.as.Destroy.impl.Op.specific_fn
+// CHECK:STDOUT:   %addr.loc19_3: %ptr.58e = addr_of %i.var
+// CHECK:STDOUT:   %MaybeUnformed.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc19_3)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- unsafe_remove_unformed.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %pattern_type.1c6: type = pattern_type %ptr.d17 [concrete]
+// CHECK:STDOUT:   %MaybeUnformed.275: type = class_type @MaybeUnformed, @MaybeUnformed(%X) [concrete]
+// CHECK:STDOUT:   %ptr.58e: type = ptr_type %MaybeUnformed.275 [concrete]
+// CHECK:STDOUT:   %reference.var: ref %X = var file.%reference.var_patt [concrete]
+// CHECK:STDOUT:   %addr.a46: %ptr.d17 = addr_of %reference.var [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.1c6 = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %reference.ref: ref %MaybeUnformed.275 = name_ref reference, file.%reference [concrete = file.%reference.var]
+// CHECK:STDOUT:   %X.ref.loc11_37: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc11_34.1: ref %X = as_compatible %reference.ref [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %.loc11_34.2: ref %X = converted %reference.ref, %.loc11_34.1 [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %addr: %ptr.d17 = addr_of %.loc11_34.2 [concrete = constants.%addr.a46]
+// CHECK:STDOUT:   %.loc11_11: type = splice_block %ptr.loc11 [concrete = constants.%ptr.d17] {
+// CHECK:STDOUT:     %X.ref.loc11_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %ptr.loc11: type = ptr_type %X.ref.loc11_10 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %ptr.d17 = bind_name a, %addr
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.1c6 = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ptr.ref: %ptr.58e = name_ref ptr, file.%ptr.loc7_5
+// CHECK:STDOUT:   %X.ref.loc12_29: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %ptr.loc12_30: type = ptr_type %X.ref.loc12_29 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   %.loc12_26.1: %ptr.d17 = as_compatible %ptr.ref
+// CHECK:STDOUT:   %.loc12_26.2: %ptr.d17 = converted %ptr.ref, %.loc12_26.1
+// CHECK:STDOUT:   %.loc12_11: type = splice_block %ptr.loc12_11 [concrete = constants.%ptr.d17] {
+// CHECK:STDOUT:     %X.ref.loc12_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %ptr.loc12_11: type = ptr_type %X.ref.loc12_10 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %ptr.d17 = bind_name b, %.loc12_26.2
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 380 - 0
toolchain/check/testdata/as/partial.carbon

@@ -0,0 +1,380 @@
+// 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/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/as/partial.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/as/partial.carbon
+
+// --- add_partial.carbon
+
+library "[[@TEST_NAME]]";
+
+base class X {}
+
+fn Init() -> X;
+let value: X = Init();
+var reference: X;
+let ptr: X* = &reference;
+
+fn Use() {
+  // TODO: Should some of these be valid without the `as`?
+  //@dump-sem-ir-begin
+  var i: partial X = Init() as partial X;
+  let v: partial X = value as partial X;
+  let a: partial X* = &(reference as partial X);
+  let b: partial X* = ptr as partial X*;
+  //@dump-sem-ir-end
+}
+
+// --- fail_todo_remove_partial_in_init.carbon
+
+library "[[@TEST_NAME]]";
+
+base class X {}
+
+fn Init() -> partial X;
+
+fn Use() {
+  //@dump-sem-ir-begin
+  // TODO: This should be valid, and should initialize the vptr. The explicit `as` should probably not be necessary.
+  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+7]]:3: error: cannot implicitly convert expression of type `partial X` to `X` [ConversionFailure]
+  // CHECK:STDERR:   var i: X = Init();
+  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+4]]:3: note: type `partial X` does not implement interface `Core.ImplicitAs(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   var i: X = Init();
+  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR:
+  var i: X = Init();
+  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+7]]:14: error: cannot convert expression of type `partial X` to `X` with `as` [ConversionFailure]
+  // CHECK:STDERR:   var j: X = Init() as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~
+  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+4]]:14: note: type `partial X` does not implement interface `Core.As(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   var j: X = Init() as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~
+  // CHECK:STDERR:
+  var j: X = Init() as X;
+  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+7]]:14: error: cannot convert expression of type `partial X` to `X` with `unsafe as` [ConversionFailure]
+  // CHECK:STDERR:   var k: X = Init() unsafe as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_todo_remove_partial_in_init.carbon:[[@LINE+4]]:14: note: type `partial X` does not implement interface `Core.UnsafeAs(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   var k: X = Init() unsafe as X;
+  // CHECK:STDERR:              ^~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  var k: X = Init() unsafe as X;
+  //@dump-sem-ir-end
+}
+
+// --- fail_cannot_remove_partial.carbon
+
+library "[[@TEST_NAME]]";
+
+base class X {}
+
+fn Init() -> partial X;
+
+let value: partial X = Init();
+var reference: partial X;
+let ptr: partial X* = &reference;
+
+fn Use() {
+  // CHECK:STDERR: fail_cannot_remove_partial.carbon:[[@LINE+7]]:14: error: cannot convert expression of type `partial X` to `X` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let v: X = value as X;
+  // CHECK:STDERR:              ^~~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_partial.carbon:[[@LINE+4]]:14: note: type `partial X` does not implement interface `Core.As(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let v: X = value as X;
+  // CHECK:STDERR:              ^~~~~~~~~~
+  // CHECK:STDERR:
+  let v: X = value as X;
+  // CHECK:STDERR: fail_cannot_remove_partial.carbon:[[@LINE+7]]:17: error: cannot convert expression of type `partial X` to `X` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let a: X* = &(reference as X);
+  // CHECK:STDERR:                 ^~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_partial.carbon:[[@LINE+4]]:17: note: type `partial X` does not implement interface `Core.As(X)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let a: X* = &(reference as X);
+  // CHECK:STDERR:                 ^~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  let a: X* = &(reference as X);
+  // CHECK:STDERR: fail_cannot_remove_partial.carbon:[[@LINE+7]]:15: error: cannot convert expression of type `partial X*` to `X*` with `as` [ConversionFailure]
+  // CHECK:STDERR:   let b: X* = ptr as X*;
+  // CHECK:STDERR:               ^~~~~~~~~
+  // CHECK:STDERR: fail_cannot_remove_partial.carbon:[[@LINE+4]]:15: note: type `partial X*` does not implement interface `Core.As(X*)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let b: X* = ptr as X*;
+  // CHECK:STDERR:               ^~~~~~~~~
+  // CHECK:STDERR:
+  let b: X* = ptr as X*;
+}
+
+// --- unsafe_remove_partial.carbon
+
+library "[[@TEST_NAME]]";
+
+base class X {}
+
+fn Init() -> partial X;
+
+let value: partial X = Init();
+var reference: partial X;
+let ptr: partial X* = &reference;
+
+fn Use() {
+  //@dump-sem-ir-begin
+  let v: X = value unsafe as X;
+  let a: X* = &(reference unsafe as X);
+  let b: X* = ptr unsafe as X*;
+  //@dump-sem-ir-end
+}
+
+// CHECK:STDOUT: --- add_partial.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %Init.type: type = fn_type @Init [concrete]
+// CHECK:STDOUT:   %Init: %Init.type = struct_value () [concrete]
+// CHECK:STDOUT:   %.e71: type = partial_type %X [concrete]
+// CHECK:STDOUT:   %pattern_type.a53: type = pattern_type %.e71 [concrete]
+// CHECK:STDOUT:   %ptr.7b2: type = ptr_type %.e71 [concrete]
+// CHECK:STDOUT:   %pattern_type.46e: type = pattern_type %ptr.7b2 [concrete]
+// CHECK:STDOUT:   %reference.var: ref %.e71 = var file.%reference.var_patt [concrete]
+// CHECK:STDOUT:   %addr.e01: %ptr.7b2 = addr_of %reference.var [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.type.057: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%.e71) [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.d31: %T.as.Destroy.impl.Op.type.057 = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %i.patt: %pattern_type.a53 = binding_pattern i [concrete]
+// CHECK:STDOUT:     %i.var_patt: %pattern_type.a53 = var_pattern %i.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %i.var: ref %.e71 = var %i.var_patt
+// CHECK:STDOUT:   %Init.ref: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc14_3: ref %.e71 = splice_block %i.var {}
+// CHECK:STDOUT:   %Init.call: init %X = call %Init.ref() to %.loc14_3
+// CHECK:STDOUT:   %X.ref.loc14_40: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc14_32: type = partial_type %X.ref.loc14_40 [concrete = constants.%.e71]
+// CHECK:STDOUT:   %.loc14_29.1: init %.e71 = as_compatible %Init.call
+// CHECK:STDOUT:   %.loc14_29.2: init %.e71 = converted %Init.call, %.loc14_29.1
+// CHECK:STDOUT:   assign %i.var, %.loc14_29.2
+// CHECK:STDOUT:   %.loc14_10.1: type = splice_block %.loc14_10.2 [concrete = constants.%.e71] {
+// CHECK:STDOUT:     %X.ref.loc14_18: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %.loc14_10.2: type = partial_type %X.ref.loc14_18 [concrete = constants.%.e71]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %i: ref %.e71 = bind_name i, %i.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %v.patt: %pattern_type.a53 = binding_pattern v [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %value.ref: %X = name_ref value, file.%value
+// CHECK:STDOUT:   %X.ref.loc15_39: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc15_31: type = partial_type %X.ref.loc15_39 [concrete = constants.%.e71]
+// CHECK:STDOUT:   %.loc15_28.1: %.e71 = as_compatible %value.ref
+// CHECK:STDOUT:   %.loc15_28.2: %.e71 = converted %value.ref, %.loc15_28.1
+// CHECK:STDOUT:   %.loc15_10.1: type = splice_block %.loc15_10.2 [concrete = constants.%.e71] {
+// CHECK:STDOUT:     %X.ref.loc15_18: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %.loc15_10.2: type = partial_type %X.ref.loc15_18 [concrete = constants.%.e71]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %v: %.e71 = bind_name v, %.loc15_28.2
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.46e = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %reference.ref: ref %X = name_ref reference, file.%reference [concrete = file.%reference.var]
+// CHECK:STDOUT:   %X.ref.loc16_46: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc16_38: type = partial_type %X.ref.loc16_46 [concrete = constants.%.e71]
+// CHECK:STDOUT:   %.loc16_35.1: ref %.e71 = as_compatible %reference.ref [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %.loc16_35.2: ref %.e71 = converted %reference.ref, %.loc16_35.1 [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %addr.loc16: %ptr.7b2 = addr_of %.loc16_35.2 [concrete = constants.%addr.e01]
+// CHECK:STDOUT:   %.loc16_19: type = splice_block %ptr.loc16 [concrete = constants.%ptr.7b2] {
+// CHECK:STDOUT:     %X.ref.loc16_18: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %.loc16_10: type = partial_type %X.ref.loc16_18 [concrete = constants.%.e71]
+// CHECK:STDOUT:     %ptr.loc16: type = ptr_type %.loc16_10 [concrete = constants.%ptr.7b2]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %ptr.7b2 = bind_name a, %addr.loc16
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.46e = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ptr.ref: %ptr.d17 = name_ref ptr, file.%ptr.loc9_5
+// CHECK:STDOUT:   %X.ref.loc17_38: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc17_30: type = partial_type %X.ref.loc17_38 [concrete = constants.%.e71]
+// CHECK:STDOUT:   %ptr.loc17_39: type = ptr_type %.loc17_30 [concrete = constants.%ptr.7b2]
+// CHECK:STDOUT:   %.loc17_27.1: %ptr.7b2 = as_compatible %ptr.ref
+// CHECK:STDOUT:   %.loc17_27.2: %ptr.7b2 = converted %ptr.ref, %.loc17_27.1
+// CHECK:STDOUT:   %.loc17_19: type = splice_block %ptr.loc17_19 [concrete = constants.%ptr.7b2] {
+// CHECK:STDOUT:     %X.ref.loc17_18: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %.loc17_10: type = partial_type %X.ref.loc17_18 [concrete = constants.%.e71]
+// CHECK:STDOUT:     %ptr.loc17_19: type = ptr_type %.loc17_10 [concrete = constants.%ptr.7b2]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %ptr.7b2 = bind_name b, %.loc17_27.2
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc14_3.1: <bound method> = bound_method %.loc14_3, constants.%T.as.Destroy.impl.Op.d31
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc14_3.1: <bound method> = bound_method %.loc14_3, %T.as.Destroy.impl.Op.specific_fn.1
+// CHECK:STDOUT:   %addr.loc14_3.1: %ptr.7b2 = addr_of %.loc14_3
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc14_3.1: init %empty_tuple.type = call %bound_method.loc14_3.1(%addr.loc14_3.1)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc14_3.2: <bound method> = bound_method %i.var, constants.%T.as.Destroy.impl.Op.d31
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc14_3.2: <bound method> = bound_method %i.var, %T.as.Destroy.impl.Op.specific_fn.2
+// CHECK:STDOUT:   %addr.loc14_3.2: %ptr.7b2 = addr_of %i.var
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc14_3.2: init %empty_tuple.type = call %bound_method.loc14_3.2(%addr.loc14_3.2)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_remove_partial_in_init.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.type: type = fn_type @X.as.Destroy.impl.Op [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op: %X.as.Destroy.impl.Op.type = struct_value () [concrete]
+// CHECK:STDOUT:   %.e71: type = partial_type %X [concrete]
+// CHECK:STDOUT:   %Init.type: type = fn_type @Init [concrete]
+// CHECK:STDOUT:   %Init: %Init.type = struct_value () [concrete]
+// CHECK:STDOUT:   %pattern_type.019: type = pattern_type %X [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.type.057: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%.e71) [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.d31: %T.as.Destroy.impl.Op.type.057 = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.7b2: type = ptr_type %.e71 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %i.patt: %pattern_type.019 = binding_pattern i [concrete]
+// CHECK:STDOUT:     %i.var_patt: %pattern_type.019 = var_pattern %i.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %i.var: ref %X = var %i.var_patt
+// CHECK:STDOUT:   %Init.ref.loc18: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc18_19: ref %.e71 = temporary_storage
+// CHECK:STDOUT:   %Init.call.loc18: init %.e71 = call %Init.ref.loc18() to %.loc18_19
+// CHECK:STDOUT:   %.loc18_3: %X = converted %Init.call.loc18, <error> [concrete = <error>]
+// CHECK:STDOUT:   assign %i.var, <error>
+// CHECK:STDOUT:   %X.ref.loc18: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %i: ref %X = bind_name i, %i.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %j.patt: %pattern_type.019 = binding_pattern j [concrete]
+// CHECK:STDOUT:     %j.var_patt: %pattern_type.019 = var_pattern %j.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %j.var: ref %X = var %j.var_patt
+// CHECK:STDOUT:   %Init.ref.loc26: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc26_19: ref %.e71 = temporary_storage
+// CHECK:STDOUT:   %Init.call.loc26: init %.e71 = call %Init.ref.loc26() to %.loc26_19
+// CHECK:STDOUT:   %X.ref.loc26_24: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc26_21: %X = converted %Init.call.loc26, <error> [concrete = <error>]
+// CHECK:STDOUT:   assign %j.var, <error>
+// CHECK:STDOUT:   %X.ref.loc26_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %j: ref %X = bind_name j, %j.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %k.patt: %pattern_type.019 = binding_pattern k [concrete]
+// CHECK:STDOUT:     %k.var_patt: %pattern_type.019 = var_pattern %k.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %k.var: ref %X = var %k.var_patt
+// CHECK:STDOUT:   %Init.ref.loc34: %Init.type = name_ref Init, file.%Init.decl [concrete = constants.%Init]
+// CHECK:STDOUT:   %.loc34_19: ref %.e71 = temporary_storage
+// CHECK:STDOUT:   %Init.call.loc34: init %.e71 = call %Init.ref.loc34() to %.loc34_19
+// CHECK:STDOUT:   %X.ref.loc34_31: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc34_28: %X = converted %Init.call.loc34, <error> [concrete = <error>]
+// CHECK:STDOUT:   assign %k.var, <error>
+// CHECK:STDOUT:   %X.ref.loc34_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %k: ref %X = bind_name k, %k.var
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc34: <bound method> = bound_method %.loc34_19, constants.%T.as.Destroy.impl.Op.d31
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc34: <bound method> = bound_method %.loc34_19, %T.as.Destroy.impl.Op.specific_fn.1
+// CHECK:STDOUT:   %addr.loc34_19: %ptr.7b2 = addr_of %.loc34_19
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc34: init %empty_tuple.type = call %bound_method.loc34(%addr.loc34_19)
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.bound.loc34: <bound method> = bound_method %k.var, constants.%X.as.Destroy.impl.Op
+// CHECK:STDOUT:   %addr.loc34_3: %ptr.d17 = addr_of %k.var
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.call.loc34: init %empty_tuple.type = call %X.as.Destroy.impl.Op.bound.loc34(%addr.loc34_3)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc26: <bound method> = bound_method %.loc26_19, constants.%T.as.Destroy.impl.Op.d31
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc26: <bound method> = bound_method %.loc26_19, %T.as.Destroy.impl.Op.specific_fn.2
+// CHECK:STDOUT:   %addr.loc26_19: %ptr.7b2 = addr_of %.loc26_19
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc26: init %empty_tuple.type = call %bound_method.loc26(%addr.loc26_19)
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.bound.loc26: <bound method> = bound_method %j.var, constants.%X.as.Destroy.impl.Op
+// CHECK:STDOUT:   %addr.loc26_3: %ptr.d17 = addr_of %j.var
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.call.loc26: init %empty_tuple.type = call %X.as.Destroy.impl.Op.bound.loc26(%addr.loc26_3)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc18: <bound method> = bound_method %.loc18_19, constants.%T.as.Destroy.impl.Op.d31
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc18: <bound method> = bound_method %.loc18_19, %T.as.Destroy.impl.Op.specific_fn.3
+// CHECK:STDOUT:   %addr.loc18_19: %ptr.7b2 = addr_of %.loc18_19
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc18: init %empty_tuple.type = call %bound_method.loc18(%addr.loc18_19)
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.bound.loc18: <bound method> = bound_method %i.var, constants.%X.as.Destroy.impl.Op
+// CHECK:STDOUT:   %addr.loc18_3: %ptr.d17 = addr_of %i.var
+// CHECK:STDOUT:   %X.as.Destroy.impl.Op.call.loc18: init %empty_tuple.type = call %X.as.Destroy.impl.Op.bound.loc18(%addr.loc18_3)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- unsafe_remove_partial.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
+// CHECK:STDOUT:   %pattern_type.1c6: type = pattern_type %ptr.d17 [concrete]
+// CHECK:STDOUT:   %.e71: type = partial_type %X [concrete]
+// CHECK:STDOUT:   %ptr.7b2: type = ptr_type %.e71 [concrete]
+// CHECK:STDOUT:   %pattern_type.019: type = pattern_type %X [concrete]
+// CHECK:STDOUT:   %reference.var: ref %X = var file.%reference.var_patt [concrete]
+// CHECK:STDOUT:   %addr.c6b: %ptr.d17 = addr_of %reference.var [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Use() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %v.patt: %pattern_type.019 = binding_pattern v [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %value.ref: %.e71 = name_ref value, file.%value
+// CHECK:STDOUT:   %X.ref.loc14_30: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc14_27.1: %X = as_compatible %value.ref
+// CHECK:STDOUT:   %.loc14_27.2: %X = converted %value.ref, %.loc14_27.1
+// CHECK:STDOUT:   %X.ref.loc14_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %v: %X = bind_name v, %.loc14_27.2
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.1c6 = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %reference.ref: ref %.e71 = name_ref reference, file.%reference [concrete = file.%reference.var]
+// CHECK:STDOUT:   %X.ref.loc15_37: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %.loc15_34.1: ref %X = as_compatible %reference.ref [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %.loc15_34.2: ref %X = converted %reference.ref, %.loc15_34.1 [concrete = constants.%reference.var]
+// CHECK:STDOUT:   %addr: %ptr.d17 = addr_of %.loc15_34.2 [concrete = constants.%addr.c6b]
+// CHECK:STDOUT:   %.loc15_11: type = splice_block %ptr.loc15 [concrete = constants.%ptr.d17] {
+// CHECK:STDOUT:     %X.ref.loc15_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %ptr.loc15: type = ptr_type %X.ref.loc15_10 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %ptr.d17 = bind_name a, %addr
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.1c6 = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %ptr.ref: %ptr.7b2 = name_ref ptr, file.%ptr.loc10_5
+// CHECK:STDOUT:   %X.ref.loc16_29: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:   %ptr.loc16_30: type = ptr_type %X.ref.loc16_29 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   %.loc16_26.1: %ptr.d17 = as_compatible %ptr.ref
+// CHECK:STDOUT:   %.loc16_26.2: %ptr.d17 = converted %ptr.ref, %.loc16_26.1
+// CHECK:STDOUT:   %.loc16_11: type = splice_block %ptr.loc16_11 [concrete = constants.%ptr.d17] {
+// CHECK:STDOUT:     %X.ref.loc16_10: type = name_ref X, file.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:     %ptr.loc16_11: type = ptr_type %X.ref.loc16_10 [concrete = constants.%ptr.d17]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %ptr.d17 = bind_name b, %.loc16_26.2
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 54
toolchain/check/testdata/as/unsafe_as.carbon

@@ -2,7 +2,7 @@
 // Exceptions. See /LICENSE for license information.
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //
-// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/unformed.carbon
 //
 //
 // AUTOUPDATE
 // AUTOUPDATE
 // TIP: To test this file alone, run:
 // TIP: To test this file alone, run:
@@ -10,18 +10,6 @@
 // TIP: To dump output, run:
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/as/unsafe_as.carbon
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/as/unsafe_as.carbon
 
 
-// --- qualifiers.carbon
-
-library "[[@TEST_NAME]]";
-
-class X {}
-
-fn Convert(p: const X*) -> X* {
-  //@dump-sem-ir-begin
-  return p unsafe as X*;
-  //@dump-sem-ir-end
-}
-
 // --- fail_no_conversion.carbon
 // --- fail_no_conversion.carbon
 
 
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
@@ -30,7 +18,7 @@ class A {};
 class B {};
 class B {};
 
 
 fn Convert(a: A) -> B {
 fn Convert(a: A) -> B {
-  // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+7]]:10: error: cannot convert expression of type `A` to `B` with `as` [ConversionFailure]
+  // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+7]]:10: error: cannot convert expression of type `A` to `B` with `unsafe as` [ConversionFailure]
   // CHECK:STDERR:   return a unsafe as B;
   // CHECK:STDERR:   return a unsafe as B;
   // CHECK:STDERR:          ^~~~~~~~~~~~~
   // CHECK:STDERR:          ^~~~~~~~~~~~~
   // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+4]]:10: note: type `A` does not implement interface `Core.UnsafeAs(B)` [MissingImplInMemberAccessNote]
   // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+4]]:10: note: type `A` does not implement interface `Core.UnsafeAs(B)` [MissingImplInMemberAccessNote]
@@ -39,43 +27,3 @@ fn Convert(a: A) -> B {
   // CHECK:STDERR:
   // CHECK:STDERR:
   return a unsafe as B;
   return a unsafe as B;
 }
 }
-
-// --- fail_remove_qualifiers_without_unsafe.carbon
-
-library "[[@TEST_NAME]]";
-
-class X {}
-
-fn Convert(p: const X*) -> X* {
-  // CHECK:STDERR: fail_remove_qualifiers_without_unsafe.carbon:[[@LINE+7]]:10: error: cannot convert expression of type `const X*` to `X*` with `as` [ConversionFailure]
-  // CHECK:STDERR:   return p as X*;
-  // CHECK:STDERR:          ^~~~~~~
-  // CHECK:STDERR: fail_remove_qualifiers_without_unsafe.carbon:[[@LINE+4]]:10: note: type `const X*` does not implement interface `Core.As(X*)` [MissingImplInMemberAccessNote]
-  // CHECK:STDERR:   return p as X*;
-  // CHECK:STDERR:          ^~~~~~~
-  // CHECK:STDERR:
-  return p as X*;
-}
-
-// CHECK:STDOUT: --- qualifiers.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %X: type = class_type @X [concrete]
-// CHECK:STDOUT:   %ptr.d17: type = ptr_type %X [concrete]
-// CHECK:STDOUT:   %const: type = const_type %X [concrete]
-// CHECK:STDOUT:   %ptr.cbd: type = ptr_type %const [concrete]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @Convert(%p.param: %ptr.cbd) -> %ptr.d17 {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %p.ref: %ptr.cbd = name_ref p, %p
-// CHECK:STDOUT:   %X.ref.loc8: type = name_ref X, file.%X.decl [concrete = constants.%X]
-// CHECK:STDOUT:   %ptr.loc8: type = ptr_type %X.ref.loc8 [concrete = constants.%ptr.d17]
-// CHECK:STDOUT:   %.loc8_19.1: %ptr.d17 = as_compatible %p.ref
-// CHECK:STDOUT:   %.loc8_19.2: %ptr.d17 = converted %p.ref, %.loc8_19.1
-// CHECK:STDOUT:   return %.loc8_19.2
-// CHECK:STDOUT: }
-// CHECK:STDOUT:

+ 7 - 0
toolchain/check/testdata/class/fail_base_bad_type.carbon

@@ -56,6 +56,13 @@ class DeriveFromi32 {
 
 
 // It's not really important whether this conversion produces an error or not,
 // It's not really important whether this conversion produces an error or not,
 // but it shouldn't crash.
 // but it shouldn't crash.
+// CHECK:STDERR: fail_derive_from_i32.carbon:[[@LINE+7]]:53: error: cannot implicitly convert expression of type `DeriveFromi32*` to `i32*` [ConversionFailure]
+// CHECK:STDERR: fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; }
+// CHECK:STDERR:                                                     ^~~~~~~~~
+// CHECK:STDERR: fail_derive_from_i32.carbon:[[@LINE+4]]:53: note: type `DeriveFromi32*` does not implement interface `Core.ImplicitAs(i32*)` [MissingImplInMemberAccessNote]
+// CHECK:STDERR: fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; }
+// CHECK:STDERR:                                                     ^~~~~~~~~
+// CHECK:STDERR:
 fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; }
 fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; }
 
 
 // CHECK:STDERR: fail_derive_from_i32.carbon:[[@LINE+4]]:70: error: member name `n` not found in `DeriveFromi32` [MemberNameNotFoundInInstScope]
 // CHECK:STDERR: fail_derive_from_i32.carbon:[[@LINE+4]]:70: error: member name `n` not found in `DeriveFromi32` [MemberNameNotFoundInInstScope]

+ 7 - 0
toolchain/check/testdata/interop/cpp/class/base.carbon

@@ -178,6 +178,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "virtual_inheritance.h";
 import Cpp library "virtual_inheritance.h";
 
 
 fn Convert(p: Cpp.B*) -> Cpp.A* {
 fn Convert(p: Cpp.B*) -> Cpp.A* {
+  // CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+21]]:3: error: semantics TODO: `class with virtual bases` [SemanticsTodo]
+  // CHECK:STDERR:   return p;
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+18]]:3: note: while completing C++ type `Cpp.B` [InCppTypeCompletion]
+  // CHECK:STDERR:   return p;
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+14]]:3: error: semantics TODO: `class with virtual bases` [SemanticsTodo]
   // CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+14]]:3: error: semantics TODO: `class with virtual bases` [SemanticsTodo]
   // CHECK:STDERR:   return p;
   // CHECK:STDERR:   return p;
   // CHECK:STDERR:   ^~~~~~~~~
   // CHECK:STDERR:   ^~~~~~~~~

+ 11 - 1
toolchain/check/type.cpp

@@ -138,10 +138,20 @@ auto GetConstType(Context& context, SemIR::TypeInstId inner_type_id)
 
 
 auto GetQualifiedType(Context& context, SemIR::TypeId type_id,
 auto GetQualifiedType(Context& context, SemIR::TypeId type_id,
                       SemIR::TypeQualifiers quals) -> SemIR::TypeId {
                       SemIR::TypeQualifiers quals) -> SemIR::TypeId {
-  if ((quals & SemIR::TypeQualifiers::Const) != SemIR::TypeQualifiers::None) {
+  if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Const)) {
     type_id = GetConstType(context, context.types().GetInstId(type_id));
     type_id = GetConstType(context, context.types().GetInstId(type_id));
     quals &= ~SemIR::TypeQualifiers::Const;
     quals &= ~SemIR::TypeQualifiers::Const;
   }
   }
+  if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed)) {
+    type_id = GetTypeImpl<SemIR::MaybeUnformedType>(
+        context, context.types().GetInstId(type_id));
+    quals &= ~SemIR::TypeQualifiers::MaybeUnformed;
+  }
+  if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Partial)) {
+    type_id = GetTypeImpl<SemIR::PartialType>(
+        context, context.types().GetInstId(type_id));
+    quals &= ~SemIR::TypeQualifiers::Partial;
+  }
   CARBON_CHECK(quals == SemIR::TypeQualifiers::None);
   CARBON_CHECK(quals == SemIR::TypeQualifiers::None);
   return type_id;
   return type_id;
 }
 }

+ 1 - 2
toolchain/lower/file_context.cpp

@@ -764,8 +764,7 @@ static auto BuildTypeForInst(FileContext& context, SemIR::ClassType inst)
 }
 }
 
 
 template <typename InstT>
 template <typename InstT>
-  requires(InstT::Kind.template IsAnyOf<
-           SemIR::ConstType, SemIR::MaybeUnformedType, SemIR::PartialType>())
+  requires(SemIR::Internal::HasInstCategory<SemIR::AnyQualifiedType, InstT>)
 static auto BuildTypeForInst(FileContext& context, InstT inst) -> llvm::Type* {
 static auto BuildTypeForInst(FileContext& context, InstT inst) -> llvm::Type* {
   return context.GetType(
   return context.GetType(
       context.sem_ir().types().GetTypeIdForTypeInstId(inst.inner_id));
       context.sem_ir().types().GetTypeIdForTypeInstId(inst.inner_id));

+ 12 - 0
toolchain/sem_ir/inst_categories.h

@@ -167,6 +167,18 @@ struct AnyParamPattern {
   CallParamIndex index;
   CallParamIndex index;
 };
 };
 
 
+// A type qualifier that wraps another type and has the same object
+// representation. Qualifiers are arranged so that adding a qualifier is
+// generally safe, and removing a qualifier is not necessarily safe or correct.
+struct AnyQualifiedType {
+  using CategoryInfo = CategoryOf<ConstType, PartialType, MaybeUnformedType>;
+
+  InstKind kind;
+
+  TypeId type_id;
+  TypeInstId inner_id;
+};
+
 // A struct-like type with a list of named fields.
 // A struct-like type with a list of named fields.
 struct AnyStructType {
 struct AnyStructType {
   using CategoryInfo = CategoryOf<StructType, CustomLayoutType>;
   using CategoryInfo = CategoryOf<StructType, CustomLayoutType>;

+ 35 - 5
toolchain/sem_ir/type.cpp

@@ -95,12 +95,42 @@ auto TypeStore::GetTransitiveAdaptedType(TypeId type_id) const -> TypeId {
 
 
 auto TypeStore::GetUnqualifiedTypeAndQualifiers(TypeId type_id) const
 auto TypeStore::GetUnqualifiedTypeAndQualifiers(TypeId type_id) const
     -> std::pair<TypeId, TypeQualifiers> {
     -> std::pair<TypeId, TypeQualifiers> {
-  if (auto const_type = TryGetAs<ConstType>(type_id)) {
-    return {file_->types().GetTypeIdForTypeInstId(const_type->inner_id),
-            TypeQualifiers::Const};
+  TypeQualifiers quals = TypeQualifiers::None;
+  while (true) {
+    if (auto qualified_type = TryGetAs<AnyQualifiedType>(type_id)) {
+      type_id = file_->types().GetTypeIdForTypeInstId(qualified_type->inner_id);
+      switch (qualified_type->kind) {
+        case ConstType::Kind:
+          quals |= TypeQualifiers::Const;
+          break;
+        case MaybeUnformedType::Kind:
+          quals |= TypeQualifiers::MaybeUnformed;
+          break;
+        case PartialType::Kind:
+          quals |= TypeQualifiers::Partial;
+          break;
+        default:
+          CARBON_FATAL("Unknown type qualifier {0}", qualified_type->kind);
+      }
+    } else {
+      return {type_id, quals};
+    }
+  }
+}
+
+auto TypeStore::GetTransitiveUnqualifiedAdaptedType(TypeId type_id) const
+    -> std::pair<TypeId, TypeQualifiers> {
+  TypeQualifiers quals = TypeQualifiers::None;
+  while (true) {
+    type_id = GetTransitiveAdaptedType(type_id);
+    auto [unqual_type_id, inner_quals] =
+        GetUnqualifiedTypeAndQualifiers(type_id);
+    if (unqual_type_id == type_id) {
+      return {type_id, quals};
+    }
+    type_id = unqual_type_id;
+    quals |= inner_quals;
   }
   }
-  // TODO: Look through PartialType when this is reachable/testable
-  return {type_id, TypeQualifiers::None};
 }
 }
 
 
 auto TypeStore::TryGetIntTypeInfo(TypeId int_type_id) const
 auto TypeStore::TryGetIntTypeInfo(TypeId int_type_id) const

+ 14 - 2
toolchain/sem_ir/type.h

@@ -21,11 +21,18 @@ LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
 enum class TypeQualifiers {
 enum class TypeQualifiers {
   None = 0,
   None = 0,
   Const = 1 << 0,
   Const = 1 << 0,
-  // TODO: Partial
+  MaybeUnformed = 1 << 1,
+  Partial = 1 << 2,
 
 
-  LLVM_MARK_AS_BITMASK_ENUM(Const)
+  LLVM_MARK_AS_BITMASK_ENUM(Partial)
 };
 };
 
 
+// Returns whether the type qualifier set `quals` contains `qual`.
+inline auto HasTypeQualifier(TypeQualifiers quals, TypeQualifiers qual)
+    -> bool {
+  return (quals & qual) != TypeQualifiers::None;
+}
+
 // Provides a ValueStore wrapper with an API specific to types.
 // Provides a ValueStore wrapper with an API specific to types.
 class TypeStore : public Yaml::Printable<TypeStore> {
 class TypeStore : public Yaml::Printable<TypeStore> {
  public:
  public:
@@ -182,6 +189,11 @@ class TypeStore : public Yaml::Printable<TypeStore> {
   auto GetUnqualifiedTypeAndQualifiers(TypeId type_id) const
   auto GetUnqualifiedTypeAndQualifiers(TypeId type_id) const
       -> std::pair<TypeId, TypeQualifiers>;
       -> std::pair<TypeId, TypeQualifiers>;
 
 
+  // Returns the non-adapter unqualified type that is compatible with the
+  // specified type.
+  auto GetTransitiveUnqualifiedAdaptedType(TypeId type_id) const
+      -> std::pair<TypeId, TypeQualifiers>;
+
   // Determines whether the given type is a signed integer type. This includes
   // Determines whether the given type is a signed integer type. This includes
   // the case where the type is `Core.IntLiteral` or a class type whose object
   // the case where the type is `Core.IntLiteral` or a class type whose object
   // representation is a signed integer type.
   // representation is a signed integer type.

+ 17 - 0
toolchain/testing/testdata/min_prelude/parts/maybe_unformed.carbon

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

+ 15 - 0
toolchain/testing/testdata/min_prelude/unformed.carbon

@@ -0,0 +1,15 @@
+// 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/parts/as.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/parts/maybe_unformed.carbon
+// EXTRA-ARGS: --custom-core --exclude-dump-file-prefix=min_prelude/
+
+// --- min_prelude/uint.carbon
+
+// A minimal prelude for test involving `Core.MaybeUnformed(T)`.
+package Core library "prelude";
+
+export import library "prelude/parts/as";
+export import library "prelude/parts/maybe_unformed";