fix: reject {} initialization for non-aggregate C++ classes (#6675)

## Summary

Fixes the toolchain incorrectly allowing `{}` initialization for
non-aggregate C++ classes.

## Problem

When importing an empty C++ class, the toolchain was treating it as a
Carbon empty struct, which allowed initialization from `{}`. This is
incorrect for non-aggregate classes (e.g., those with user-declared
constructors).

```carbon
import Cpp inline '''
struct X { X(); };  // non-aggregate (has user-declared constructor)
''';

fn Make() {
  var x: Cpp.X = {};  // incorrectly accepted, should be rejected
}
```

## Solution

Added a check for `clang_def->isAggregate()` in `ImportClassObjectRepr`
so that only aggregate classes get the empty struct representation.

**Before:**
```cpp
if (clang_def->isEmpty() && !clang_def->getNumBases()) {
```

**After:**
```cpp
if (clang_def->isEmpty() && !clang_def->getNumBases() &&
    clang_def->isAggregate()) {
```

## Testing

Added test file
`toolchain/check/testdata/interop/cpp/class/non_aggregate_init.carbon`
with:
- Non-aggregate class (`struct X { X(); }`) - should reject `{}`
initialization
- Aggregate class (`struct Y {}`) - should accept `{}` initialization

Note: I couldn't run tests locally due to clang version requirements
(needs >= 19, have 17). The CI should validate the changes.

Closes #6669

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
little Kitchen
2026-02-24 18:19:50 +00:00
committed by GitHub
co-authored by Richard Smith
parent 9915e155a3
commit 8edd5eb9a1
5 changed files with 181 additions and 216 deletions
+7 -4
View File
@@ -429,11 +429,14 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
return SemIR::ErrorInst::TypeInstId;
}
// For now, if the class is empty, produce an empty struct as the object
// representation. This allows our tests to continue to pass while we don't
// properly support initializing imported C++ classes.
// For now, if the class is empty and an aggregate, produce an empty struct as
// the object representation. This allows our tests to continue to pass while
// we don't properly support initializing imported C++ classes. We only do
// this for aggregates so that non-aggregate classes are not incorrectly
// initializable from `{}`.
// TODO: Remove this.
if (clang_def->isEmpty() && !clang_def->getNumBases()) {
if (clang_def->isEmpty() && !clang_def->getNumBases() &&
clang_def->isAggregate()) {
return context.types().GetAsTypeInstId(AddInst(
context,
MakeImportedLocIdAndInst(