Skip to content

Conversation

fahadnayyar
Copy link
Contributor

@fahadnayyar fahadnayyar commented Sep 9, 2025

Swift crashes when importing C++ typedefs to forward-declared explicit template specializations. In assert build we see this assertion failure happening in clang:
Assertion failed: (!T->isDependentType() && "should not see dependent types here"), function getTypeInfoImpl, file TypeNodes.inc, line 79.

Example that crashes:

template <typename T> struct MyTemplate { T value; };
template <> struct MyTemplate<int>;  // Forward-declared specialization
typedef MyTemplate<int> MyIntTemplate;

In this patch, I propose detecting forward-declared explicit specializations in typedef imports. Instead of crashing, these specializations should be blocked from being imported with a diagnostic similar to the forward declared (but not defined) structs.

rdar://147595723

@fahadnayyar fahadnayyar self-assigned this Sep 9, 2025
@fahadnayyar fahadnayyar added the c++ interop Feature: Interoperability with C++ label Sep 9, 2025
@fahadnayyar
Copy link
Contributor Author

@swift-ci please smoke test

Copy link
Contributor

@j-hui j-hui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the problem you describe and the fix you propose make sense to me at a high level, but I'm having a bit of trouble connecting it to your explanation:

Forward-declared explicit specializations (TSK_ExplicitSpecialization without isCompleteDefinition()) cause Clang's getTypeInfoInChars() to access the primary template's dependent types, triggering this assertion failure :

Assertion failed: (!T->isDependentType() && "should not see dependent types here"), function getTypeInfoImpl, file TypeNodes.inc, line 79

Where is getTypeInfoInChars() being called (by Swift)? And why does an incomplete definition lead to that code path (in Clang, presumably)? The relationship between the incomplete definition and dependent types is a little unclear to me here.

@Xazax-hun

This comment was marked as resolved.

@fahadnayyar

This comment was marked as resolved.

@fahadnayyar

This comment was marked as off-topic.

@fahadnayyar fahadnayyar force-pushed the fnayyar/rdar-147595723 branch from 7108c3e to 7688401 Compare September 11, 2025 21:00
@Xazax-hun
Copy link
Contributor

I think not importing these types is the right fix. That being said, similar to John, I'd expect a different failure/assertion about incomplete types rather than something about dependent types. The question is, whether we end up switching to the primary template instead of the specialization inadvertently in the importer (or in Clang). If that is the case, this might be a symptom that we have a different bug (in addition to the one you fixed here). So it might be interesting to understand why is this happening, but I think that investigation can happen independently of this fix.

…specializations in typedefs

rdar://147595723
@fahadnayyar fahadnayyar force-pushed the fnayyar/rdar-147595723 branch from 7688401 to 7bfbd68 Compare September 26, 2025 03:44
@fahadnayyar fahadnayyar force-pushed the fnayyar/rdar-147595723 branch from 7bfbd68 to b5a0a4e Compare September 26, 2025 03:52
@fahadnayyar
Copy link
Contributor Author

@swift-ci please smoke test

@fahadnayyar
Copy link
Contributor Author

'd expect a different failure/assertion about incomplete types rather than something about dependent types. The question is, whether we end up switching to the primary template instead of the specialization inadvertently in the importer (or in Clang)

why does an incomplete definition lead to that code path (in Clang, presumably)? The relationship between the incomplete definition and dependent types is a little unclear to me here.

After some printf debugging in clang, I identified this root cause of the assertion failure:
Forward-declared template specializations (template<> struct MyTemplate<int>;) create a ClassTemplateSpecializationDecl that inherits fields from the primary template without template argument substitution.

in clang/lib/AST/RecordLayoutBuilder.cpp:1444, the ItaniumRecordLayoutBuilder::LayoutFields() method calls:

for (auto I = D->field_begin(), End = D->field_end(); I != End; ++I) {

This field iteration returns FieldDecl objects from the primary template that still reference T instead of the substituted type int. The specialization correctly knows its template arguments (int), but the layout builder receives unsubstituted fields and tries to get type info for the dependent type T, triggering the assertion.

Call chain: Swift's ClangImporter calls getTypeAlignInChars() → .... -> getASTRecordLayout()LayoutFields() → field iteration over unsubstituted T value → getTypeInfoInChars(T) → crash on dependent type assertion.

My guess is that in pure C++ compilation, we get an "incomplete type" error during semantic analysis before reaching layout. But Swift's ClangImporter takes a different path - it directly calls layout APIs (getTypeAlignInChars in ClangImporter::Implementation::isOverAligned) which bypasses the C++ normal semantic checks and hits the field iteration logic that exposes unsubstituted template parameters.

CC: @j-hui, @Xazax-hun

For reference, pasting the assertion failure log (just the clang part) below:

Assertion failed: (!T->isDependentType() && "should not see dependent types here"), function getTypeInfoImpl, file TypeNodes.inc, line 79.
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
1.	Swift version 6.3-dev (LLVM 9682898a32ef144, Swift 84daca65d161266)
2.	Compiling with effective version 5.10
3.	While evaluating request TypeCheckPrimaryFileRequest(source_file "main.swift")
4.	While type-checking statement at [main.swift:5:1 - line:5:1] RangeText=""
5.	While type-checking expression at [main.swift:5:1 - line:5:1] RangeText=""
6.	While type-checking-target starting at main.swift:5:1
7.	While evaluating request UnqualifiedLookupRequest(looking up 'MyIntTemplate' from 0xb16ee6f20 TopLevelCodeDecl line=5 with options { AllowProtocolMembers, IncludeOuterResults })
8.	While evaluating request LookupInModuleRequest(0xb16e53008 FileUnit file="main.swift", 'MyIntTemplate', UnqualifiedLookup, Overloadable, 0xb16e53008 FileUnit file="main.swift", { NL_RemoveNonVisible, NL_RemoveOverridden })
9.	/Users/fahadnayyar/Desktop/repos/woc-2024/InteropExamples/Example1/include/test.h:9:25: importing 'MyIntTemplate'
10.	While importing a clang type ElaboratedType 0xb16f325c0 'MyTemplate<int>' sugar imported
`-TemplateSpecializationType 0xb16f32570 'MyTemplate<int>' sugar imported
  |-name: 'MyTemplate' qualified
  | `-ClassTemplateDecl 0xb16df4ee0 </Users/fahadnayyar/Desktop/repos/woc-2024/InteropExamples/Example1/include/test.h:2:1, line:5:1> line:3:8 imported in TreeExample MyTemplate
  |-TemplateArgument type 'int'
  | `-BuiltinType 0xb16cd7110 'int'
  `-RecordType 0xb16f32550 'MyTemplate<int>' imported
    `-ClassTemplateSpecialization 0xb16f323d8 'MyTemplate'
11.	While importing a clang type TemplateSpecializationType 0xb16f32570 'MyTemplate<int>' sugar imported
|-name: 'MyTemplate' qualified
| `-ClassTemplateDecl 0xb16df4ee0 </Users/fahadnayyar/Desktop/repos/woc-2024/InteropExamples/Example1/include/test.h:2:1, line:5:1> line:3:8 imported in TreeExample MyTemplate
|-TemplateArgument type 'int'
| `-BuiltinType 0xb16cd7110 'int'
`-RecordType 0xb16f32550 'MyTemplate<int>' imported
  `-ClassTemplateSpecialization 0xb16f323d8 'MyTemplate'
12.	While importing a clang type RecordType 0xb16f32550 'MyTemplate<int>' imported
`-ClassTemplateSpecialization 0xb16f323d8 'MyTemplate'
13.	/Users/fahadnayyar/Desktop/repos/woc-2024/InteropExamples/Example1/include/test.h:3:8: importing 'MyTemplate'
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0  swift-frontend           0x000000010ec0ac80 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 80
1  swift-frontend           0x000000010ec0b178 PrintStackTraceSignalHandler(void*) + 28
2  swift-frontend           0x000000010ec09264 llvm::sys::RunSignalHandlers() + 148
3  swift-frontend           0x000000010ec0be80 SignalHandler(int, __siginfo*, void*) + 252
4  libsystem_platform.dylib 0x0000000197b7a744 _sigtramp + 56
5  libsystem_pthread.dylib  0x0000000197b70888 pthread_kill + 296
6  libsystem_c.dylib        0x0000000197a76808 abort + 124
7  libsystem_c.dylib        0x0000000197a75a3c err + 0
8  swift-frontend           0x000000010c421eec clang::ASTContext::getTypeInfoImpl(clang::Type const*) const + 612
9  swift-frontend           0x000000010c4262b8 clang::ASTContext::getTypeInfo(clang::Type const*) const + 128
10 swift-frontend           0x000000010c426018 clang::ASTContext::getTypeInfoInChars(clang::Type const*) const + 88
11 swift-frontend           0x000000010c425f94 clang::ASTContext::getTypeInfoInChars(clang::QualType) const + 76
12 swift-frontend           0x000000010cbd3bb8 (anonymous namespace)::ItaniumRecordLayoutBuilder::LayoutField(clang::FieldDecl const*, bool)::$_0::operator()(bool) const + 92
13 swift-frontend           0x000000010cbd2220 (anonymous namespace)::ItaniumRecordLayoutBuilder::LayoutField(clang::FieldDecl const*, bool) + 740
14 swift-frontend           0x000000010cbcdadc (anonymous namespace)::ItaniumRecordLayoutBuilder::LayoutFields(clang::RecordDecl const*) + 240
15 swift-frontend           0x000000010cbc0d90 (anonymous namespace)::ItaniumRecordLayoutBuilder::Layout(clang::CXXRecordDecl const*) + 76
16 swift-frontend           0x000000010cbc051c clang::ASTContext::getASTRecordLayout(clang::RecordDecl const*) const + 1236
17 swift-frontend           0x000000010c42483c clang::ASTContext::getTypeInfoImpl(clang::Type const*) const + 11188
18 swift-frontend           0x000000010c4262b8 clang::ASTContext::getTypeInfo(clang::Type const*) const + 128
19 swift-frontend           0x0000000100f56fcc clang::ASTContext::getTypeInfo(clang::QualType) const + 68
20 swift-frontend           0x0000000106ca63f0 clang::ASTContext::getTypeAlign(clang::QualType) const + 56
21 swift-frontend           0x000000010c4277b0 clang::ASTContext::getTypeAlignInChars(clang::QualType) const + 60
22 swift-frontend           0x0000000103869444 swift::ClangImporter::Implementation::isOverAligned(clang::QualType) + 60

Copy link
Contributor

@j-hui j-hui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing up all that investigation! This all makes sense to me, and not importing these incomplete types is the right thing to do.

My only concern is here that this may be confusing for users who see the explicit template specialization and do not realize that there’s no definition associated with it. We could emit a more specific diagnostic there, but I don’t think that’s necessary since trying to use that incomplete type definition is also an error in C++.

float value;
};

// Case 4: For comparison - forward-declared non-templated struct (have same behavior)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it’s not clear what the behavior is the same as. Better to make it explicit imo:

Suggested change
// Case 4: For comparison - forward-declared non-templated struct (have same behavior)
// Case 4: For comparison - forward-declared non-templated struct (also does not import)

This suggestion isn’t blocking, i.e., no need to change the comment and be forced to re-run CI.

@Xazax-hun
Copy link
Contributor

Thanks for investigating!

Forward-declared template specializations (template<> struct MyTemplate;) create a ClassTemplateSpecializationDecl that inherits fields from the primary template without template argument substitution.

This is the surprising part. The specialization is an incomplete types, so it does not have definition data, does not have fields. I'd expect clang to error out when we call D->field_begin() on an incomplete type and it is surprising that it falls back to the primary template instead. I think this might mask some bugs and maybe we should change this upstream to fail earlier. That being said that is outside of the scope of this change, let's land this PR.

@fahadnayyar fahadnayyar merged commit cc8f060 into swiftlang:main Sep 30, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants