This fixes a crash when a class defined in a method of a templated class inherits from a class that is forward-declared.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
I'm not sure why this shouldn't be caught in Sema::CheckBaseSpecifier. But there is a check for the definition of the class before findCircularInheritance is called, so I'm guessing there is a reason the code shouldn't be rejected here.
We should reject a definition of a class that has a non-dependent base class that's an incomplete type. We need non-dependent bases to be complete in order to be able to do name lookup into them when processing the template definition. (The C++ standard permits us to reject such cases but does not require it, because "a hypothetical instantiation of [the templated class] immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter".)
Then there's a question of error recovery and AST invariants: should we permit a base specifier to name a non-dependent type that's not a complete class type? If so, we'll need to make sure that all code that iterates over base classes checks for this condition (I bet there are more cases than the two that you found). But tooling use cases probably do want to know that such base specifiers were written. So perhaps we should allow such base classes but mark the class definition as invalid if we find them -- in which case we would want something like this patch as part of the error recovery.
I think the non-dependent base class you mention in this case is B0, but it seems that the type of B0 in the AST is dependent as isDependentType returns true. Should we fix this? Or does isDependentType mean something different?
Just a potentially related thought: I was recently trying to understand under what circumstances should I expect CXXBaseSpecifier::getType() to return QualType instance for which isNull() could be true. I gave up and don't make any assumption.
Would the suggested change affect this?
Oh, sorry, we do already do the thing I was suggesting. But in your testcase the issue is that the base class B0 is dependent but is also resolved during template instantiation to a (dependent) class type. We should handle this by skipping dependent base classes, not by skipping incomplete base classes. For example, consider this testcase (which currently asserts):
struct A {}; template<typename T> struct X { struct B : A {}; struct C : A, B {}; };
here, we can resolve the B base of C to the member C of the current instantiation, but we should not traverse to B's base class list when considering the base classes of C, because we do not know that that definition of B will be used by any particular instantiation of C. For example:
template<> struct X<int>::B {}; X<int>::C c; // has only one `A` base class