From: Richard Smith Date: Thu, 12 Sep 2013 23:28:08 +0000 (+0000) Subject: PR13657 (and duplicates): X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9bd3cdc3b78653275a36f15df81e1def3b2db8db;p=clang PR13657 (and duplicates): When a comma occurs in a default argument or default initializer within a class, disambiguate whether it is part of the initializer or whether it ends the initializer. The way this works (which I will be proposing for standardization) is to treat the comma as ending the default argument or default initializer if the following token sequence matches the syntactic constraints of a parameter-declaration-clause or init-declarator-list (respectively). This is both consistent with the disambiguation rules elsewhere (where entities are treated as declarations if they can be), and should have no regressions over our old behavior. I think it might also disambiguate all cases correctly, but I don't have a proof of that. There is an annoyance here: because we're performing a tentative parse in a situation where we may not have seen declarations of all relevant entities (if the comma is part of the initializer, lookup may find entites declared later in the class), we need to turn off typo-correction and diagnostics during the tentative parse, and in the rare case that we decide the comma is part of the initializer, we need to revert all token annotations we performed while disambiguating. Any diagnostics that occur outside of the immediate context of the tentative parse (for instance, if we trigger the implicit instantiation of a class template) are *not* suppressed, mirroring the usual rules for a SFINAE context. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@190639 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h index f52ac9c649..985554636d 100644 --- a/include/clang/Parse/Parser.h +++ b/include/clang/Parse/Parser.h @@ -608,6 +608,7 @@ private: assert(!isActive && "Forgot to call Commit or Revert!"); } }; + class UnannotatedTentativeParsingAction; /// ObjCDeclContextSwitch - An object used to switch context from /// an objective-c decl context to its enclosing decl context and @@ -1061,6 +1062,11 @@ private: void DeallocateParsedClasses(ParsingClass *Class); void PopParsingClass(Sema::ParsingClassState); + enum CachedInitKind { + CIK_DefaultArgument, + CIK_DefaultInitializer + }; + NamedDecl *ParseCXXInlineMethodDef(AccessSpecifier AS, AttributeList *AccessAttrs, ParsingDeclarator &D, @@ -1082,6 +1088,8 @@ private: void ParseLexedMemberInitializer(LateParsedMemberInitializer &MI); void ParseLexedObjCMethodDefs(LexedMethod &LM, bool parseMethod); bool ConsumeAndStoreFunctionPrologue(CachedTokens &Toks); + bool ConsumeAndStoreInitializer(CachedTokens &Toks, CachedInitKind CIK); + bool ConsumeAndStoreConditional(CachedTokens &Toks); bool ConsumeAndStoreUntil(tok::TokenKind T1, CachedTokens &Toks, bool StopAtSemi = true, @@ -1800,6 +1808,11 @@ private: isCXXDeclarationSpecifier(TPResult BracedCastResult = TPResult::False(), bool *HasMissingTypename = 0); + /// Given that isCXXDeclarationSpecifier returns \c TPResult::True or + /// \c TPResult::Ambiguous, determine whether the decl-specifier would be + /// a type-specifier other than a cv-qualifier. + bool isCXXDeclarationSpecifierAType(); + /// \brief Determine whether an identifier has been tentatively declared as a /// non-type. Such tentative declarations should not be found to name a type /// during a tentative parse, but also should not be annotated as a non-type. @@ -1812,15 +1825,18 @@ private: // that more tentative parsing is necessary for disambiguation. // They all consume tokens, so backtracking should be used after calling them. - TPResult TryParseDeclarationSpecifier(bool *HasMissingTypename = 0); TPResult TryParseSimpleDeclaration(bool AllowForRangeDecl); TPResult TryParseTypeofSpecifier(); TPResult TryParseProtocolQualifiers(); + TPResult TryParsePtrOperatorSeq(); + TPResult TryParseOperatorId(); TPResult TryParseInitDeclaratorList(); TPResult TryParseDeclarator(bool mayBeAbstract, bool mayHaveIdentifier=true); - TPResult TryParseParameterDeclarationClause(bool *InvalidAsDeclaration = 0); + TPResult TryParseParameterDeclarationClause(bool *InvalidAsDeclaration = 0, + bool VersusTemplateArg = false); TPResult TryParseFunctionDeclarator(); TPResult TryParseBracketDeclarator(); + TPResult TryConsumeDeclarationSpecifier(); public: TypeResult ParseTypeName(SourceRange *Range = 0, diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index ee1bafb86f..385bc04f34 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -6208,7 +6208,7 @@ public: /// \brief RAII class used to determine whether SFINAE has /// trapped any errors that occur during template argument - /// deduction.` + /// deduction. class SFINAETrap { Sema &SemaRef; unsigned PrevSFINAEErrors; @@ -6240,10 +6240,34 @@ public: } }; + /// \brief RAII class used to indicate that we are performing provisional + /// semantic analysis to determine the validity of a construct, so + /// typo-correction and diagnostics in the immediate context (not within + /// implicitly-instantiated templates) should be suppressed. + class TentativeAnalysisScope { + Sema &SemaRef; + // FIXME: Using a SFINAETrap for this is a hack. + SFINAETrap Trap; + bool PrevDisableTypoCorrection; + public: + explicit TentativeAnalysisScope(Sema &SemaRef) + : SemaRef(SemaRef), Trap(SemaRef, true), + PrevDisableTypoCorrection(SemaRef.DisableTypoCorrection) { + SemaRef.DisableTypoCorrection = true; + } + ~TentativeAnalysisScope() { + SemaRef.DisableTypoCorrection = PrevDisableTypoCorrection; + } + }; + /// \brief The current instantiation scope used to store local /// variables. LocalInstantiationScope *CurrentInstantiationScope; + /// \brief Tracks whether we are in a context where typo correction is + /// disabled. + bool DisableTypoCorrection; + /// \brief The number of typos corrected by CorrectTypo. unsigned TyposCorrected; diff --git a/lib/Parse/ParseCXXInlineMethods.cpp b/lib/Parse/ParseCXXInlineMethods.cpp index ed087e342a..725d69f77b 100644 --- a/lib/Parse/ParseCXXInlineMethods.cpp +++ b/lib/Parse/ParseCXXInlineMethods.cpp @@ -215,8 +215,7 @@ void Parser::ParseCXXNonStaticMemberInitializer(Decl *VarD) { ConsumeAndStoreUntil(tok::r_brace, Toks, /*StopAtSemi=*/true); } else { // Consume everything up to (but excluding) the comma or semicolon. - ConsumeAndStoreUntil(tok::comma, Toks, /*StopAtSemi=*/true, - /*ConsumeFinalToken=*/false); + ConsumeAndStoreInitializer(Toks, CIK_DefaultInitializer); } // Store an artificial EOF token to ensure that we don't run off the end of @@ -345,8 +344,15 @@ void Parser::ParseLexedMethodDeclaration(LateParsedMethodDeclaration &LM) { else { if (Tok.is(tok::cxx_defaultarg_end)) ConsumeToken(); - else - Diag(Tok.getLocation(), diag::err_default_arg_unparsed); + else { + // The last two tokens are the terminator and the saved value of + // Tok; the last token in the default argument is the one before + // those. + assert(Toks->size() >= 3 && "expected a token in default arg"); + Diag(Tok.getLocation(), diag::err_default_arg_unparsed) + << SourceRange(Tok.getLocation(), + (*Toks)[Toks->size() - 3].getLocation()); + } Actions.ActOnParamDefaultArgument(LM.DefaultArgs[I].Param, EqualLoc, DefArgResult.take()); } @@ -814,3 +820,276 @@ bool Parser::ConsumeAndStoreFunctionPrologue(CachedTokens &Toks) { } } } + +/// \brief Consume and store tokens from the '?' to the ':' in a conditional +/// expression. +bool Parser::ConsumeAndStoreConditional(CachedTokens &Toks) { + // Consume '?'. + assert(Tok.is(tok::question)); + Toks.push_back(Tok); + ConsumeToken(); + + while (Tok.isNot(tok::colon)) { + if (!ConsumeAndStoreUntil(tok::question, tok::colon, Toks, /*StopAtSemi*/true, + /*ConsumeFinalToken*/false)) + return false; + + // If we found a nested conditional, consume it. + if (Tok.is(tok::question) && !ConsumeAndStoreConditional(Toks)) + return false; + } + + // Consume ':'. + Toks.push_back(Tok); + ConsumeToken(); + return true; +} + +/// \brief A tentative parsing action that can also revert token annotations. +class Parser::UnannotatedTentativeParsingAction : public TentativeParsingAction { +public: + explicit UnannotatedTentativeParsingAction(Parser &Self, + tok::TokenKind EndKind) + : TentativeParsingAction(Self), Self(Self), EndKind(EndKind) { + // Stash away the old token stream, so we can restore it once the + // tentative parse is complete. + TentativeParsingAction Inner(Self); + Self.ConsumeAndStoreUntil(EndKind, Toks, true, /*ConsumeFinalToken*/false); + Inner.Revert(); + } + + void RevertAnnotations() { + Revert(); + + // Put back the original tokens. + Self.SkipUntil(EndKind, true, /*DontConsume*/true); + if (Toks.size()) { + Token *Buffer = new Token[Toks.size()]; + std::copy(Toks.begin() + 1, Toks.end(), Buffer); + Buffer[Toks.size() - 1] = Self.Tok; + Self.PP.EnterTokenStream(Buffer, Toks.size(), true, /*Owned*/true); + + Self.Tok = Toks.front(); + } + } + +private: + Parser &Self; + CachedTokens Toks; + tok::TokenKind EndKind; +}; + +/// ConsumeAndStoreInitializer - Consume and store the token at the passed token +/// container until the end of the current initializer expression (either a +/// default argument or an in-class initializer for a non-static data member). +/// The final token is not consumed. +bool Parser::ConsumeAndStoreInitializer(CachedTokens &Toks, + CachedInitKind CIK) { + // We always want this function to consume at least one token if not at EOF. + bool IsFirstTokenConsumed = true; + + // Number of possible unclosed struct c { c(int) = delete; typedef void val; operator int() const; }; + +int val; +int foobar; +struct S { + int k1 = a < b < c, d > ::val, e1; + int k2 = a < b, c < d > ::val, e2; + int k3 = b < a < c, d > ::val, e3; + int k4 = b < c, x, y = d > ::val, e4; + int k5 = T1 < b, &S::operator=(int); // expected-error {{extra qualification}} + int k6 = T2 < b, &S::operator= >::val; + int k7 = T1 < b, &S::operator>(int); // expected-error {{extra qualification}} + int k8 = T2 < b, &S::operator> >::val; + int k9 = T3 < a < b, c >> (d), e5 = 1 > (e4); + int k10 = 0 < T3 < a < b, c >> (d + ) // expected-error {{expected ';' at end of declaration}} + , a > (e4); + int k11 = 0 < 1, c<3>::*ptr; + int k12 = e < 0, int a::* >(), e11; + + void f1( + int k1 = a < b < c, d > ::val, + int k2 = b < a < c, d > ::val, + int k3 = b < c, int x = 0 > ::val, + int k4 = a < b, T3 < int > >(), // expected-error {{must be an expression}} + int k5 = a < b, c < d > ::val, + int k6 = a < b, c < d > (n) // expected-error {{undeclared identifier 'n'}} + ); + + void f2a( + // T3 here is a parameter type, so must be declared before it is used. + int k1 = c < b, T3 < int > x = 0 // expected-error {{unexpected end of default argument expression}} + ); + + template struct T3 { T3(int); operator int(); }; + + void f2b( + int k1 = c < b, T3 < int > x = 0 // ok + ); + + // This is a one-parameter function. Ensure we don't typo-correct it to + // int = a < b, c < foobar > () + // ... which would be a function with two parameters. + int f3(int = a < b, c < goobar > ()); + static constexpr int (S::*f3_test)(int) = &S::f3; + + void f4( + int k1 = a<1,2>::val, + int missing_default // expected-error {{missing default argument on parameter}} + ); + + void f5( + int k1 = b < c, + int missing_default // expected-error {{missing default argument on parameter}} + ); + + void f6( + int k = b < c, + unsigned int (missing_default) // expected-error {{missing default argument on parameter}} + ); + + template struct a { static const int val = 0; operator int(); }; // expected-note {{here}} + static const int b = 0, c = 1, d = 2, goobar = 3; + template struct e { operator int(); }; + + int mp1 = 0 < 1, + a::*mp2, + mp3 = 0 > a::val, + a::*mp4 = 0, + a::*mp5 {0}, + a::*mp6; + + int np1 = e<0, int a::*>(); + + static const int T1 = 4; + template struct T2 { static const int val = 0; }; +}; + +namespace NoAnnotationTokens { + template struct Bool { Bool(int); }; + static const bool in_class = false; + + struct Test { + // Check we don't keep around a Bool annotation token here. + int f(Bool = X >(0)); + + // But it's OK if we do here. + int g(Bool = Z = Bool(0)); + + static const bool in_class = true; + template using X = U; + static const int Y = 0, Z = 0; + }; +} + +namespace ImplicitInstantiation { + template struct HasError { typename T::error error; }; // expected-error {{has no members}} + + struct S { + // This triggers the instantiation of the outer HasError during + // disambiguation, even though it uses the inner HasError. + void f(int a = X::Z >()); // expected-note {{in instantiation of}} + + template struct X { operator int(); }; + typedef int Y; + template struct HasError { typedef int Z; }; + }; + + HasError hei; +} + +namespace CWG325 { + template struct T { static int i; operator int(); }; + class C { + int Foo (int i = T<1, int>::i); + }; + + class D { + int Foo (int i = T<1, int>::i); + template struct T {static int i;}; + }; + + const int a = 0; + typedef int b; + T c; + struct E { + int n = T(c); + }; +} + +namespace Operators { + struct Y {}; + constexpr int operator,(const Y&, const Y&) { return 8; } + constexpr int operator>(const Y&, const Y&) { return 8; } + constexpr int operator<(const Y&, const Y&) { return 8; } + constexpr int operator>>(const Y&, const Y&) { return 8; } + + struct X { + typedef int (*Fn)(const Y&, const Y&); + + Fn a = operator,, b = operator<, c = operator>; + void f(Fn a = operator,, Fn b = operator<, Fn c = operator>); + + int k1 = T1<0, operator<, operator>, operator<>::val, l1; + int k2 = T1<0, operator>, operator,, operator,>::val, l2; + int k3 = T2<0, operator,(Y{}, Y{}), operator<(Y{}, Y{})>::val, l3; + int k4 = T2<0, operator>(Y{}, Y{}), operator,(Y{}, Y{})>::val, l4; + int k5 = T3<0, operator>>>::val, l5; + int k6 = T4<0, T3<0, operator>>>>::val, l6; + + template struct T1 { enum { val }; }; + template struct T2 { enum { val }; }; + template struct T3 { enum { val }; }; + template struct T4 : T {}; + }; +} + +namespace ElaboratedTypeSpecifiers { + struct S { + int f(int x = T()); + int g(int x = T()); + int h(int x = T()); + int i(int x = T()); + int j(int x = T>()); + template struct T { operator int(); }; + static const int a = 0; + enum E {}; + }; +} diff --git a/test/Parser/cxx-default-args.cpp b/test/Parser/cxx-default-args.cpp index 7fe8474142..36abf0d8cb 100644 --- a/test/Parser/cxx-default-args.cpp +++ b/test/Parser/cxx-default-args.cpp @@ -14,3 +14,20 @@ typedef struct Inst { struct X { void f(int x = 1:); // expected-error {{unexpected end of default argument expression}} }; + +// PR13657 +struct T { + template struct T1 { enum {V};}; + template struct T2 { enum {V}; }; + template static int func(int); + + + void f1(T1 = T1()); + void f2(T1 = T1(), T2<0, 5> = T2<0, 5>()); + void f3(int a = T2<0, (T1::V > 10) ? 5 : 6>::V, bool b = 4<5 ); + void f4(bool a = 1 < 0, bool b = 2 > 0 ); + void f5(bool a = 1 > T2<0, 0>::V, bool b = T1::V < 3, int c = 0); + void f6(bool a = T2<0,3>::V < 4, bool b = 4 > T2<0,3>::V); + void f7(bool a = T1::V < 3); + void f8(int = func<0,1<2>(0), int = 1<0, T1(int) = 0); +}; diff --git a/test/Parser/cxx0x-member-initializers.cpp b/test/Parser/cxx0x-member-initializers.cpp index a324f974bc..43e99b1336 100644 --- a/test/Parser/cxx0x-member-initializers.cpp +++ b/test/Parser/cxx0x-member-initializers.cpp @@ -27,3 +27,13 @@ struct V1 { int a, b; V1() : a(), b{} {} }; + +template struct T1 { enum {V};}; +template struct T2 { enum {V};}; +struct A { + T1 a1 = T1(), *a2 = new T1; + T2<0,0> b1 = T2<0,0>(), b2 = T2<0,0>(), b3; + bool c1 = 1 < 2, c2 = 2 < 1, c3 = false; + bool d1 = T1>::V < 3, d2; + T1 e = T1(); +};