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

C++ Interop: Support reference types in fields and globals (#6187)

Implemented by generalizing the reference type support for parameters
and return values to other use cases.
The changes to the `method.carbon` test are due to to supporting the
reference types but not supporting the necessary conversions.

C++ Interop Demo:

```c++
// global.h

struct C {
  int member = 0;
  int& member_ref = member;
};

extern C& global;
```

```c++
// global.cpp

#include "global.h"

static C static_c;

C& global= static_c;
```

```carbon
// main.carbon

library "Main";

import Core library "io";

import Cpp library "global.h";

fn Run() -> i32 {
  Core.Print(Cpp.global->member);
  ++(*Cpp.global->member_ref);
  Core.Print(Cpp.global->member);
  ++(*Cpp.global->member_ref);
  Core.Print(Cpp.global->member);
  return 0;
}
```

```shell
$ clang++ -stdlib=libc++ -c global.cpp
$ bazel build toolchain:carbon && bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link global.o main.o --output=demo
$ ./demo
0
1
2
```

**Without this change**:
```shell
main.carbon:10:14: error: semantics TODO: `Unsupported: var type: C &`
  Core.Print(Cpp.global->member);
             ^~~~~~~~~~
main.carbon:10:14: note: in `Cpp` name lookup for `global`
  Core.Print(Cpp.global->member);
             ^~~~~~~~~~
```

Part of #6006 and #6186.
Boaz Brickner 6 месяцев назад
Родитель
Сommit
1ac1d11063

+ 24 - 18
toolchain/check/cpp/import.cpp

@@ -1243,6 +1243,23 @@ static auto MapPointerType(Context& context, clang::QualType type,
           .type_id = pointer_type_id};
 }
 
+// Maps a C++ reference type to a Carbon type.
+// We map `T&` to `T*`, and `T&&` to `T`.
+// TODO: Revisit this and decide what we really want to do here.
+static auto MapReferenceType(Context& context, clang::QualType type,
+                             TypeExpr referenced_type_expr) -> TypeExpr {
+  CARBON_CHECK(type->isReferenceType());
+
+  if (!type->isLValueReferenceType()) {
+    return referenced_type_expr;
+  }
+
+  SemIR::TypeId pointer_type_id =
+      GetPointerType(context, referenced_type_expr.inst_id);
+  return {.inst_id = context.types().GetInstId(pointer_type_id),
+          .type_id = pointer_type_id};
+}
+
 // Maps a C++ type to a Carbon type. `type` should not be canonicalized because
 // we check for pointer nullability and nullability will be lost by
 // canonicalization.
@@ -1256,6 +1273,8 @@ static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
       type = type.getUnqualifiedType();
     } else if (type->isPointerType()) {
       type = type->getPointeeType();
+    } else if (type->isReferenceType()) {
+      type = type.getNonReferenceType();
     } else {
       break;
     }
@@ -1274,6 +1293,8 @@ static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
       mapped = MapQualifiedType(context, wrapper, mapped);
     } else if (wrapper->isPointerType()) {
       mapped = MapPointerType(context, wrapper, mapped);
+    } else if (wrapper->isReferenceType()) {
+      mapped = MapReferenceType(context, wrapper, mapped);
     } else {
       CARBON_FATAL("Unexpected wrapper type {0}", wrapper.getAsString());
     }
@@ -1373,11 +1394,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
     // TODO: The presence of qualifiers here is probably a Clang bug.
     clang::QualType param_type = orig_param_type.getUnqualifiedType();
 
-    // We map `T&` parameters to `addr param: T*`, and `T&&` parameters to
-    // `param: T`.
-    // TODO: Revisit this and decide what we really want to do here.
     bool is_ref_param = param_type->isLValueReferenceType();
-    param_type = param_type.getNonReferenceType();
 
     // Mark the start of a region of insts, needed for the type expression
     // created later with the call of `EndSubpatternAsExpr()`.
@@ -1395,10 +1412,6 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
       return SemIR::InstBlockId::None;
     }
 
-    if (is_ref_param) {
-      type_id = GetPointerType(context, orig_type_inst_id);
-    }
-
     llvm::StringRef param_name = param->getName();
     SemIR::NameId name_id =
         param_name.empty()
@@ -1425,6 +1438,8 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
                        .subpattern_id = pattern_id,
                        .index = SemIR::CallParamIndex::None})});
     if (is_ref_param) {
+      // We map `T&` parameters to `addr param: T*`.
+      // TODO: Revisit this and decide what we really want to do here.
       pattern_id = AddPatternInst(
           context, {param_loc_id,
                     SemIR::AddrPattern(
@@ -1446,12 +1461,7 @@ static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id,
                               clang::FunctionDecl* clang_decl) -> TypeExpr {
   clang::QualType orig_ret_type = clang_decl->getReturnType();
   if (!orig_ret_type->isVoidType()) {
-    // We map `T&` return type to `addr param: T*`, and `T&&` parameters to
-    // `param: T`.
-    // TODO: Revisit this and decide what we really want to do here.
-    clang::QualType ret_type = orig_ret_type.getNonReferenceType();
-
-    auto [orig_type_inst_id, type_id] = MapType(context, loc_id, ret_type);
+    auto [orig_type_inst_id, type_id] = MapType(context, loc_id, orig_ret_type);
     if (!orig_type_inst_id.has_value()) {
       context.TODO(loc_id, llvm::formatv("Unsupported: return type: {0}",
                                          orig_ret_type.getAsString()));
@@ -1459,10 +1469,6 @@ static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id,
               .type_id = SemIR::ErrorInst::TypeId};
     }
 
-    if (orig_ret_type->isLValueReferenceType()) {
-      type_id = GetPointerType(context, orig_type_inst_id);
-    }
-
     return {orig_type_inst_id, type_id};
   }
 

+ 26 - 24
toolchain/check/testdata/interop/cpp/class/field.carbon

@@ -16,6 +16,7 @@ struct Struct {
   int a;
   int b;
   int* _Nonnull p;
+  int& r;
 };
 
 // --- use_struct_fields.carbon
@@ -24,9 +25,9 @@ library "[[@TEST_NAME]]";
 
 import Cpp library "struct.h";
 
-fn F(s: Cpp.Struct) -> (i32, i32, i32) {
+fn F(s: Cpp.Struct) -> (i32, i32, i32, i32) {
   //@dump-sem-ir-begin
-  return (s.a, s.b, *s.p);
+  return (s.a, s.b, *s.p, *s.r);
   //@dump-sem-ir-end
 }
 
@@ -160,8 +161,6 @@ struct UnsupportedMembers {
   volatile int is_volatile;
   // Nullable pointers are not supported.
   int *is_nullable;
-  // References are not supported.
-  int &is_reference;
   // But this should be fine.
   int integer;
 };
@@ -202,21 +201,11 @@ fn Test(m: Cpp.UnsupportedMembers*) {
   // CHECK:STDERR:        ^
   let a: i32 = m->is_volatile;
 
-  // CHECK:STDERR: fail_use_unsupported_members.carbon:[[@LINE+8]]:17: note: in `Cpp` name lookup for `is_nullable` [InCppNameLookup]
+  // CHECK:STDERR: fail_use_unsupported_members.carbon:[[@LINE+4]]:17: note: in `Cpp` name lookup for `is_nullable` [InCppNameLookup]
   // CHECK:STDERR:   let b: i32 = *m->is_nullable;
   // CHECK:STDERR:                 ^~~~~~~~~~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_use_unsupported_members.carbon:[[@LINE-17]]:10: in file included here [InCppInclude]
-  // CHECK:STDERR: ./unsupported_members.h:8:8: error: semantics TODO: `Unsupported: field declaration has unhandled type or kind` [SemanticsTodo]
-  // CHECK:STDERR:   int &is_reference;
-  // CHECK:STDERR:        ^
   let b: i32 = *m->is_nullable;
-
-  // CHECK:STDERR: fail_use_unsupported_members.carbon:[[@LINE+4]]:16: note: in `Cpp` name lookup for `is_reference` [InCppNameLookup]
-  // CHECK:STDERR:   let c: i32 = m->is_reference;
-  // CHECK:STDERR:                ^~~~~~~~~~~~~~~
-  // CHECK:STDERR:
-  let c: i32 = m->is_reference;
 }
 
 // CHECK:STDOUT: --- use_struct_fields.carbon
@@ -226,7 +215,7 @@ fn Test(m: Cpp.UnsupportedMembers*) {
 // CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
 // CHECK:STDOUT:   %N: Core.IntLiteral = bind_symbolic_name N, 0 [symbolic]
 // CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
-// CHECK:STDOUT:   %tuple.type.189: type = tuple_type (%i32, %i32, %i32) [concrete]
+// CHECK:STDOUT:   %tuple.type.7ea: type = tuple_type (%i32, %i32, %i32, %i32) [concrete]
 // CHECK:STDOUT:   %Struct.elem.86b: type = unbound_element_type %Struct, %i32 [concrete]
 // CHECK:STDOUT:   %ptr.235: type = ptr_type %i32 [concrete]
 // CHECK:STDOUT:   %Struct.elem.765: type = unbound_element_type %Struct, %ptr.235 [concrete]
@@ -247,7 +236,7 @@ fn Test(m: Cpp.UnsupportedMembers*) {
 // CHECK:STDOUT:   %Copy.impl_witness_table.1ed = impl_witness_table (%Core.import_ref.d0f6), @Int.as.Copy.impl [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%s.param: %Struct) -> %return.param: %tuple.type.189 {
+// CHECK:STDOUT: fn @F(%s.param: %Struct) -> %return.param: %tuple.type.7ea {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %s.ref.loc8_11: %Struct = name_ref s, %s
 // CHECK:STDOUT:   %a.ref: %Struct.elem.86b = name_ref a, @Struct.%.1 [concrete = @Struct.%.1]
@@ -262,21 +251,26 @@ fn Test(m: Cpp.UnsupportedMembers*) {
 // CHECK:STDOUT:   %.loc8_23.1: ref %ptr.235 = class_element_access %s.ref.loc8_22, element2
 // CHECK:STDOUT:   %.loc8_23.2: %ptr.235 = bind_value %.loc8_23.1
 // CHECK:STDOUT:   %.loc8_21.1: ref %i32 = deref %.loc8_23.2
-// CHECK:STDOUT:   %.loc8_25.1: %tuple.type.189 = tuple_literal (%.loc8_12.2, %.loc8_17.2, %.loc8_21.1)
+// CHECK:STDOUT:   %s.ref.loc8_28: %Struct = name_ref s, %s
+// CHECK:STDOUT:   %r.ref: %Struct.elem.765 = name_ref r, @Struct.%.4 [concrete = @Struct.%.4]
+// CHECK:STDOUT:   %.loc8_29.1: ref %ptr.235 = class_element_access %s.ref.loc8_28, element3
+// CHECK:STDOUT:   %.loc8_29.2: %ptr.235 = bind_value %.loc8_29.1
+// CHECK:STDOUT:   %.loc8_27.1: ref %i32 = deref %.loc8_29.2
+// CHECK:STDOUT:   %.loc8_31.1: %tuple.type.7ea = tuple_literal (%.loc8_12.2, %.loc8_17.2, %.loc8_21.1, %.loc8_27.1)
 // CHECK:STDOUT:   %impl.elem0.loc8_12: %.7fa = impl_witness_access constants.%Copy.impl_witness.a32, element0 [concrete = constants.%Int.as.Copy.impl.Op.f59]
 // CHECK:STDOUT:   %bound_method.loc8_12.1: <bound method> = bound_method %.loc8_12.2, %impl.elem0.loc8_12
 // CHECK:STDOUT:   %specific_fn.loc8_12: <specific function> = specific_function %impl.elem0.loc8_12, @Int.as.Copy.impl.Op(constants.%int_32) [concrete = constants.%Int.as.Copy.impl.Op.specific_fn]
 // CHECK:STDOUT:   %bound_method.loc8_12.2: <bound method> = bound_method %.loc8_12.2, %specific_fn.loc8_12
 // CHECK:STDOUT:   %Int.as.Copy.impl.Op.call.loc8_12: init %i32 = call %bound_method.loc8_12.2(%.loc8_12.2)
 // CHECK:STDOUT:   %tuple.elem0: ref %i32 = tuple_access %return, element0
-// CHECK:STDOUT:   %.loc8_25.2: init %i32 = initialize_from %Int.as.Copy.impl.Op.call.loc8_12 to %tuple.elem0
+// CHECK:STDOUT:   %.loc8_31.2: init %i32 = initialize_from %Int.as.Copy.impl.Op.call.loc8_12 to %tuple.elem0
 // CHECK:STDOUT:   %impl.elem0.loc8_17: %.7fa = impl_witness_access constants.%Copy.impl_witness.a32, element0 [concrete = constants.%Int.as.Copy.impl.Op.f59]
 // CHECK:STDOUT:   %bound_method.loc8_17.1: <bound method> = bound_method %.loc8_17.2, %impl.elem0.loc8_17
 // CHECK:STDOUT:   %specific_fn.loc8_17: <specific function> = specific_function %impl.elem0.loc8_17, @Int.as.Copy.impl.Op(constants.%int_32) [concrete = constants.%Int.as.Copy.impl.Op.specific_fn]
 // CHECK:STDOUT:   %bound_method.loc8_17.2: <bound method> = bound_method %.loc8_17.2, %specific_fn.loc8_17
 // CHECK:STDOUT:   %Int.as.Copy.impl.Op.call.loc8_17: init %i32 = call %bound_method.loc8_17.2(%.loc8_17.2)
 // CHECK:STDOUT:   %tuple.elem1: ref %i32 = tuple_access %return, element1
-// CHECK:STDOUT:   %.loc8_25.3: init %i32 = initialize_from %Int.as.Copy.impl.Op.call.loc8_17 to %tuple.elem1
+// CHECK:STDOUT:   %.loc8_31.3: init %i32 = initialize_from %Int.as.Copy.impl.Op.call.loc8_17 to %tuple.elem1
 // CHECK:STDOUT:   %.loc8_21.2: %i32 = bind_value %.loc8_21.1
 // CHECK:STDOUT:   %impl.elem0.loc8_21: %.7fa = impl_witness_access constants.%Copy.impl_witness.a32, element0 [concrete = constants.%Int.as.Copy.impl.Op.f59]
 // CHECK:STDOUT:   %bound_method.loc8_21.1: <bound method> = bound_method %.loc8_21.2, %impl.elem0.loc8_21
@@ -284,10 +278,18 @@ fn Test(m: Cpp.UnsupportedMembers*) {
 // CHECK:STDOUT:   %bound_method.loc8_21.2: <bound method> = bound_method %.loc8_21.2, %specific_fn.loc8_21
 // CHECK:STDOUT:   %Int.as.Copy.impl.Op.call.loc8_21: init %i32 = call %bound_method.loc8_21.2(%.loc8_21.2)
 // CHECK:STDOUT:   %tuple.elem2: ref %i32 = tuple_access %return, element2
-// CHECK:STDOUT:   %.loc8_25.4: init %i32 = initialize_from %Int.as.Copy.impl.Op.call.loc8_21 to %tuple.elem2
-// CHECK:STDOUT:   %.loc8_25.5: init %tuple.type.189 = tuple_init (%.loc8_25.2, %.loc8_25.3, %.loc8_25.4) to %return
-// CHECK:STDOUT:   %.loc8_26: init %tuple.type.189 = converted %.loc8_25.1, %.loc8_25.5
-// CHECK:STDOUT:   return %.loc8_26 to %return
+// CHECK:STDOUT:   %.loc8_31.4: init %i32 = initialize_from %Int.as.Copy.impl.Op.call.loc8_21 to %tuple.elem2
+// CHECK:STDOUT:   %.loc8_27.2: %i32 = bind_value %.loc8_27.1
+// CHECK:STDOUT:   %impl.elem0.loc8_27: %.7fa = impl_witness_access constants.%Copy.impl_witness.a32, element0 [concrete = constants.%Int.as.Copy.impl.Op.f59]
+// CHECK:STDOUT:   %bound_method.loc8_27.1: <bound method> = bound_method %.loc8_27.2, %impl.elem0.loc8_27
+// CHECK:STDOUT:   %specific_fn.loc8_27: <specific function> = specific_function %impl.elem0.loc8_27, @Int.as.Copy.impl.Op(constants.%int_32) [concrete = constants.%Int.as.Copy.impl.Op.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc8_27.2: <bound method> = bound_method %.loc8_27.2, %specific_fn.loc8_27
+// CHECK:STDOUT:   %Int.as.Copy.impl.Op.call.loc8_27: init %i32 = call %bound_method.loc8_27.2(%.loc8_27.2)
+// CHECK:STDOUT:   %tuple.elem3: ref %i32 = tuple_access %return, element3
+// CHECK:STDOUT:   %.loc8_31.5: init %i32 = initialize_from %Int.as.Copy.impl.Op.call.loc8_27 to %tuple.elem3
+// CHECK:STDOUT:   %.loc8_31.6: init %tuple.type.7ea = tuple_init (%.loc8_31.2, %.loc8_31.3, %.loc8_31.4, %.loc8_31.5) to %return
+// CHECK:STDOUT:   %.loc8_32: init %tuple.type.7ea = converted %.loc8_31.1, %.loc8_31.6
+// CHECK:STDOUT:   return %.loc8_32 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- use_union_fields.carbon

+ 43 - 19
toolchain/check/testdata/interop/cpp/class/method.carbon

@@ -47,43 +47,67 @@ fn F(v: Cpp.HasQualifiers, p: Cpp.HasQualifiers*) {
 
 library "[[@TEST_NAME]]";
 
+// CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+8]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./object_param_qualifiers.h:10:8: error: 'this' argument to member function 'ref_ref_this' is an lvalue, but function has rvalue ref-qualifier [CppInteropParseError]
+// CHECK:STDERR:    10 |   void ref_ref_this() &&;
+// CHECK:STDERR:       |        ^
+// CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./object_param_qualifiers.h:10:8: note: 'ref_ref_this' declared here [CppInteropParseNote]
+// CHECK:STDERR:    10 |   void ref_ref_this() &&;
+// CHECK:STDERR:       |        ^
 import Cpp library "object_param_qualifiers.h";
 
 fn Value(v: Cpp.HasQualifiers) {
-  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+5]]:3: error: `addr self` method cannot be invoked on a value [AddrSelfIsNonRef]
-  // CHECK:STDERR:   v.plain();
-  // CHECK:STDERR:   ^
-  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon: note: initializing function parameter [InCallToFunctionParam]
-  // CHECK:STDERR:
   v.plain();
 
   // TODO: This should remain invalid once we support `volatile`.
-  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+4]]:3: error: semantics TODO: `Unsupported: object parameter type: volatile struct HasQualifiers` [SemanticsTodo]
-  // CHECK:STDERR:   v.volatile_this();
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
   v.volatile_this();
 
-  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+8]]:14: error: no matching function for call to 'ref_this' [CppInteropParseError]
-  // CHECK:STDERR:    29 |   v.ref_this();
-  // CHECK:STDERR:       |              ^
-  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE-20]]:10: in file included here [InCppInclude]
-  // CHECK:STDERR: ./object_param_qualifiers.h:7:8: note: candidate function not viable: expects an lvalue for object argument [CppInteropParseNote]
-  // CHECK:STDERR:     7 |   void ref_this() &;
-  // CHECK:STDERR:       |        ^
-  // CHECK:STDERR:
   v.ref_this();
 
-  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+4]]:3: error: semantics TODO: `Unsupported: object parameter type: struct HasQualifiers &&` [SemanticsTodo]
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+12]]:3: note: in thunk for C++ function used here [InCppThunk]
   // CHECK:STDERR:   v.ref_ref_this();
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~
   // CHECK:STDERR:
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE-14]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./object_param_qualifiers.h:11:8: error: 'this' argument to member function 'const_ref_ref_this' is an lvalue, but function has rvalue ref-qualifier [CppInteropParseError]
+  // CHECK:STDERR:    11 |   void const_ref_ref_this() const&&;
+  // CHECK:STDERR:       |        ^
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE-18]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./object_param_qualifiers.h:11:8: note: 'const_ref_ref_this' declared here [CppInteropParseNote]
+  // CHECK:STDERR:    11 |   void const_ref_ref_this() const&&;
+  // CHECK:STDERR:       |        ^
   v.ref_ref_this();
 
-  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+4]]:3: error: semantics TODO: `Unsupported: object parameter type: const struct HasQualifiers &&` [SemanticsTodo]
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+29]]:3: note: in thunk for C++ function used here [InCppThunk]
   // CHECK:STDERR:   v.const_ref_ref_this();
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE-25]]:3: error: `addr self` method cannot be invoked on a value [AddrSelfIsNonRef]
+  // CHECK:STDERR:   v.plain();
+  // CHECK:STDERR:   ^
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE-27]]:3: error: semantics TODO: `Unsupported: object parameter type: volatile struct HasQualifiers` [SemanticsTodo]
+  // CHECK:STDERR:   v.volatile_this();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE-29]]:14: error: no matching function for call to 'ref_this' [CppInteropParseError]
+  // CHECK:STDERR:    20 |   v.ref_this();
+  // CHECK:STDERR:       |              ^
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE-40]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./object_param_qualifiers.h:7:8: note: candidate function not viable: expects an lvalue for object argument [CppInteropParseNote]
+  // CHECK:STDERR:     7 |   void ref_this() &;
+  // CHECK:STDERR:       |        ^
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+8]]:3: error: cannot implicitly convert expression of type `Cpp.HasQualifiers` to `const Cpp.HasQualifiers` [ConversionFailure]
+  // CHECK:STDERR:   v.const_ref_ref_this();
+  // CHECK:STDERR:   ^
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon:[[@LINE+5]]:3: note: type `Cpp.HasQualifiers` does not implement interface `Core.ImplicitAs(const Cpp.HasQualifiers)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   v.const_ref_ref_this();
+  // CHECK:STDERR:   ^
+  // CHECK:STDERR: fail_bad_object_param_qualifiers_by_value.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
   v.const_ref_ref_this();
 }
 

+ 39 - 4
toolchain/check/testdata/interop/cpp/globals.carbon

@@ -19,6 +19,9 @@
 
 class C {};
 C global;
+C* _Nonnull global_ptr = &global;
+C& global_ref = global;
+
 
 // --- import_global_scope.carbon
 
@@ -28,6 +31,8 @@ import Cpp library "global_scope.h";
 
 fn MyF() {
   let local: Cpp.C = Cpp.global;
+  let local_ptr: Cpp.C* = Cpp.global_ptr;
+  let local_ref: Cpp.C* = Cpp.global_ref;
 }
 
 // ============================================================================
@@ -93,7 +98,9 @@ fn MyF() {
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
-// CHECK:STDOUT:   %pattern_type: type = pattern_type %C [concrete]
+// CHECK:STDOUT:   %pattern_type.217: type = pattern_type %C [concrete]
+// CHECK:STDOUT:   %ptr.d9e: type = ptr_type %C [concrete]
+// CHECK:STDOUT:   %pattern_type.a31: type = pattern_type %ptr.d9e [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -104,10 +111,14 @@ fn MyF() {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .C = %C.decl
 // CHECK:STDOUT:     .global = %global.var
+// CHECK:STDOUT:     .global_ptr = %global_ptr.var
+// CHECK:STDOUT:     .global_ref = %global_ref.var
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
 // CHECK:STDOUT:   %global.var: ref %C = var %global.var_patt [concrete]
+// CHECK:STDOUT:   %global_ptr.var: ref %ptr.d9e = var %global_ptr.var_patt [concrete]
+// CHECK:STDOUT:   %global_ref.var: ref %ptr.d9e = var %global_ref.var_patt [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -135,16 +146,40 @@ fn MyF() {
 // CHECK:STDOUT: fn @MyF() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %local.patt: %pattern_type = binding_pattern local [concrete]
+// CHECK:STDOUT:     %local.patt: %pattern_type.217 = binding_pattern local [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Cpp.ref.loc7_22: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %global.ref: ref %C = name_ref global, imports.%global.var [concrete = imports.%global.var]
-// CHECK:STDOUT:   %.loc7_17: type = splice_block %C.ref [concrete = constants.%C] {
+// CHECK:STDOUT:   %.loc7_17: type = splice_block %C.ref.loc7 [concrete = constants.%C] {
 // CHECK:STDOUT:     %Cpp.ref.loc7_14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %C.ref: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:     %C.ref.loc7: type = name_ref C, imports.%C.decl [concrete = constants.%C]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc7_25: %C = bind_value %global.ref
 // CHECK:STDOUT:   %local: %C = bind_name local, %.loc7_25
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %local_ptr.patt: %pattern_type.a31 = binding_pattern local_ptr [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc8_27: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %global_ptr.ref: ref %ptr.d9e = name_ref global_ptr, imports.%global_ptr.var [concrete = imports.%global_ptr.var]
+// CHECK:STDOUT:   %.loc8_23: type = splice_block %ptr.loc8 [concrete = constants.%ptr.d9e] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %C.ref.loc8: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:     %ptr.loc8: type = ptr_type %C.ref.loc8 [concrete = constants.%ptr.d9e]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc8_30: %ptr.d9e = bind_value %global_ptr.ref
+// CHECK:STDOUT:   %local_ptr: %ptr.d9e = bind_name local_ptr, %.loc8_30
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %local_ref.patt: %pattern_type.a31 = binding_pattern local_ref [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc9_27: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %global_ref.ref: ref %ptr.d9e = name_ref global_ref, imports.%global_ref.var [concrete = imports.%global_ref.var]
+// CHECK:STDOUT:   %.loc9_23: type = splice_block %ptr.loc9 [concrete = constants.%ptr.d9e] {
+// CHECK:STDOUT:     %Cpp.ref.loc9_18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %C.ref.loc9: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:     %ptr.loc9: type = ptr_type %C.ref.loc9 [concrete = constants.%ptr.d9e]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc9_30: %ptr.d9e = bind_value %global_ref.ref
+// CHECK:STDOUT:   %local_ref: %ptr.d9e = bind_name local_ref, %.loc9_30
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: