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

Support assigning to a struct field through a macro (#6843)

Example:

```carbon
import Cpp inline '''
struct B {
  int c;
};
struct A {
  B b;
};
A a;
#define m a.b.c
''';

fn F() {
  Cpp.m = 2;
}
```
Nicholas Bishop 1 месяц назад
Родитель
Сommit
8a4888c3df
2 измененных файлов с 180 добавлено и 12 удалено
  1. 59 6
      toolchain/check/cpp/macros.cpp
  2. 121 6
      toolchain/check/testdata/interop/cpp/macros.carbon

+ 59 - 6
toolchain/check/cpp/macros.cpp

@@ -12,6 +12,8 @@
 #include "toolchain/check/cpp/constant.h"
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/literal.h"
+#include "toolchain/check/member_access.h"
+#include "toolchain/check/type_completion.h"
 
 namespace Carbon::Check {
 
@@ -113,18 +115,69 @@ auto TryEvaluateMacro(Context& context, SemIR::LocId loc_id,
     const auto* value_decl =
         ap_value.getLValueBase().get<const clang::ValueDecl*>();
 
+    if (!ap_value.hasLValuePath()) {
+      context.TODO(loc_id, "Macro expanded to lvalue with no path");
+      return SemIR::ErrorInst::InstId;
+    }
+
+    if (ap_value.isLValueOnePastTheEnd()) {
+      context.TODO(loc_id, "Macro expanded to a one-past-the-end lvalue");
+      return SemIR::ErrorInst::InstId;
+    }
+
     auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
         // TODO: can this const_cast be avoided?
         const_cast<clang::ValueDecl*>(value_decl));
 
-    if (ap_value.hasLValuePath() && ap_value.getLValuePath().size() > 0) {
-      context.TODO(loc_id, "Macro evaluated to an lvalue with a path: " +
-                               ap_value.getAsString(context.ast_context(),
-                                                    result_expr->getType()));
-      return SemIR::ErrorInst::InstId;
+    auto inst_id = ImportCppDecl(context, loc_id, key);
+    if (ap_value.getLValuePath().size() == 0) {
+      return inst_id;
     }
 
-    return ImportCppDecl(context, loc_id, key);
+    // Import the base type so that its fields can be accessed.
+    auto var_storage = context.insts().GetAs<SemIR::VarStorage>(inst_id);
+    // TODO: currently an error isn't reachable here because incomplete
+    // array types can't be imported. Once that changes, switch to
+    // `RequireCompleteType` and handle the error.
+    CompleteTypeOrCheckFail(context, var_storage.type_id);
+
+    clang::QualType qual_type = ap_value.getLValueBase().getType();
+    for (const auto& entry : ap_value.getLValuePath()) {
+      if (qual_type->isArrayType()) {
+        context.TODO(loc_id, "Macro expanded to array type");
+      } else {
+        const auto* decl =
+            cast<clang::Decl>(entry.getAsBaseOrMember().getPointer());
+
+        const auto* field_decl = dyn_cast<clang::FieldDecl>(decl);
+        if (!field_decl) {
+          context.TODO(loc_id, "Macro expanded to a base class subobject");
+          return SemIR::ErrorInst::InstId;
+        }
+
+        auto field_inst_id =
+            ImportCppDecl(context, loc_id,
+                          SemIR::ClangDeclKey::ForNonFunctionDecl(
+                              const_cast<clang::FieldDecl*>(field_decl)));
+
+        if (field_inst_id == SemIR::ErrorInst::InstId) {
+          context.TODO(loc_id,
+                       "Unsupported field in macro expansion: " +
+                           ap_value.getAsString(context.ast_context(),
+                                                result_expr->getType()));
+          return SemIR::ErrorInst::InstId;
+        }
+
+        const SemIR::FieldDecl& field_decl_inst =
+            context.insts().GetAs<SemIR::FieldDecl>(field_inst_id);
+
+        qual_type = field_decl->getType();
+        inst_id = PerformMemberAccess(context, loc_id, inst_id,
+                                      field_decl_inst.name_id);
+      }
+    }
+
+    return inst_id;
   } else {
     auto const_id =
         MapAPValueToConstant(context, loc_id, ap_value, result_expr->getType());

+ 121 - 6
toolchain/check/testdata/interop/cpp/macros.carbon

@@ -891,27 +891,82 @@ fn F() {
   (*Cpp.m) = 2;
 }
 
-// --- fail_todo_assign_subobject.carbon
+// --- assign_subobject.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+struct B {
+  int c;
+};
+struct A {
+  B b;
+};
+A a;
+#define m a.b.c
+''';
+
+fn F() {
+  //@dump-sem-ir-begin
+  Cpp.m = 2;
+  //@dump-sem-ir-end
+}
+
+// --- fail_todo_assign_array.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp inline '''
 struct Struct {
-  int field;
+  // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+3]]:7: error: semantics TODO: `Unsupported: field declaration has unhandled type or kind` [SemanticsTodo]
+  // CHECK:STDERR:   int arr[3];
+  // CHECK:STDERR:       ^
+  int arr[3];
 };
 Struct s;
-#define m s.field
+#define m s.arr[1]
+''';
+
+fn F() {
+  // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+15]]:3: note: in `Cpp` name lookup for `m` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.m = 2;
+  // CHECK:STDERR:   ^~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+11]]:3: error: semantics TODO: `Unsupported field in macro expansion: &s.arr[1]` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.m = 2;
+  // CHECK:STDERR:   ^~~~~
+  // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+8]]:3: note: in `Cpp` name lookup for `m` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.m = 2;
+  // CHECK:STDERR:   ^~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+4]]:3: error: member name `m` not found in `Cpp` [MemberNameNotFoundInInstScope]
+  // CHECK:STDERR:   Cpp.m = 2;
+  // CHECK:STDERR:   ^~~~~
+  // CHECK:STDERR:
+  Cpp.m = 2;
+}
+
+// --- fail_todo_assign_base_class_member.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+struct A {
+  int n;
+};
+struct B : A {} b;
+#define m b.n
 ''';
 
 fn F() {
-  // CHECK:STDERR: fail_todo_assign_subobject.carbon:[[@LINE+11]]:3: error: semantics TODO: `Macro evaluated to an lvalue with a path: &s.field` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_assign_base_class_member.carbon:[[@LINE+11]]:3: error: semantics TODO: `Macro expanded to a base class subobject` [SemanticsTodo]
   // CHECK:STDERR:   Cpp.m = 2;
   // CHECK:STDERR:   ^~~~~
-  // CHECK:STDERR: fail_todo_assign_subobject.carbon:[[@LINE+8]]:3: note: in `Cpp` name lookup for `m` [InCppNameLookup]
+  // CHECK:STDERR: fail_todo_assign_base_class_member.carbon:[[@LINE+8]]:3: note: in `Cpp` name lookup for `m` [InCppNameLookup]
   // CHECK:STDERR:   Cpp.m = 2;
   // CHECK:STDERR:   ^~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_todo_assign_subobject.carbon:[[@LINE+4]]:3: error: member name `m` not found in `Cpp` [MemberNameNotFoundInInstScope]
+  // CHECK:STDERR: fail_todo_assign_base_class_member.carbon:[[@LINE+4]]:3: error: member name `m` not found in `Cpp` [MemberNameNotFoundInInstScope]
   // CHECK:STDERR:   Cpp.m = 2;
   // CHECK:STDERR:   ^~~~~
   // CHECK:STDERR:
@@ -2015,3 +2070,63 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- assign_subobject.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [concrete]
+// CHECK:STDOUT:   %pattern_type.9de: type = pattern_type %A [concrete]
+// CHECK:STDOUT:   %B: type = class_type @B [concrete]
+// CHECK:STDOUT:   %A.elem: type = unbound_element_type %A, %B [concrete]
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %B.elem: type = unbound_element_type %B, %i32 [concrete]
+// CHECK:STDOUT:   %.1b7: ref %B = class_element_access imports.%a.var, element0 [concrete]
+// CHECK:STDOUT:   %.59e: ref %i32 = class_element_access %.1b7, element0 [concrete]
+// CHECK:STDOUT:   %int_2.ecc: Core.IntLiteral = int_value 2 [concrete]
+// CHECK:STDOUT:   %ImplicitAs.type.e8c: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
+// CHECK:STDOUT:   %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.3c2: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6 = struct_value () [symbolic]
+// CHECK:STDOUT:   %ImplicitAs.impl_witness.6bc: <witness> = impl_witness imports.%ImplicitAs.impl_witness_table.74f, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e0d: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.e0d = struct_value () [concrete]
+// CHECK:STDOUT:   %ImplicitAs.facet: %ImplicitAs.type.e8c = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.6bc) [concrete]
+// CHECK:STDOUT:   %ImplicitAs.WithSelf.Convert.type.b37: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%i32, %ImplicitAs.facet) [concrete]
+// CHECK:STDOUT:   %.545: type = fn_type_with_self_type %ImplicitAs.WithSelf.Convert.type.b37, %ImplicitAs.facet [concrete]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: <bound method> = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5 [concrete]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
+// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %int_2.ecc, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
+// CHECK:STDOUT:   %int_2.ef8: %i32 = int_value 2 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .m = @F.%.loc17_6.2
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a.patt: %pattern_type.9de = ref_binding_pattern a [concrete]
+// CHECK:STDOUT:   %a.var_patt: %pattern_type.9de = var_pattern %a.patt [concrete]
+// CHECK:STDOUT:   %a.var: ref %A = var %a.var_patt [concrete]
+// CHECK:STDOUT:   %Core.import_ref.42d: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.4e6) = import_ref Core//prelude/types/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.3c2)]
+// CHECK:STDOUT:   %ImplicitAs.impl_witness_table.74f = impl_witness_table (%Core.import_ref.42d), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
+// 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:   %b.ref: %A.elem = name_ref b, @A.%.1 [concrete = @A.%.1]
+// CHECK:STDOUT:   %.loc17_6.1: ref %B = class_element_access imports.%a.var, element0 [concrete = constants.%.1b7]
+// CHECK:STDOUT:   %c.ref: %B.elem = name_ref c, @B.%.1 [concrete = @B.%.1]
+// CHECK:STDOUT:   %.loc17_6.2: ref %i32 = class_element_access %.loc17_6.1, element0 [concrete = constants.%.59e]
+// CHECK:STDOUT:   %m.ref: ref %i32 = name_ref m, %.loc17_6.2 [concrete = constants.%.59e]
+// CHECK:STDOUT:   %int_2: Core.IntLiteral = int_value 2 [concrete = constants.%int_2.ecc]
+// CHECK:STDOUT:   %impl.elem0: %.545 = impl_witness_access constants.%ImplicitAs.impl_witness.6bc, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.0b5]
+// CHECK:STDOUT:   %bound_method.loc17_9.1: <bound method> = bound_method %int_2, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
+// CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc17_9.2: <bound method> = bound_method %int_2, %specific_fn [concrete = constants.%bound_method]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc17_9.2(%int_2) [concrete = constants.%int_2.ef8]
+// CHECK:STDOUT:   %.loc17_9: init %i32 = converted %int_2, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_2.ef8]
+// CHECK:STDOUT:   assign %m.ref, %.loc17_9
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT: