Skip to content

Commit a8fd0d0

Browse files
authored
[Clang][Sema] Diagnose class member access expressions naming non-existent members of the current instantiation prior to instantiation in the absence of dependent base classes (llvm#84050)
Consider the following: ```cpp template<typename T> struct A { auto f() { return this->x; } }; ``` Although `A` has no dependent base classes and the lookup context for `x` is the current instantiation, we currently do not diagnose the absence of a member `x` until `A<T>::f` is instantiated. This patch moves the point of diagnosis for such expressions to occur at the point of definition (i.e. prior to instantiation).
1 parent 2f2e31c commit a8fd0d0

File tree

30 files changed

+765
-224
lines changed

30 files changed

+765
-224
lines changed

clang-tools-extra/clangd/unittests/FindTargetTests.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ TEST_F(TargetDeclTest, DependentExprs) {
854854
}
855855
};
856856
)cpp";
857-
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()");
857+
EXPECT_DECLS("MemberExpr", "void foo()");
858858

859859
// Similar to above but base expression involves a function call.
860860
Code = R"cpp(
@@ -872,7 +872,7 @@ TEST_F(TargetDeclTest, DependentExprs) {
872872
}
873873
};
874874
)cpp";
875-
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()");
875+
EXPECT_DECLS("MemberExpr", "void foo()");
876876

877877
// Similar to above but uses a function pointer.
878878
Code = R"cpp(
@@ -891,7 +891,7 @@ TEST_F(TargetDeclTest, DependentExprs) {
891891
}
892892
};
893893
)cpp";
894-
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()");
894+
EXPECT_DECLS("MemberExpr", "void foo()");
895895

896896
// Base expression involves a member access into this.
897897
Code = R"cpp(
@@ -962,7 +962,7 @@ TEST_F(TargetDeclTest, DependentExprs) {
962962
void Foo() { this->[[find]](); }
963963
};
964964
)cpp";
965-
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void find()");
965+
EXPECT_DECLS("MemberExpr", "void find()");
966966
}
967967

968968
TEST_F(TargetDeclTest, DependentTypes) {

clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ sizeof...($TemplateParameter[[Elements]]);
621621
struct $Class_def[[Foo]] {
622622
int $Field_decl[[Waldo]];
623623
void $Method_def[[bar]]() {
624-
$Class[[Foo]]().$Field_dependentName[[Waldo]];
624+
$Class[[Foo]]().$Field[[Waldo]];
625625
}
626626
template $Bracket[[<]]typename $TemplateParameter_def[[U]]$Bracket[[>]]
627627
void $Method_def[[bar1]]() {

clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/owning-memory.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ struct HeapArray { // Ok, since destruc
309309

310310
HeapArray(HeapArray &&other) : _data(other._data), size(other.size) { // Ok
311311
other._data = nullptr; // Ok
312+
// CHECK-NOTES: [[@LINE-1]]:5: warning: expected assignment source to be of type 'gsl::owner<>'; got 'std::nullptr_t'
313+
// FIXME: This warning is emitted because an ImplicitCastExpr for the NullToPointer conversion isn't created for dependent types.
312314
other.size = 0;
313315
}
314316

clang-tools-extra/test/clang-tidy/checkers/modernize/use-equals-default-copy.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ template <class T>
260260
struct Template {
261261
Template() = default;
262262
Template(const Template &Other) : Field(Other.Field) {}
263+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use '= default'
264+
// CHECK-FIXES: Template(const Template &Other) = default;
263265
Template &operator=(const Template &Other);
264266
void foo(const T &t);
265267
int Field;
@@ -269,8 +271,12 @@ Template<T> &Template<T>::operator=(const Template<T> &Other) {
269271
Field = Other.Field;
270272
return *this;
271273
}
274+
// CHECK-MESSAGES: :[[@LINE-4]]:27: warning: use '= default'
275+
// CHECK-FIXES: Template<T> &Template<T>::operator=(const Template<T> &Other) = default;
276+
272277
Template<int> T1;
273278

279+
274280
// Dependent types.
275281
template <class T>
276282
struct DT1 {
@@ -284,6 +290,9 @@ DT1<T> &DT1<T>::operator=(const DT1<T> &Other) {
284290
Field = Other.Field;
285291
return *this;
286292
}
293+
// CHECK-MESSAGES: :[[@LINE-4]]:17: warning: use '= default'
294+
// CHECK-FIXES: DT1<T> &DT1<T>::operator=(const DT1<T> &Other) = default;
295+
287296
DT1<int> Dt1;
288297

289298
template <class T>
@@ -303,6 +312,9 @@ DT2<T> &DT2<T>::operator=(const DT2<T> &Other) {
303312
struct T {
304313
typedef int TT;
305314
};
315+
// CHECK-MESSAGES: :[[@LINE-8]]:17: warning: use '= default'
316+
// CHECK-FIXES: DT2<T> &DT2<T>::operator=(const DT2<T> &Other) = default;
317+
306318
DT2<T> Dt2;
307319

308320
// Default arguments.

clang/docs/ReleaseNotes.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,18 @@ Improvements to Clang's diagnostics
385385

386386
- Clang now diagnoses requires expressions with explicit object parameters.
387387

388+
- Clang now looks up members of the current instantiation in the template definition context
389+
if the current instantiation has no dependent base classes.
390+
391+
.. code-block:: c++
392+
393+
template<typename T>
394+
struct A {
395+
int f() {
396+
return this->x; // error: no member named 'x' in 'A<T>'
397+
}
398+
};
399+
388400
Improvements to Clang's time-trace
389401
----------------------------------
390402

clang/include/clang/Sema/Lookup.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,9 @@ class LookupResult {
499499
/// Note that while no result was found in the current instantiation,
500500
/// there were dependent base classes that could not be searched.
501501
void setNotFoundInCurrentInstantiation() {
502-
assert(ResultKind == NotFound && Decls.empty());
502+
assert((ResultKind == NotFound ||
503+
ResultKind == NotFoundInCurrentInstantiation) &&
504+
Decls.empty());
503505
ResultKind = NotFoundInCurrentInstantiation;
504506
}
505507

clang/include/clang/Sema/Sema.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7472,7 +7472,7 @@ class Sema final : public SemaBase {
74727472
bool LookupQualifiedName(LookupResult &R, DeclContext *LookupCtx,
74737473
CXXScopeSpec &SS);
74747474
bool LookupParsedName(LookupResult &R, Scope *S, CXXScopeSpec *SS,
7475-
bool AllowBuiltinCreation = false,
7475+
QualType ObjectType, bool AllowBuiltinCreation = false,
74767476
bool EnteringContext = false);
74777477
ObjCProtocolDecl *LookupProtocol(
74787478
IdentifierInfo *II, SourceLocation IdLoc,
@@ -8881,11 +8881,13 @@ class Sema final : public SemaBase {
88818881
/// functions (but no function templates).
88828882
FoundFunctions,
88838883
};
8884-
bool LookupTemplateName(
8885-
LookupResult &R, Scope *S, CXXScopeSpec &SS, QualType ObjectType,
8886-
bool EnteringContext, bool &MemberOfUnknownSpecialization,
8887-
RequiredTemplateKind RequiredTemplate = SourceLocation(),
8888-
AssumedTemplateKind *ATK = nullptr, bool AllowTypoCorrection = true);
8884+
8885+
bool
8886+
LookupTemplateName(LookupResult &R, Scope *S, CXXScopeSpec &SS,
8887+
QualType ObjectType, bool EnteringContext,
8888+
RequiredTemplateKind RequiredTemplate = SourceLocation(),
8889+
AssumedTemplateKind *ATK = nullptr,
8890+
bool AllowTypoCorrection = true);
88898891

88908892
TemplateNameKind isTemplateName(Scope *S, CXXScopeSpec &SS,
88918893
bool hasTemplateKeyword,

clang/lib/AST/Expr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const Expr *Expr::skipRValueSubobjectAdjustments(
103103
}
104104
} else if (const auto *ME = dyn_cast<MemberExpr>(E)) {
105105
if (!ME->isArrow()) {
106-
assert(ME->getBase()->getType()->isRecordType());
106+
assert(ME->getBase()->getType()->getAsRecordDecl());
107107
if (const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
108108
if (!Field->isBitField() && !Field->getType()->isReferenceType()) {
109109
E = ME->getBase();

clang/lib/Parse/ParseDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2998,7 +2998,7 @@ bool Parser::ParseImplicitInt(DeclSpec &DS, CXXScopeSpec *SS,
29982998
<< TokenName << TagName << getLangOpts().CPlusPlus
29992999
<< FixItHint::CreateInsertion(Tok.getLocation(), FixitTagName);
30003000

3001-
if (Actions.LookupParsedName(R, getCurScope(), SS)) {
3001+
if (Actions.LookupName(R, getCurScope())) {
30023002
for (LookupResult::iterator I = R.begin(), IEnd = R.end();
30033003
I != IEnd; ++I)
30043004
Diag((*I)->getLocation(), diag::note_decl_hiding_tag_type)

clang/lib/Sema/HLSLExternalSemaSource.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,15 @@ struct BuiltinTypeDeclBuilder {
126126

127127
static DeclRefExpr *lookupBuiltinFunction(ASTContext &AST, Sema &S,
128128
StringRef Name) {
129-
CXXScopeSpec SS;
130129
IdentifierInfo &II = AST.Idents.get(Name, tok::TokenKind::identifier);
131130
DeclarationNameInfo NameInfo =
132131
DeclarationNameInfo(DeclarationName(&II), SourceLocation());
133132
LookupResult R(S, NameInfo, Sema::LookupOrdinaryName);
134-
S.LookupParsedName(R, S.getCurScope(), &SS, false);
133+
// AllowBuiltinCreation is false but LookupDirect will create
134+
// the builtin when searching the global scope anyways...
135+
S.LookupName(R, S.getCurScope());
136+
// FIXME: If the builtin function was user-declared in global scope,
137+
// this assert *will* fail. Should this call LookupBuiltin instead?
135138
assert(R.isSingleResult() &&
136139
"Since this is a builtin it should always resolve!");
137140
auto *VD = cast<ValueDecl>(R.getFoundDecl());

clang/lib/Sema/SemaAttr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,7 @@ void Sema::ActOnPragmaUnused(const Token &IdTok, Scope *curScope,
837837

838838
IdentifierInfo *Name = IdTok.getIdentifierInfo();
839839
LookupResult Lookup(*this, Name, IdTok.getLocation(), LookupOrdinaryName);
840-
LookupParsedName(Lookup, curScope, nullptr, true);
840+
LookupName(Lookup, curScope, /*AllowBuiltinCreation=*/true);
841841

842842
if (Lookup.empty()) {
843843
Diag(PragmaLoc, diag::warn_pragma_unused_undeclared_var)

clang/lib/Sema/SemaDecl.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,7 @@ static bool isTagTypeWithMissingTag(Sema &SemaRef, LookupResult &Result,
832832
IdentifierInfo *&Name,
833833
SourceLocation NameLoc) {
834834
LookupResult R(SemaRef, Name, NameLoc, Sema::LookupTagName);
835-
SemaRef.LookupParsedName(R, S, &SS);
835+
SemaRef.LookupParsedName(R, S, &SS, /*ObjectType=*/QualType());
836836
if (TagDecl *Tag = R.getAsSingle<TagDecl>()) {
837837
StringRef FixItTagName;
838838
switch (Tag->getTagKind()) {
@@ -869,7 +869,7 @@ static bool isTagTypeWithMissingTag(Sema &SemaRef, LookupResult &Result,
869869

870870
// Replace lookup results with just the tag decl.
871871
Result.clear(Sema::LookupTagName);
872-
SemaRef.LookupParsedName(Result, S, &SS);
872+
SemaRef.LookupParsedName(Result, S, &SS, /*ObjectType=*/QualType());
873873
return true;
874874
}
875875

@@ -896,7 +896,8 @@ Sema::NameClassification Sema::ClassifyName(Scope *S, CXXScopeSpec &SS,
896896
}
897897

898898
LookupResult Result(*this, Name, NameLoc, LookupOrdinaryName);
899-
LookupParsedName(Result, S, &SS, !CurMethod);
899+
LookupParsedName(Result, S, &SS, /*ObjectType=*/QualType(),
900+
/*AllowBuiltinCreation=*/!CurMethod);
900901

901902
if (SS.isInvalid())
902903
return NameClassification::Error();

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4517,7 +4517,7 @@ Sema::BuildMemInitializer(Decl *ConstructorD,
45174517
DS.getBeginLoc(), DS.getEllipsisLoc());
45184518
} else {
45194519
LookupResult R(*this, MemberOrBase, IdLoc, LookupOrdinaryName);
4520-
LookupParsedName(R, S, &SS);
4520+
LookupParsedName(R, S, &SS, /*ObjectType=*/QualType());
45214521

45224522
TypeDecl *TyD = R.getAsSingle<TypeDecl>();
45234523
if (!TyD) {
@@ -12262,7 +12262,7 @@ Decl *Sema::ActOnUsingDirective(Scope *S, SourceLocation UsingLoc,
1226212262

1226312263
// Lookup namespace name.
1226412264
LookupResult R(*this, NamespcName, IdentLoc, LookupNamespaceName);
12265-
LookupParsedName(R, S, &SS);
12265+
LookupParsedName(R, S, &SS, /*ObjectType=*/QualType());
1226612266
if (R.isAmbiguous())
1226712267
return nullptr;
1226812268

@@ -13721,7 +13721,7 @@ Decl *Sema::ActOnNamespaceAliasDef(Scope *S, SourceLocation NamespaceLoc,
1372113721

1372213722
// Lookup the namespace name.
1372313723
LookupResult R(*this, Ident, IdentLoc, LookupNamespaceName);
13724-
LookupParsedName(R, S, &SS);
13724+
LookupParsedName(R, S, &SS, /*ObjectType=*/QualType());
1372513725

1372613726
if (R.isAmbiguous())
1372713727
return nullptr;

clang/lib/Sema/SemaExpr.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -673,8 +673,9 @@ ExprResult Sema::DefaultLvalueConversion(Expr *E) {
673673
// expressions of certain types in C++.
674674
if (getLangOpts().CPlusPlus &&
675675
(E->getType() == Context.OverloadTy ||
676-
T->isDependentType() ||
677-
T->isRecordType()))
676+
// FIXME: This is a hack! We want the lvalue-to-rvalue conversion applied
677+
// to pointer types even if the pointee type is dependent.
678+
(T->isDependentType() && !T->isPointerType()) || T->isRecordType()))
678679
return E;
679680

680681
// The C standard is actually really unclear on this point, and
@@ -2751,8 +2752,8 @@ Sema::ActOnIdExpression(Scope *S, CXXScopeSpec &SS,
27512752
if (isBoundsAttrContext() && !getLangOpts().CPlusPlus && S->isClassScope()) {
27522753
// See if this is reference to a field of struct.
27532754
LookupResult R(*this, NameInfo, LookupMemberName);
2754-
// LookupParsedName handles a name lookup from within anonymous struct.
2755-
if (LookupParsedName(R, S, &SS)) {
2755+
// LookupName handles a name lookup from within anonymous struct.
2756+
if (LookupName(R, S)) {
27562757
if (auto *VD = dyn_cast<ValueDecl>(R.getFoundDecl())) {
27572758
QualType type = VD->getType().getNonReferenceType();
27582759
// This will eventually be translated into MemberExpr upon
@@ -2773,20 +2774,19 @@ Sema::ActOnIdExpression(Scope *S, CXXScopeSpec &SS,
27732774
// lookup to determine that it was a template name in the first place. If
27742775
// this becomes a performance hit, we can work harder to preserve those
27752776
// results until we get here but it's likely not worth it.
2776-
bool MemberOfUnknownSpecialization;
27772777
AssumedTemplateKind AssumedTemplate;
2778-
if (LookupTemplateName(R, S, SS, QualType(), /*EnteringContext=*/false,
2779-
MemberOfUnknownSpecialization, TemplateKWLoc,
2778+
if (LookupTemplateName(R, S, SS, /*ObjectType=*/QualType(),
2779+
/*EnteringContext=*/false, TemplateKWLoc,
27802780
&AssumedTemplate))
27812781
return ExprError();
27822782

2783-
if (MemberOfUnknownSpecialization ||
2784-
(R.getResultKind() == LookupResult::NotFoundInCurrentInstantiation))
2783+
if (R.wasNotFoundInCurrentInstantiation())
27852784
return ActOnDependentIdExpression(SS, TemplateKWLoc, NameInfo,
27862785
IsAddressOfOperand, TemplateArgs);
27872786
} else {
27882787
bool IvarLookupFollowUp = II && !SS.isSet() && getCurMethodDecl();
2789-
LookupParsedName(R, S, &SS, !IvarLookupFollowUp);
2788+
LookupParsedName(R, S, &SS, /*ObjectType=*/QualType(),
2789+
/*AllowBuiltinCreation=*/!IvarLookupFollowUp);
27902790

27912791
// If the result might be in a dependent base class, this is a dependent
27922792
// id-expression.

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9157,7 +9157,7 @@ Sema::CheckMicrosoftIfExistsSymbol(Scope *S,
91579157
// Do the redeclaration lookup in the current scope.
91589158
LookupResult R(*this, TargetNameInfo, Sema::LookupAnyName,
91599159
RedeclarationKind::NotForRedeclaration);
9160-
LookupParsedName(R, S, &SS);
9160+
LookupParsedName(R, S, &SS, /*ObjectType=*/QualType());
91619161
R.suppressDiagnostics();
91629162

91639163
switch (R.getResultKind()) {

0 commit comments

Comments
 (0)