aboutsummaryrefslogtreecommitdiff
path: root/clang/lib/Parse/ParseDecl.cpp
diff options
context:
space:
mode:
authorKrystian Stasiowski <sdkrystian@gmail.com>2024-07-29 14:01:00 -0400
committerGitHub <noreply@github.com>2024-07-29 14:01:00 -0400
commit708a9a06cba66bc8f739b05646e7d3be9247feee (patch)
treeb22b5d17e04e01c5d040c55ea7c95c8c93638c9f /clang/lib/Parse/ParseDecl.cpp
parentc66d25d1429fbf49c97ee9cd0195246642178cb7 (diff)
downloadllvm-708a9a06cba66bc8f739b05646e7d3be9247feee.zip
llvm-708a9a06cba66bc8f739b05646e7d3be9247feee.tar.gz
llvm-708a9a06cba66bc8f739b05646e7d3be9247feee.tar.bz2
[Clang][Parse] Fix ambiguity with nested-name-specifiers that may declarative (#96364)
Consider the following: ``` template<typename T> struct A { }; template<typename T> int A<T>::B::* f(); // error: no member named 'B' in 'A<T>' ``` Although this is clearly valid, clang rejects it because the _nested-name-specifier_ `A<T>::` is parsed as-if it was declarative, meaning, we parse it as-if it was the _nested-name-specifier_ in a redeclaration/specialization. However, we don't (and can't) know whether the _nested-name-specifier_ is declarative until we see the '`*`' token, but at that point we have already complained that `A` has no member named `B`! This patch addresses this bug by adding support for _fully_ unannotated _and_ unbounded tentative parsing, which allows for us to parse past tokens without having to cache them until we reach a point where we can guarantee to be past the construct we are disambiguating. I don't know where the approach taken here is ideal -- alternatives are welcome. However, the performance impact (as measured by llvm-compile-time-tracker (https://llvm-compile-time-tracker.com/?config=Overview&stat=instructions%3Au&remote=sdkrystian) is quite minimal (0.09%, which I plan to further improve).
Diffstat (limited to 'clang/lib/Parse/ParseDecl.cpp')
-rw-r--r--clang/lib/Parse/ParseDecl.cpp82
1 files changed, 50 insertions, 32 deletions
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 7ce9a9c..8d7d37c 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -6650,48 +6650,66 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
(Tok.is(tok::identifier) &&
(NextToken().is(tok::coloncolon) || NextToken().is(tok::less))) ||
Tok.is(tok::annot_cxxscope))) {
+ TentativeParsingAction TPA(*this, /*Unannotated=*/true);
bool EnteringContext = D.getContext() == DeclaratorContext::File ||
D.getContext() == DeclaratorContext::Member;
CXXScopeSpec SS;
SS.setTemplateParamLists(D.getTemplateParameterLists());
- ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
- /*ObjectHasErrors=*/false, EnteringContext);
- if (SS.isNotEmpty()) {
- if (Tok.isNot(tok::star)) {
- // The scope spec really belongs to the direct-declarator.
- if (D.mayHaveIdentifier())
- D.getCXXScopeSpec() = SS;
- else
- AnnotateScopeToken(SS, true);
+ if (ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
+ /*ObjectHasErrors=*/false,
+ /*EnteringContext=*/false,
+ /*MayBePseudoDestructor=*/nullptr,
+ /*IsTypename=*/false, /*LastII=*/nullptr,
+ /*OnlyNamespace=*/false,
+ /*InUsingDeclaration=*/false,
+ /*Disambiguation=*/EnteringContext) ||
+
+ SS.isEmpty() || SS.isInvalid() || !EnteringContext ||
+ Tok.is(tok::star)) {
+ TPA.Commit();
+ if (SS.isNotEmpty() && Tok.is(tok::star)) {
+ if (SS.isValid()) {
+ checkCompoundToken(SS.getEndLoc(), tok::coloncolon,
+ CompoundToken::MemberPtr);
+ }
- if (DirectDeclParser)
- (this->*DirectDeclParser)(D);
+ SourceLocation StarLoc = ConsumeToken();
+ D.SetRangeEnd(StarLoc);
+ DeclSpec DS(AttrFactory);
+ ParseTypeQualifierListOpt(DS);
+ D.ExtendWithDeclSpec(DS);
+
+ // Recurse to parse whatever is left.
+ Actions.runWithSufficientStackSpace(D.getBeginLoc(), [&] {
+ ParseDeclaratorInternal(D, DirectDeclParser);
+ });
+
+ // Sema will have to catch (syntactically invalid) pointers into global
+ // scope. It has to catch pointers into namespace scope anyway.
+ D.AddTypeInfo(DeclaratorChunk::getMemberPointer(
+ SS, DS.getTypeQualifiers(), StarLoc, DS.getEndLoc()),
+ std::move(DS.getAttributes()),
+ /*EndLoc=*/SourceLocation());
return;
}
+ } else {
+ TPA.Revert();
+ SS.clear();
+ ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
+ /*ObjectHasErrors=*/false,
+ /*EnteringContext=*/true);
+ }
- if (SS.isValid()) {
- checkCompoundToken(SS.getEndLoc(), tok::coloncolon,
- CompoundToken::MemberPtr);
- }
+ if (SS.isNotEmpty()) {
+ // The scope spec really belongs to the direct-declarator.
+ if (D.mayHaveIdentifier())
+ D.getCXXScopeSpec() = SS;
+ else
+ AnnotateScopeToken(SS, true);
- SourceLocation StarLoc = ConsumeToken();
- D.SetRangeEnd(StarLoc);
- DeclSpec DS(AttrFactory);
- ParseTypeQualifierListOpt(DS);
- D.ExtendWithDeclSpec(DS);
-
- // Recurse to parse whatever is left.
- Actions.runWithSufficientStackSpace(D.getBeginLoc(), [&] {
- ParseDeclaratorInternal(D, DirectDeclParser);
- });
-
- // Sema will have to catch (syntactically invalid) pointers into global
- // scope. It has to catch pointers into namespace scope anyway.
- D.AddTypeInfo(DeclaratorChunk::getMemberPointer(
- SS, DS.getTypeQualifiers(), StarLoc, DS.getEndLoc()),
- std::move(DS.getAttributes()),
- /* Don't replace range end. */ SourceLocation());
+ if (DirectDeclParser)
+ (this->*DirectDeclParser)(D);
return;
}
}