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

Support for importing const-qualified types from C++. (#5794)

Incidentally also supports import of pointers-to-pointers.

Imported const-qualified types aren't especially useful just yet,
because on the Carbon side we don't yet permit conversions from
non-const to const types, so most of the tests still fail, but for
different reasons now.
Richard Smith 9 месяцев назад
Родитель
Сommit
a6acba9eab

+ 68 - 28
toolchain/check/import_cpp.cpp

@@ -702,17 +702,11 @@ static auto MapRecordType(Context& context, SemIR::LocId loc_id,
       .type_id = context.types().GetTypeIdForTypeInstId(record_type_inst_id)};
 }
 
-// Maps a C++ non-pointer type to a Carbon type.
+// Maps a C++ type that is not a wrapper type such as a pointer to a Carbon
+// type.
 // TODO: Support more types.
-static auto MapNonPointerType(Context& context, SemIR::LocId loc_id,
+static auto MapNonWrapperType(Context& context, SemIR::LocId loc_id,
                               clang::QualType type) -> TypeExpr {
-  if (type.hasQualifiers()) {
-    // TODO: Support type qualifiers.
-    return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
-  }
-
-  CARBON_CHECK(!type->isPointerType());
-
   if (const auto* builtin_type = type->getAs<clang::BuiltinType>()) {
     return MapBuiltinType(context, *builtin_type);
   }
@@ -721,12 +715,40 @@ static auto MapNonPointerType(Context& context, SemIR::LocId loc_id,
     return MapRecordType(context, loc_id, *record_type);
   }
 
+  CARBON_CHECK(!type.hasQualifiers() && !type->isPointerType(),
+               "Should not see wrapper types here");
+
   return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
 }
 
+// Maps a qualified C++ type to a Carbon type.
+static auto MapQualifiedType(Context& context, SemIR::LocId loc_id,
+                             clang::QualType type, TypeExpr type_expr)
+    -> TypeExpr {
+  auto quals = type.getQualifiers();
+
+  if (quals.hasConst()) {
+    auto type_id = GetConstType(context, type_expr.inst_id);
+    type_expr = {.inst_id = context.types().GetInstId(type_id),
+                 .type_id = type_id};
+    quals.removeConst();
+  }
+
+  // TODO: Support other qualifiers.
+  if (!quals.empty()) {
+    context.TODO(loc_id, llvm::formatv("Unsupported: qualified type: {0}",
+                                       type.getAsString()));
+    return {.inst_id = SemIR::ErrorInst::TypeInstId,
+            .type_id = SemIR::ErrorInst::TypeId};
+  }
+
+  return type_expr;
+}
+
 // Maps a C++ pointer type to a Carbon pointer type.
 static auto MapPointerType(Context& context, SemIR::LocId loc_id,
-                           clang::QualType type) -> TypeExpr {
+                           clang::QualType type, TypeExpr pointee_type_expr)
+    -> TypeExpr {
   CARBON_CHECK(type->isPointerType());
 
   if (auto nullability = type->getNullability();
@@ -738,21 +760,6 @@ static auto MapPointerType(Context& context, SemIR::LocId loc_id,
             .type_id = SemIR::ErrorInst::TypeId};
   }
 
-  clang::QualType pointee_type = type->getPointeeType();
-
-  if (pointee_type->isAnyPointerType()) {
-    context.TODO(loc_id,
-                 llvm::formatv("Unsupported: pointer to pointer type: {0}",
-                               pointee_type.getAsString()));
-    return {.inst_id = SemIR::ErrorInst::TypeInstId,
-            .type_id = SemIR::ErrorInst::TypeId};
-  }
-
-  TypeExpr pointee_type_expr = MapNonPointerType(context, loc_id, pointee_type);
-  if (!pointee_type_expr.inst_id.has_value()) {
-    return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
-  }
-
   SemIR::TypeId pointer_type_id =
       GetPointerType(context, pointee_type_expr.inst_id);
   return {.inst_id = context.types().GetInstId(pointer_type_id),
@@ -764,10 +771,38 @@ static auto MapPointerType(Context& context, SemIR::LocId loc_id,
 // canonicalization.
 static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
     -> TypeExpr {
-  if (type->isPointerType()) {
-    return MapPointerType(context, loc_id, type);
+  // Unwrap any type modifiers and wrappers.
+  llvm::SmallVector<clang::QualType> wrapper_types;
+  while (true) {
+    clang::QualType orig_type = type;
+    if (type.hasQualifiers()) {
+      type = type.getUnqualifiedType();
+    } else if (type->isPointerType()) {
+      type = type->getPointeeType();
+    } else {
+      break;
+    }
+    wrapper_types.push_back(orig_type);
   }
-  return MapNonPointerType(context, loc_id, type);
+
+  auto mapped = MapNonWrapperType(context, loc_id, type);
+
+  for (auto wrapper : llvm::reverse(wrapper_types)) {
+    if (!mapped.inst_id.has_value() ||
+        mapped.type_id == SemIR::ErrorInst::TypeId) {
+      break;
+    }
+
+    if (wrapper.hasQualifiers()) {
+      mapped = MapQualifiedType(context, loc_id, wrapper, mapped);
+    } else if (wrapper->isPointerType()) {
+      mapped = MapPointerType(context, loc_id, wrapper, mapped);
+    } else {
+      CARBON_FATAL("Unexpected wrapper type {0}", wrapper.getAsString());
+    }
+  }
+
+  return mapped;
 }
 
 // Returns a block id for the explicit parameters of the given function
@@ -785,6 +820,11 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
   llvm::SmallVector<SemIR::InstId> params;
   params.reserve(clang_decl.parameters().size());
   for (const clang::ParmVarDecl* param : clang_decl.parameters()) {
+    // TODO: Get the parameter type from the function, not from the
+    // `ParmVarDecl`. The type of the `ParmVarDecl` is the type within the
+    // function, and isn't in general the same as the type that's exposed to
+    // callers. In particular, the parameter type exposed to callers will never
+    // be cv-qualified.
     clang::QualType param_type = param->getType();
 
     // Mark the start of a region of insts, needed for the type expression

+ 44 - 19
toolchain/check/testdata/interop/cpp/function/param_int16.carbon

@@ -203,12 +203,13 @@ import Cpp library "const_short.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_const_short.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: const short` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_const_short.carbon:[[@LINE+8]]:11: error: cannot implicitly convert expression of type `i16` to `const i16` [ConversionFailure]
   // CHECK:STDERR:   Cpp.foo(1 as i16);
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_const_short.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:           ^~~~~~~~
+  // CHECK:STDERR: fail_todo_import_const_short.carbon:[[@LINE+5]]:11: note: type `i16` does not implement interface `Core.ImplicitAs(const i16)` [MissingImplInMemberAccessNote]
   // CHECK:STDERR:   Cpp.foo(1 as i16);
-  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:           ^~~~~~~~
+  // CHECK:STDERR: fail_todo_import_const_short.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo(1 as i16);
   //@dump-sem-ir-end
@@ -308,12 +309,13 @@ import Cpp library "const_short_pointer.h";
 fn F() {
   var a: i16 = 1;
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_const_short_pointer.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: const short * _Nonnull` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_const_short_pointer.carbon:[[@LINE+8]]:11: error: cannot implicitly convert expression of type `i16*` to `const i16*` [ConversionFailure]
   // CHECK:STDERR:   Cpp.foo(&a);
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_const_short_pointer.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_const_short_pointer.carbon:[[@LINE+5]]:11: note: type `i16*` does not implement interface `Core.ImplicitAs(const i16*)` [MissingImplInMemberAccessNote]
   // CHECK:STDERR:   Cpp.foo(&a);
-  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_const_short_pointer.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo(&a);
   //@dump-sem-ir-end
@@ -734,9 +736,13 @@ fn F() {
 // CHECK:STDOUT: --- fail_todo_import_const_short.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %int_1.5b8: Core.IntLiteral = int_value 1 [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %int_16: Core.IntLiteral = int_value 16 [concrete]
 // CHECK:STDOUT:   %i16: type = class_type @Int, @Int(%int_16) [concrete]
+// CHECK:STDOUT:   %const: type = const_type %i16 [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %int_1.5b8: Core.IntLiteral = int_value 1 [concrete]
 // CHECK:STDOUT:   %As.type.a96: type = facet_type <@As, @As(%i16)> [concrete]
 // CHECK:STDOUT:   %Convert.type.be5: type = fn_type @Convert.1, @As(%i16) [concrete]
 // CHECK:STDOUT:   %To: Core.IntLiteral = bind_symbolic_name To, 0 [symbolic]
@@ -755,9 +761,14 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core.import_ref.78a: @As.impl.686.%Convert.type (%Convert.type.062) = import_ref Core//prelude/parts/int, loc25_39, loaded [symbolic = @As.impl.686.%Convert (constants.%Convert.527)]
 // CHECK:STDOUT:   %As.impl_witness_table.eb4 = impl_witness_table (%Core.import_ref.78a), @As.impl.686 [concrete]
 // CHECK:STDOUT: }
@@ -765,17 +776,19 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8]
 // CHECK:STDOUT:   %int_16: Core.IntLiteral = int_value 16 [concrete = constants.%int_16]
 // CHECK:STDOUT:   %i16: type = class_type @Int, @Int(constants.%int_16) [concrete = constants.%i16]
 // CHECK:STDOUT:   %impl.elem0: %.91d = impl_witness_access constants.%As.impl_witness.0ef, element0 [concrete = constants.%Convert.489]
-// CHECK:STDOUT:   %bound_method.loc15_13.1: <bound method> = bound_method %int_1, %impl.elem0 [concrete = constants.%Convert.bound]
+// CHECK:STDOUT:   %bound_method.loc16_13.1: <bound method> = bound_method %int_1, %impl.elem0 [concrete = constants.%Convert.bound]
 // CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @Convert.5(constants.%int_16) [concrete = constants.%Convert.specific_fn]
-// CHECK:STDOUT:   %bound_method.loc15_13.2: <bound method> = bound_method %int_1, %specific_fn [concrete = constants.%bound_method]
-// CHECK:STDOUT:   %int.convert_checked: init %i16 = call %bound_method.loc15_13.2(%int_1) [concrete = constants.%int_1.f90]
-// CHECK:STDOUT:   %.loc15_13.1: %i16 = value_of_initializer %int.convert_checked [concrete = constants.%int_1.f90]
-// CHECK:STDOUT:   %.loc15_13.2: %i16 = converted %int_1, %.loc15_13.1 [concrete = constants.%int_1.f90]
+// CHECK:STDOUT:   %bound_method.loc16_13.2: <bound method> = bound_method %int_1, %specific_fn [concrete = constants.%bound_method]
+// CHECK:STDOUT:   %int.convert_checked: init %i16 = call %bound_method.loc16_13.2(%int_1) [concrete = constants.%int_1.f90]
+// CHECK:STDOUT:   %.loc16_13.1: %i16 = value_of_initializer %int.convert_checked [concrete = constants.%int_1.f90]
+// CHECK:STDOUT:   %.loc16_13.2: %i16 = converted %int_1, %.loc16_13.1 [concrete = constants.%int_1.f90]
+// CHECK:STDOUT:   %.loc16_13.3: %const = converted %.loc16_13.2, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -862,25 +875,37 @@ fn F() {
 // CHECK:STDOUT: --- fail_todo_import_const_short_pointer.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %int_16: Core.IntLiteral = int_value 16 [concrete]
 // CHECK:STDOUT:   %i16: type = class_type @Int, @Int(%int_16) [concrete]
+// CHECK:STDOUT:   %const: type = const_type %i16 [concrete]
+// CHECK:STDOUT:   %ptr.758: type = ptr_type %const [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.251: type = ptr_type %i16 [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %a.ref: ref %i16 = name_ref a, %a
-// CHECK:STDOUT:   %addr.loc16: %ptr.251 = addr_of %a.ref
+// CHECK:STDOUT:   %addr.loc17: %ptr.251 = addr_of %a.ref
+// CHECK:STDOUT:   %.loc17: %ptr.758 = converted %addr.loc17, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 39 - 13
toolchain/check/testdata/interop/cpp/function/param_int32.carbon

@@ -263,12 +263,13 @@ import Cpp library "const_int.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_const_int.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: const int` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_const_int.carbon:[[@LINE+8]]:11: error: cannot implicitly convert expression of type `Core.IntLiteral` to `const i32` [ConversionFailure]
   // CHECK:STDERR:   Cpp.foo(1);
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_const_int.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:           ^
+  // CHECK:STDERR: fail_todo_import_const_int.carbon:[[@LINE+5]]:11: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(const i32)` [MissingImplInMemberAccessNote]
   // CHECK:STDERR:   Cpp.foo(1);
-  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:           ^
+  // CHECK:STDERR: fail_todo_import_const_int.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo(1);
   //@dump-sem-ir-end
@@ -368,12 +369,13 @@ import Cpp library "const_int_pointer.h";
 fn F() {
   var a : i32 = 1;
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_const_int_pointer.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: const int * _Nonnull` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_const_int_pointer.carbon:[[@LINE+8]]:11: error: cannot implicitly convert expression of type `i32*` to `const i32*` [ConversionFailure]
   // CHECK:STDERR:   Cpp.foo(&a);
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_const_int_pointer.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_const_int_pointer.carbon:[[@LINE+5]]:11: note: type `i32*` does not implement interface `Core.ImplicitAs(const i32*)` [MissingImplInMemberAccessNote]
   // CHECK:STDERR:   Cpp.foo(&a);
-  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_const_int_pointer.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo(&a);
   //@dump-sem-ir-end
@@ -979,21 +981,34 @@ fn F() {
 // CHECK:STDOUT: --- fail_todo_import_const_int.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %const: type = const_type %i32 [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
 // CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1]
+// CHECK:STDOUT:   %.loc16: %const = converted %int_1, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -1082,23 +1097,34 @@ fn F() {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
 // CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %const: type = const_type %i32 [concrete]
+// CHECK:STDOUT:   %ptr.36b: type = ptr_type %const [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.235: type = ptr_type %i32 [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %a.ref: ref %i32 = name_ref a, %a
-// CHECK:STDOUT:   %addr.loc16: %ptr.235 = addr_of %a.ref
+// CHECK:STDOUT:   %addr.loc17: %ptr.235 = addr_of %a.ref
+// CHECK:STDOUT:   %.loc17: %ptr.36b = converted %addr.loc17, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.call: init %i32 = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 54 - 57
toolchain/check/testdata/interop/cpp/function/pointer.carbon

@@ -43,7 +43,7 @@ struct S {};
 
 auto foo(S* _Nonnull * _Nonnull) -> void;
 
-// --- fail_todo_import_double_pointer_param.carbon
+// --- import_double_pointer_param.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -53,13 +53,6 @@ fn F() {
   //@dump-sem-ir-begin
   var s: Cpp.S = {};
   var p: Cpp.S* = &s;
-  // CHECK:STDERR: fail_todo_import_double_pointer_param.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: pointer to pointer type: S * _Nonnull` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.foo(&p);
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_double_pointer_param.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.foo(&p);
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR:
   Cpp.foo(&p);
   //@dump-sem-ir-end
 }
@@ -74,7 +67,7 @@ struct S {};
 
 auto foo(const S* _Nonnull) -> void;
 
-// --- fail_todo_import_const_pointer_param.carbon
+// --- import_const_pointer_param.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -85,13 +78,6 @@ fn G() -> const Cpp.S;
 fn F() {
   //@dump-sem-ir-begin
   var s: const Cpp.S = G();
-  // CHECK:STDERR: fail_todo_import_const_pointer_param.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: const S * _Nonnull` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.foo(&s);
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_const_pointer_param.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.foo(&s);
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR:
   Cpp.foo(&s);
   //@dump-sem-ir-end
 }
@@ -131,7 +117,7 @@ struct S {};
 
 auto foo() -> S* _Nonnull * _Nonnull;
 
-// --- fail_todo_import_double_pointer_return.carbon
+// --- import_double_pointer_return.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -141,13 +127,6 @@ fn IngestDoublePointer(s: Cpp.S**);
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_double_pointer_return.carbon:[[@LINE+7]]:23: error: semantics TODO: `Unsupported: pointer to pointer type: S * _Nonnull` [SemanticsTodo]
-  // CHECK:STDERR:   IngestDoublePointer(Cpp.foo());
-  // CHECK:STDERR:                       ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_double_pointer_return.carbon:[[@LINE+4]]:23: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   IngestDoublePointer(Cpp.foo());
-  // CHECK:STDERR:                       ^~~~~~~
-  // CHECK:STDERR:
   IngestDoublePointer(Cpp.foo());
   Cpp.foo();
   //@dump-sem-ir-end
@@ -163,7 +142,7 @@ struct S {};
 
 auto foo() -> const S* _Nonnull;
 
-// --- fail_todo_import_const_pointer_return.carbon
+// --- import_const_pointer_return.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -173,13 +152,6 @@ fn IngestConstPointer(s: const Cpp.S*);
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_const_pointer_return.carbon:[[@LINE+7]]:22: error: semantics TODO: `Unsupported: return type: const S * _Nonnull` [SemanticsTodo]
-  // CHECK:STDERR:   IngestConstPointer(Cpp.foo());
-  // CHECK:STDERR:                      ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_const_pointer_return.carbon:[[@LINE+4]]:22: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   IngestConstPointer(Cpp.foo());
-  // CHECK:STDERR:                      ^~~~~~~
-  // CHECK:STDERR:
   IngestConstPointer(Cpp.foo());
   Cpp.foo();
   //@dump-sem-ir-end
@@ -274,7 +246,7 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_double_pointer_param.carbon
+// CHECK:STDOUT: --- import_double_pointer_param.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
@@ -284,9 +256,9 @@ fn F() {
 // CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.5c7: type = ptr_type %S [concrete]
 // CHECK:STDOUT:   %pattern_type.259: type = pattern_type %ptr.5c7 [concrete]
+// CHECK:STDOUT:   %ptr.dfe: type = ptr_type %ptr.5c7 [concrete]
 // CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
 // CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
-// CHECK:STDOUT:   %ptr.dfe: type = ptr_type %ptr.5c7 [concrete]
 // CHECK:STDOUT:   %Op.type.c07: type = fn_type @Op.2, @Destroy.impl(%ptr.5c7) [concrete]
 // CHECK:STDOUT:   %Op.64b: %Op.type.c07 = struct_value () [concrete]
 // CHECK:STDOUT:   %Op.type.642: type = fn_type @Op.2, @Destroy.impl(%S) [concrete]
@@ -338,11 +310,11 @@ fn F() {
 // CHECK:STDOUT:     %ptr: type = ptr_type %S.ref.loc9 [concrete = constants.%ptr.5c7]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %p: ref %ptr.5c7 = bind_name p, %p.var
-// CHECK:STDOUT:   %Cpp.ref.loc17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Cpp.ref.loc10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %p.ref: ref %ptr.5c7 = name_ref p, %p
-// CHECK:STDOUT:   %addr.loc17: %ptr.dfe = addr_of %p.ref
-// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
+// CHECK:STDOUT:   %addr.loc10: %ptr.dfe = addr_of %p.ref
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%addr.loc10)
 // CHECK:STDOUT:   %Op.bound.loc9: <bound method> = bound_method %p.var, constants.%Op.64b
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %bound_method.loc9: <bound method> = bound_method %p.var, %Op.specific_fn.1
@@ -356,7 +328,7 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_const_pointer_param.carbon
+// CHECK:STDOUT: --- import_const_pointer_param.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %S: type = class_type @S [concrete]
@@ -366,6 +338,8 @@ fn F() {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.ff5: type = ptr_type %const [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
 // CHECK:STDOUT:   %Op.type.372: type = fn_type @Op.2, @Destroy.impl(%const) [concrete]
 // CHECK:STDOUT:   %Op.af7: %Op.type.372 = struct_value () [concrete]
 // CHECK:STDOUT: }
@@ -373,10 +347,15 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .S = %S.decl
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
@@ -396,10 +375,11 @@ fn F() {
 // CHECK:STDOUT:     %const: type = const_type %S.ref [concrete = constants.%const]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %s: ref %const = bind_name s, %s.var
-// CHECK:STDOUT:   %Cpp.ref.loc18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %Cpp.ref.loc11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %s.ref: ref %const = name_ref s, %s
-// CHECK:STDOUT:   %addr.loc18: %ptr.ff5 = addr_of %s.ref
+// CHECK:STDOUT:   %addr.loc11: %ptr.ff5 = addr_of %s.ref
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%addr.loc11)
 // CHECK:STDOUT:   %Op.bound.loc10_3.1: <bound method> = bound_method %.loc10_3, constants.%Op.af7
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %bound_method.loc10_3.1: <bound method> = bound_method %.loc10_3, %Op.specific_fn.1
@@ -454,10 +434,12 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_double_pointer_return.carbon
+// CHECK:STDOUT: --- import_double_pointer_return.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %ptr.5c7: type = ptr_type %S [concrete]
+// CHECK:STDOUT:   %ptr.dfe: type = ptr_type %ptr.5c7 [concrete]
 // CHECK:STDOUT:   %IngestDoublePointer.type: type = fn_type @IngestDoublePointer [concrete]
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %IngestDoublePointer: %IngestDoublePointer.type = struct_value () [concrete]
@@ -482,42 +464,57 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %IngestDoublePointer.ref: %IngestDoublePointer.type = name_ref IngestDoublePointer, file.%IngestDoublePointer.decl [concrete = constants.%IngestDoublePointer]
-// CHECK:STDOUT:   %Cpp.ref.loc17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref.loc17: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
-// CHECK:STDOUT:   %foo.call.loc17: init <error> = call %foo.ref.loc17()
-// CHECK:STDOUT:   %IngestDoublePointer.call: init %empty_tuple.type = call %IngestDoublePointer.ref(<error>)
-// CHECK:STDOUT:   %Cpp.ref.loc18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref.loc18: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
-// CHECK:STDOUT:   %foo.call.loc18: init <error> = call %foo.ref.loc18()
+// CHECK:STDOUT:   %Cpp.ref.loc10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref.loc10: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %foo.call.loc10: init %ptr.dfe = call %foo.ref.loc10()
+// CHECK:STDOUT:   %.loc10_31.1: %ptr.dfe = value_of_initializer %foo.call.loc10
+// CHECK:STDOUT:   %.loc10_31.2: %ptr.dfe = converted %foo.call.loc10, %.loc10_31.1
+// CHECK:STDOUT:   %IngestDoublePointer.call: init %empty_tuple.type = call %IngestDoublePointer.ref(%.loc10_31.2)
+// CHECK:STDOUT:   %Cpp.ref.loc11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref.loc11: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %foo.call.loc11: init %ptr.dfe = call %foo.ref.loc11()
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_const_pointer_return.carbon
+// CHECK:STDOUT: --- import_const_pointer_return.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %const: type = const_type %S [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %const [concrete]
 // CHECK:STDOUT:   %IngestConstPointer.type: type = fn_type @IngestConstPointer [concrete]
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %IngestConstPointer: %IngestConstPointer.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .S = %S.decl
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %IngestConstPointer.ref: %IngestConstPointer.type = name_ref IngestConstPointer, file.%IngestConstPointer.decl [concrete = constants.%IngestConstPointer]
-// CHECK:STDOUT:   %Cpp.ref.loc17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref.loc17: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %IngestConstPointer.call: init %empty_tuple.type = call %IngestConstPointer.ref(<error>)
-// CHECK:STDOUT:   %Cpp.ref.loc18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %foo.ref.loc18: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %Cpp.ref.loc10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref.loc10: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %foo.call.loc10: init %ptr = call %foo.ref.loc10()
+// CHECK:STDOUT:   %.loc10_30.1: %ptr = value_of_initializer %foo.call.loc10
+// CHECK:STDOUT:   %.loc10_30.2: %ptr = converted %foo.call.loc10, %.loc10_30.1
+// CHECK:STDOUT:   %IngestConstPointer.call: init %empty_tuple.type = call %IngestConstPointer.ref(%.loc10_30.2)
+// CHECK:STDOUT:   %Cpp.ref.loc11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref.loc11: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %foo.call.loc11: init %ptr = call %foo.ref.loc11()
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/interop/cpp/function/union.carbon

@@ -381,7 +381,7 @@ import Cpp library "decl_pointer_return_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_decl_pointer_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: nullable pointer: U *` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_decl_pointer_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
   // CHECK:STDERR:   Cpp.foo();
   // CHECK:STDERR:   ^~~~~~~
   // CHECK:STDERR: fail_todo_import_decl_pointer_return_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]

+ 5 - 0
toolchain/check/type.cpp

@@ -105,6 +105,11 @@ auto GetAssociatedEntityType(Context& context, SemIR::InterfaceId interface_id,
                                                   interface_specific_id);
 }
 
+auto GetConstType(Context& context, SemIR::TypeInstId inner_type_id)
+    -> SemIR::TypeId {
+  return GetTypeImpl<SemIR::ConstType>(context, inner_type_id);
+}
+
 auto GetSingletonType(Context& context, SemIR::TypeInstId singleton_id)
     -> SemIR::TypeId {
   CARBON_CHECK(SemIR::IsSingletonInstId(singleton_id));

+ 4 - 0
toolchain/check/type.h

@@ -38,6 +38,10 @@ auto GetAssociatedEntityType(Context& context, SemIR::InterfaceId interface_id,
 auto GetSingletonType(Context& context, SemIR::TypeInstId singleton_id)
     -> SemIR::TypeId;
 
+// Gets a const-qualified version of a type.
+auto GetConstType(Context& context, SemIR::TypeInstId inner_type_id)
+    -> SemIR::TypeId;
+
 // Gets a class type.
 auto GetClassType(Context& context, SemIR::ClassId class_id,
                   SemIR::SpecificId specific_id) -> SemIR::TypeId;