]> granicus.if.org Git - clang/commitdiff
PR13657 (and duplicates):
authorRichard Smith <richard-llvm@metafoo.co.uk>
Thu, 12 Sep 2013 23:28:08 +0000 (23:28 +0000)
committerRichard Smith <richard-llvm@metafoo.co.uk>
Thu, 12 Sep 2013 23:28:08 +0000 (23:28 +0000)
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

include/clang/Parse/Parser.h
include/clang/Sema/Sema.h
lib/Parse/ParseCXXInlineMethods.cpp
lib/Parse/ParseDecl.cpp
lib/Parse/ParseTentative.cpp
lib/Sema/Sema.cpp
lib/Sema/SemaLookup.cpp
test/Parser/cxx-ambig-init-templ.cpp [new file with mode: 0644]
test/Parser/cxx-default-args.cpp
test/Parser/cxx0x-member-initializers.cpp

index f52ac9c649af6f6aeb17de7dbcfabbdab713fca7..985554636d2a2b250992cf5f440ff6585aa6a964 100644 (file)
@@ -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,
index ee1bafb86fdf93b76655faaa3ce91c1d66e0b119..385bc04f345fbace555aaf2db38cbd410b116ccf 100644 (file)
@@ -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;
 
index ed087e342ae6bb5a9c807284972be51db88a3700..725d69f77b8b1679a5b2561195b7cbf93be2360e 100644 (file)
@@ -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 <s we've seen so far. These might be templates,
+  // and might not, but if there were none of them (or we know for sure that
+  // we're within a template), we can avoid a tentative parse.
+  unsigned AngleCount = 0;
+  unsigned KnownTemplateCount = 0;
+
+  while (1) {
+    switch (Tok.getKind()) {
+    case tok::comma:
+      // If we might be in a template, perform a tentative parse to check.
+      if (!AngleCount)
+        // Not a template argument: this is the end of the initializer.
+        return true;
+      if (KnownTemplateCount)
+        goto consume_token;
+
+      // We hit a comma inside angle brackets. This is the hard case. The
+      // rule we follow is:
+      //  * For a default argument, if the tokens after the comma form a
+      //    syntactically-valid parameter-declaration-clause, in which each
+      //    parameter has an initializer, then this comma ends the default
+      //    argument.
+      //  * For a default initializer, if the tokens after the comma form a
+      //    syntactically-valid init-declarator-list, then this comma ends
+      //    the default initializer.
+      {
+        UnannotatedTentativeParsingAction PA(*this,
+                                             CIK == CIK_DefaultInitializer
+                                               ? tok::semi : tok::r_paren);
+        Sema::TentativeAnalysisScope Scope(Actions);
+
+        TPResult Result = TPResult::Error();
+        ConsumeToken();
+        switch (CIK) {
+        case CIK_DefaultInitializer:
+          Result = TryParseInitDeclaratorList();
+          // If we parsed a complete, ambiguous init-declarator-list, this
+          // is only syntactically-valid if it's followed by a semicolon.
+          if (Result == TPResult::Ambiguous() && Tok.isNot(tok::semi))
+            Result = TPResult::False();
+          break;
+
+        case CIK_DefaultArgument:
+          bool InvalidAsDeclaration = false;
+          Result = TryParseParameterDeclarationClause(
+              &InvalidAsDeclaration, /*VersusTemplateArgument*/true);
+          // If this is an expression or a declaration with a missing
+          // 'typename', assume it's not a declaration.
+          if (Result == TPResult::Ambiguous() && InvalidAsDeclaration)
+            Result = TPResult::False();
+          break;
+        }
+
+        // If what follows could be a declaration, it is a declaration.
+        if (Result != TPResult::False() && Result != TPResult::Error()) {
+          PA.Revert();
+          return true;
+        }
+
+        // In the uncommon case that we decide the following tokens are part
+        // of a template argument, revert any annotations we've performed in
+        // those tokens. We're not going to look them up until we've parsed
+        // the rest of the class, and that might add more declarations.
+        PA.RevertAnnotations();
+      }
+
+      // Keep going. We know we're inside a template argument list now.
+      ++KnownTemplateCount;
+      goto consume_token;
+
+    case tok::eof:
+      // Ran out of tokens.
+      return false;
+
+    case tok::less:
+      // FIXME: A '<' can only start a template-id if it's preceded by an
+      // identifier, an operator-function-id, or a literal-operator-id.
+      ++AngleCount;
+      goto consume_token;
+
+    case tok::question:
+      // In 'a ? b : c', 'b' can contain an unparenthesized comma. If it does,
+      // that is *never* the end of the initializer. Skip to the ':'.
+      if (!ConsumeAndStoreConditional(Toks))
+        return false;
+      break;
+
+    case tok::greatergreatergreater:
+      if (!getLangOpts().CPlusPlus11)
+        goto consume_token;
+      if (AngleCount) --AngleCount;
+      if (KnownTemplateCount) --KnownTemplateCount;
+      // Fall through.
+    case tok::greatergreater:
+      if (!getLangOpts().CPlusPlus11)
+        goto consume_token;
+      if (AngleCount) --AngleCount;
+      if (KnownTemplateCount) --KnownTemplateCount;
+      // Fall through.
+    case tok::greater:
+      if (AngleCount) --AngleCount;
+      if (KnownTemplateCount) --KnownTemplateCount;
+      goto consume_token;
+
+    case tok::kw_template:
+      // 'template' identifier '<' is known to start a template argument list,
+      // and can be used to disambiguate the parse.
+      // FIXME: Support all forms of 'template' unqualified-id '<'.
+      Toks.push_back(Tok);
+      ConsumeToken();
+      if (Tok.is(tok::identifier)) {
+        Toks.push_back(Tok);
+        ConsumeToken();
+        if (Tok.is(tok::less)) {
+          ++KnownTemplateCount;
+          Toks.push_back(Tok);
+          ConsumeToken();
+        }
+      }
+      break;
+
+    case tok::kw_operator:
+      // If 'operator' precedes other punctuation, that punctuation loses
+      // its special behavior.
+      Toks.push_back(Tok);
+      ConsumeToken();
+      switch (Tok.getKind()) {
+      case tok::comma:
+      case tok::greatergreatergreater:
+      case tok::greatergreater:
+      case tok::greater:
+      case tok::less:
+        Toks.push_back(Tok);
+        ConsumeToken();
+        break;
+      default:
+        break;
+      }
+      break;
+
+    case tok::l_paren:
+      // Recursively consume properly-nested parens.
+      Toks.push_back(Tok);
+      ConsumeParen();
+      ConsumeAndStoreUntil(tok::r_paren, Toks, /*StopAtSemi=*/false);
+      break;
+    case tok::l_square:
+      // Recursively consume properly-nested square brackets.
+      Toks.push_back(Tok);
+      ConsumeBracket();
+      ConsumeAndStoreUntil(tok::r_square, Toks, /*StopAtSemi=*/false);
+      break;
+    case tok::l_brace:
+      // Recursively consume properly-nested braces.
+      Toks.push_back(Tok);
+      ConsumeBrace();
+      ConsumeAndStoreUntil(tok::r_brace, Toks, /*StopAtSemi=*/false);
+      break;
+
+    // Okay, we found a ']' or '}' or ')', which we think should be balanced.
+    // Since the user wasn't looking for this token (if they were, it would
+    // already be handled), this isn't balanced.  If there is a LHS token at a
+    // higher level, we will assume that this matches the unbalanced token
+    // and return it.  Otherwise, this is a spurious RHS token, which we skip.
+    case tok::r_paren:
+      if (CIK == CIK_DefaultArgument)
+        return true; // End of the default argument.
+      if (ParenCount && !IsFirstTokenConsumed)
+        return false;  // Matches something.
+      goto consume_token;
+    case tok::r_square:
+      if (BracketCount && !IsFirstTokenConsumed)
+        return false;  // Matches something.
+      goto consume_token;
+    case tok::r_brace:
+      if (BraceCount && !IsFirstTokenConsumed)
+        return false;  // Matches something.
+      goto consume_token;
+
+    case tok::code_completion:
+      Toks.push_back(Tok);
+      ConsumeCodeCompletionToken();
+      break;
+
+    case tok::string_literal:
+    case tok::wide_string_literal:
+    case tok::utf8_string_literal:
+    case tok::utf16_string_literal:
+    case tok::utf32_string_literal:
+      Toks.push_back(Tok);
+      ConsumeStringToken();
+      break;
+    case tok::semi:
+      if (CIK == CIK_DefaultInitializer)
+        return true; // End of the default initializer.
+      // FALL THROUGH.
+    default:
+    consume_token:
+      Toks.push_back(Tok);
+      ConsumeToken();
+      break;
+    }
+    IsFirstTokenConsumed = false;
+  }
+}
index 6d7ab979b08adfe5a3c08f303cc43417d7a12ba2..74982a3894e89032faf9432491810d24a355c380 100644 (file)
@@ -5333,9 +5333,7 @@ void Parser::ParseParameterDeclarationClause(
           // FIXME: Can we use a smart pointer for Toks?
           DefArgToks = new CachedTokens;
 
-          if (!ConsumeAndStoreUntil(tok::comma, tok::r_paren, *DefArgToks,
-                                    /*StopAtSemi=*/true,
-                                    /*ConsumeFinalToken=*/false)) {
+          if (!ConsumeAndStoreInitializer(*DefArgToks, CIK_DefaultArgument)) {
             delete DefArgToks;
             DefArgToks = 0;
             Actions.ActOnParamDefaultArgumentError(Param);
index 37e1412be7a5d41d0a3ee1196916848e38cf77cf..7d1475c3a6b6b2c987ce8e6b166b65ac0e8fc287 100644 (file)
@@ -142,6 +142,82 @@ bool Parser::isCXXSimpleDeclaration(bool AllowForRangeDecl) {
   return TPR == TPResult::True();
 }
 
+/// Try to consume a token sequence that we've already identified as
+/// (potentially) starting a decl-specifier.
+Parser::TPResult Parser::TryConsumeDeclarationSpecifier() {
+  switch (Tok.getKind()) {
+  case tok::kw__Atomic:
+    if (NextToken().isNot(tok::l_paren)) {
+      ConsumeToken();
+      break;
+    }
+    // Fall through.
+  case tok::kw_typeof:
+  case tok::kw___attribute:
+  case tok::kw___underlying_type: {
+    ConsumeToken();
+    if (Tok.isNot(tok::l_paren))
+      return TPResult::Error();
+    ConsumeParen();
+    if (!SkipUntil(tok::r_paren, false))
+      return TPResult::Error();
+    break;
+  }
+
+  case tok::kw_class:
+  case tok::kw_struct:
+  case tok::kw_union:
+  case tok::kw___interface:
+  case tok::kw_enum:
+    // elaborated-type-specifier:
+    //     class-key attribute-specifier-seq[opt]
+    //         nested-name-specifier[opt] identifier
+    //     class-key nested-name-specifier[opt] template[opt] simple-template-id
+    //     enum nested-name-specifier[opt] identifier
+    //
+    // FIXME: We don't support class-specifiers nor enum-specifiers here.
+    ConsumeToken();
+
+    // Skip attributes.
+    while (Tok.is(tok::l_square) || Tok.is(tok::kw___attribute) ||
+           Tok.is(tok::kw___declspec) || Tok.is(tok::kw_alignas)) {
+      if (Tok.is(tok::l_square)) {
+        ConsumeBracket();
+        if (!SkipUntil(tok::r_square, false))
+          return TPResult::Error();
+      } else {
+        ConsumeToken();
+        if (Tok.isNot(tok::l_paren))
+          return TPResult::Error();
+        ConsumeParen();
+        if (!SkipUntil(tok::r_paren, false))
+          return TPResult::Error();
+      }
+    }
+
+    if (TryAnnotateCXXScopeToken())
+      return TPResult::Error();
+    if (Tok.is(tok::annot_cxxscope))
+      ConsumeToken();
+    if (Tok.isNot(tok::identifier) && Tok.isNot(tok::annot_template_id))
+      return TPResult::Error();
+    ConsumeToken();
+    break;
+
+  case tok::annot_cxxscope:
+    ConsumeToken();
+    // Fall through.
+  default:
+    ConsumeToken();
+
+    if (getLangOpts().ObjC1 && Tok.is(tok::less))
+      return TryParseProtocolQualifiers();
+    break;
+  }
+
+  return TPResult::Ambiguous();
+}
+
 /// simple-declaration:
 ///   decl-specifier-seq init-declarator-list[opt] ';'
 ///
@@ -151,16 +227,8 @@ bool Parser::isCXXSimpleDeclaration(bool AllowForRangeDecl) {
 ///    attribute-specifier-seqopt type-specifier-seq declarator
 ///
 Parser::TPResult Parser::TryParseSimpleDeclaration(bool AllowForRangeDecl) {
-  if (Tok.is(tok::kw_typeof))
-    TryParseTypeofSpecifier();
-  else {
-    if (Tok.is(tok::annot_cxxscope))
-      ConsumeToken();
-    ConsumeToken();
-
-    if (getLangOpts().ObjC1 && Tok.is(tok::less))
-      TryParseProtocolQualifiers();
-  }
+  if (TryConsumeDeclarationSpecifier() == TPResult::Error())
+    return TPResult::Error();
 
   // Two decl-specifiers in a row conclusively disambiguate this as being a
   // simple-declaration. Don't bother calling isCXXDeclarationSpecifier in the
@@ -233,7 +301,7 @@ Parser::TPResult Parser::TryParseInitDeclaratorList() {
       // expression can never be followed directly by a braced-init-list.
       return TPResult::True();
     } else if (Tok.is(tok::equal) || isTokIdentifier_in()) {
-      // MSVC and g++ won't examine the rest of declarators if '=' is 
+      // MSVC and g++ won't examine the rest of declarators if '=' is
       // encountered; they just conclude that we have a declaration.
       // EDG parses the initializer completely, which is the proper behavior
       // for this case.
@@ -241,12 +309,14 @@ Parser::TPResult Parser::TryParseInitDeclaratorList() {
       // At present, Clang follows MSVC and g++, since the parser does not have
       // the ability to parse an expression fully without recording the
       // results of that parse.
-      // Also allow 'in' after on objective-c declaration as in: 
-      // for (int (^b)(void) in array). Ideally this should be done in the 
+      // FIXME: Handle this case correctly.
+      //
+      // Also allow 'in' after an Objective-C declaration as in:
+      // for (int (^b)(void) in array). Ideally this should be done in the
       // context of parsing for-init-statement of a foreach statement only. But,
       // in any other context 'in' is invalid after a declaration and parser
       // issues the error regardless of outcome of this decision.
-      // FIXME. Change if above assumption does not hold.
+      // FIXME: Change if above assumption does not hold.
       return TPResult::True();
     }
 
@@ -286,14 +356,7 @@ bool Parser::isCXXConditionDeclaration() {
   TentativeParsingAction PA(*this);
 
   // type-specifier-seq
-  if (Tok.is(tok::kw_typeof))
-    TryParseTypeofSpecifier();
-  else {
-    ConsumeToken();
-    
-    if (getLangOpts().ObjC1 && Tok.is(tok::less))
-      TryParseProtocolQualifiers();
-  }
+  TryConsumeDeclarationSpecifier();
   assert(Tok.is(tok::l_paren) && "Expected '('");
 
   // declarator
@@ -363,15 +426,7 @@ bool Parser::isCXXTypeId(TentativeCXXTypeIdContext Context, bool &isAmbiguous) {
   TentativeParsingAction PA(*this);
 
   // type-specifier-seq
-  if (Tok.is(tok::kw_typeof))
-    TryParseTypeofSpecifier();
-  else {
-    ConsumeToken();
-    
-    if (getLangOpts().ObjC1 && Tok.is(tok::less))
-      TryParseProtocolQualifiers();
-  }
-  
+  TryConsumeDeclarationSpecifier();
   assert(Tok.is(tok::l_paren) && "Expected '('");
 
   // declarator
@@ -569,6 +624,121 @@ Parser::isCXX11AttributeSpecifier(bool Disambiguate,
   return CAK_NotAttributeSpecifier;
 }
 
+Parser::TPResult Parser::TryParsePtrOperatorSeq() {
+  while (true) {
+    if (Tok.is(tok::coloncolon) || Tok.is(tok::identifier))
+      if (TryAnnotateCXXScopeToken(true))
+        return TPResult::Error();
+
+    if (Tok.is(tok::star) || Tok.is(tok::amp) || Tok.is(tok::caret) ||
+        Tok.is(tok::ampamp) ||
+        (Tok.is(tok::annot_cxxscope) && NextToken().is(tok::star))) {
+      // ptr-operator
+      ConsumeToken();
+      while (Tok.is(tok::kw_const)    ||
+             Tok.is(tok::kw_volatile) ||
+             Tok.is(tok::kw_restrict))
+        ConsumeToken();
+    } else {
+      return TPResult::True();
+    }
+  }
+}
+
+///         operator-function-id:
+///           'operator' operator
+///
+///         operator: one of
+///           new  delete  new[]  delete[]  +  -  *  /  %  ^  [...]
+///
+///         conversion-function-id:
+///           'operator' conversion-type-id
+///
+///         conversion-type-id:
+///           type-specifier-seq conversion-declarator[opt]
+///
+///         conversion-declarator:
+///           ptr-operator conversion-declarator[opt]
+///
+///         literal-operator-id:
+///           'operator' string-literal identifier
+///           'operator' user-defined-string-literal
+Parser::TPResult Parser::TryParseOperatorId() {
+  assert(Tok.is(tok::kw_operator));
+  ConsumeToken();
+
+  // Maybe this is an operator-function-id.
+  switch (Tok.getKind()) {
+  case tok::kw_new: case tok::kw_delete:
+    ConsumeToken();
+    if (Tok.is(tok::l_square) && NextToken().is(tok::r_square)) {
+      ConsumeBracket();
+      ConsumeBracket();
+    }
+    return TPResult::True();
+
+#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemOnly) \
+  case tok::Token:
+#define OVERLOADED_OPERATOR_MULTI(Name, Spelling, Unary, Binary, MemOnly)
+#include "clang/Basic/OperatorKinds.def"
+    ConsumeToken();
+    return TPResult::True();
+
+  case tok::l_square:
+    if (NextToken().is(tok::r_square)) {
+      ConsumeBracket();
+      ConsumeBracket();
+      return TPResult::True();
+    }
+    break;
+
+  case tok::l_paren:
+    if (NextToken().is(tok::r_paren)) {
+      ConsumeParen();
+      ConsumeParen();
+      return TPResult::True();
+    }
+    break;
+
+  default:
+    break;
+  }
+
+  // Maybe this is a literal-operator-id.
+  if (getLangOpts().CPlusPlus11 && isTokenStringLiteral()) {
+    bool FoundUDSuffix = false;
+    do {
+      FoundUDSuffix |= Tok.hasUDSuffix();
+      ConsumeStringToken();
+    } while (isTokenStringLiteral());
+
+    if (!FoundUDSuffix) {
+      if (Tok.is(tok::identifier))
+        ConsumeToken();
+      else
+        return TPResult::Error();
+    }
+    return TPResult::True();
+  }
+
+  // Maybe this is a conversion-function-id.
+  bool AnyDeclSpecifiers = false;
+  while (true) {
+    TPResult TPR = isCXXDeclarationSpecifier();
+    if (TPR == TPResult::Error())
+      return TPR;
+    if (TPR == TPResult::False()) {
+      if (!AnyDeclSpecifiers)
+        return TPResult::Error();
+      break;
+    }
+    if (TryConsumeDeclarationSpecifier() == TPResult::Error())
+      return TPResult::Error();
+    AnyDeclSpecifiers = true;
+  }
+  return TryParsePtrOperatorSeq();
+}
+
 ///         declarator:
 ///           direct-declarator
 ///           ptr-operator declarator
@@ -615,9 +785,11 @@ Parser::isCXX11AttributeSpecifier(bool Disambiguate,
 ///
 ///         unqualified-id:
 ///           identifier
-///           operator-function-id                                        [TODO]
-///           conversion-function-id                                      [TODO]
+///           operator-function-id
+///           conversion-function-id
+///           literal-operator-id
 ///           '~' class-name                                              [TODO]
+///           '~' decltype-specifier                                      [TODO]
 ///           template-id                                                 [TODO]
 ///
 Parser::TPResult Parser::TryParseDeclarator(bool mayBeAbstract,
@@ -625,40 +797,28 @@ Parser::TPResult Parser::TryParseDeclarator(bool mayBeAbstract,
   // declarator:
   //   direct-declarator
   //   ptr-operator declarator
-
-  while (1) {
-    if (Tok.is(tok::coloncolon) || Tok.is(tok::identifier))
-      if (TryAnnotateCXXScopeToken(true))
-        return TPResult::Error();
-
-    if (Tok.is(tok::star) || Tok.is(tok::amp) || Tok.is(tok::caret) ||
-        Tok.is(tok::ampamp) ||
-        (Tok.is(tok::annot_cxxscope) && NextToken().is(tok::star))) {
-      // ptr-operator
-      ConsumeToken();
-      while (Tok.is(tok::kw_const)    ||
-             Tok.is(tok::kw_volatile) ||
-             Tok.is(tok::kw_restrict))
-        ConsumeToken();
-    } else {
-      break;
-    }
-  }
+  if (TryParsePtrOperatorSeq() == TPResult::Error())
+    return TPResult::Error();
 
   // direct-declarator:
   // direct-abstract-declarator:
   if (Tok.is(tok::ellipsis))
     ConsumeToken();
-  
-  if ((Tok.is(tok::identifier) ||
-       (Tok.is(tok::annot_cxxscope) && NextToken().is(tok::identifier))) &&
+
+  if ((Tok.is(tok::identifier) || Tok.is(tok::kw_operator) ||
+       (Tok.is(tok::annot_cxxscope) && (NextToken().is(tok::identifier) ||
+                                        NextToken().is(tok::kw_operator)))) &&
       mayHaveIdentifier) {
     // declarator-id
     if (Tok.is(tok::annot_cxxscope))
       ConsumeToken();
-    else
+    else if (Tok.is(tok::identifier))
       TentativelyDeclaredIdentifiers.push_back(Tok.getIdentifierInfo());
-    ConsumeToken();
+    if (Tok.is(tok::kw_operator)) {
+      if (TryParseOperatorId() == TPResult::Error())
+        return TPResult::Error();
+    } else
+      ConsumeToken();
   } else if (Tok.is(tok::l_paren)) {
     ConsumeParen();
     if (mayBeAbstract &&
@@ -836,14 +996,15 @@ Parser::isExpressionOrTypeSpecifierSimple(tok::TokenKind Kind) {
   case tok::kw_wchar_t:
   case tok::kw_char16_t:
   case tok::kw_char32_t:
-  case tok::kw___underlying_type:
   case tok::kw__Decimal32:
   case tok::kw__Decimal64:
   case tok::kw__Decimal128:
+  case tok::kw___interface:
   case tok::kw___thread:
   case tok::kw_thread_local:
   case tok::kw__Thread_local:
   case tok::kw_typeof:
+  case tok::kw___underlying_type:
   case tok::kw___cdecl:
   case tok::kw___stdcall:
   case tok::kw___fastcall:
@@ -1103,6 +1264,7 @@ Parser::isCXXDeclarationSpecifier(Parser::TPResult BracedCastResult,
   case tok::kw_class:
   case tok::kw_struct:
   case tok::kw_union:
+  case tok::kw___interface:
     // enum-specifier
   case tok::kw_enum:
     // cv-qualifier
@@ -1325,6 +1487,56 @@ Parser::isCXXDeclarationSpecifier(Parser::TPResult BracedCastResult,
   }
 }
 
+bool Parser::isCXXDeclarationSpecifierAType() {
+  switch (Tok.getKind()) {
+    // typename-specifier
+  case tok::annot_decltype:
+  case tok::annot_template_id:
+  case tok::annot_typename:
+  case tok::kw_typeof:
+  case tok::kw___underlying_type:
+    return true;
+
+    // elaborated-type-specifier
+  case tok::kw_class:
+  case tok::kw_struct:
+  case tok::kw_union:
+  case tok::kw___interface:
+  case tok::kw_enum:
+    return true;
+
+    // simple-type-specifier
+  case tok::kw_char:
+  case tok::kw_wchar_t:
+  case tok::kw_char16_t:
+  case tok::kw_char32_t:
+  case tok::kw_bool:
+  case tok::kw_short:
+  case tok::kw_int:
+  case tok::kw_long:
+  case tok::kw___int64:
+  case tok::kw___int128:
+  case tok::kw_signed:
+  case tok::kw_unsigned:
+  case tok::kw_half:
+  case tok::kw_float:
+  case tok::kw_double:
+  case tok::kw_void:
+  case tok::kw___unknown_anytype:
+    return true;
+
+  case tok::kw_auto:
+    return getLangOpts().CPlusPlus11;
+
+  case tok::kw__Atomic:
+    // "_Atomic foo"
+    return NextToken().is(tok::l_paren);
+
+  default:
+    return false;
+  }
+}
+
 /// [GNU] typeof-specifier:
 ///         'typeof' '(' expressions ')'
 ///         'typeof' '(' type-name ')'
@@ -1366,27 +1578,6 @@ Parser::TPResult Parser::TryParseProtocolQualifiers() {
   return TPResult::Error();
 }
 
-Parser::TPResult
-Parser::TryParseDeclarationSpecifier(bool *HasMissingTypename) {
-  TPResult TPR = isCXXDeclarationSpecifier(TPResult::False(),
-                                           HasMissingTypename);
-  if (TPR != TPResult::Ambiguous())
-    return TPR;
-
-  if (Tok.is(tok::kw_typeof))
-    TryParseTypeofSpecifier();
-  else {
-    if (Tok.is(tok::annot_cxxscope))
-      ConsumeToken();
-    ConsumeToken();
-    
-    if (getLangOpts().ObjC1 && Tok.is(tok::less))
-      TryParseProtocolQualifiers();
-  }
-
-  return TPResult::Ambiguous();
-}
-
 /// isCXXFunctionDeclarator - Disambiguates between a function declarator or
 /// a constructor-style initializer, when parsing declaration statements.
 /// Returns true for function declarator and false for constructor-style
@@ -1461,7 +1652,8 @@ bool Parser::isCXXFunctionDeclarator(bool *IsAmbiguous) {
 ///     attributes[opt] '=' assignment-expression
 ///
 Parser::TPResult
-Parser::TryParseParameterDeclarationClause(bool *InvalidAsDeclaration) {
+Parser::TryParseParameterDeclarationClause(bool *InvalidAsDeclaration,
+                                           bool VersusTemplateArgument) {
 
   if (Tok.is(tok::r_paren))
     return TPResult::Ambiguous();
@@ -1494,8 +1686,32 @@ Parser::TryParseParameterDeclarationClause(bool *InvalidAsDeclaration) {
     // decl-specifier-seq
     // A parameter-declaration's initializer must be preceded by an '=', so
     // decl-specifier-seq '{' is not a parameter in C++11.
-    TPResult TPR = TryParseDeclarationSpecifier(InvalidAsDeclaration);
-    if (TPR != TPResult::Ambiguous())
+    TPResult TPR = isCXXDeclarationSpecifier(TPResult::False(),
+                                             InvalidAsDeclaration);
+
+    if (VersusTemplateArgument && TPR == TPResult::True()) {
+      // Consume the decl-specifier-seq. We have to look past it, since a
+      // type-id might appear here in a template argument.
+      bool SeenType = false;
+      do {
+        SeenType |= isCXXDeclarationSpecifierAType();
+        if (TryConsumeDeclarationSpecifier() == TPResult::Error())
+          return TPResult::Error();
+
+        // If we see a parameter name, this can't be a template argument.
+        if (SeenType && Tok.is(tok::identifier))
+          return TPResult::True();
+
+        TPR = isCXXDeclarationSpecifier(TPResult::False(),
+                                        InvalidAsDeclaration);
+        if (TPR == TPResult::Error())
+          return TPR;
+      } while (TPR != TPResult::False());
+    } else if (TPR == TPResult::Ambiguous()) {
+      // Disambiguate what follows the decl-specifier.
+      if (TryConsumeDeclarationSpecifier() == TPResult::Error())
+        return TPResult::Error();
+    } else
       return TPR;
 
     // declarator
@@ -1508,9 +1724,24 @@ Parser::TryParseParameterDeclarationClause(bool *InvalidAsDeclaration) {
     if (Tok.is(tok::kw___attribute))
       return TPResult::True();
 
+    // If we're disambiguating a template argument in a default argument in
+    // a class definition versus a parameter declaration, an '=' here
+    // disambiguates the parse one way or the other.
+    // If this is a parameter, it must have a default argument because
+    //   (a) the previous parameter did, and
+    //   (b) this must be the first declaration of the function, so we can't
+    //       inherit any default arguments from elsewhere.
+    // If we see an ')', then we've reached the end of a
+    // parameter-declaration-clause, and the last param is missing its default
+    // argument.
+    if (VersusTemplateArgument)
+      return (Tok.is(tok::equal) || Tok.is(tok::r_paren)) ? TPResult::True()
+                                                          : TPResult::False();
+
     if (Tok.is(tok::equal)) {
       // '=' assignment-expression
       // Parse through assignment-expression.
+      // FIXME: assignment-expression may contain an unparenthesized comma.
       if (!SkipUntil(tok::comma, tok::r_paren, true/*StopAtSemi*/,
                      true/*DontConsume*/))
         return TPResult::Error();
index c6f213a7380ef798f4b2e0502eb8185845a5aca9..72fdc528fd4bee5586b15747ab556dc2058c1940 100644 (file)
@@ -89,8 +89,9 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer,
     NumSFINAEErrors(0), InFunctionDeclarator(0),
     AccessCheckingSFINAE(false), InNonInstantiationSFINAEContext(false),
     NonInstantiationEntries(0), ArgumentPackSubstitutionIndex(-1),
-    CurrentInstantiationScope(0), TyposCorrected(0),
-    AnalysisWarnings(*this), VarDataSharingAttributesStack(0), CurScope(0),
+    CurrentInstantiationScope(0), DisableTypoCorrection(false),
+    TyposCorrected(0), AnalysisWarnings(*this),
+    VarDataSharingAttributesStack(0), CurScope(0),
     Ident_super(0), Ident___float128(0)
 {
   TUScope = 0;
index 5b641d80598959b31c5413239bfe3027a41c813c..c26960670e29c7932a60f674b2a0c64aa1d6817f 100644 (file)
@@ -3955,7 +3955,8 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName,
       return Correction;
   }
 
-  if (Diags.hasFatalErrorOccurred() || !getLangOpts().SpellChecking)
+  if (Diags.hasFatalErrorOccurred() || !getLangOpts().SpellChecking ||
+      DisableTypoCorrection)
     return TypoCorrection();
 
   // In Microsoft mode, don't perform typo correction in a template member
diff --git a/test/Parser/cxx-ambig-init-templ.cpp b/test/Parser/cxx-ambig-init-templ.cpp
new file mode 100644 (file)
index 0000000..88ab61c
--- /dev/null
@@ -0,0 +1,171 @@
+// RUN: %clang_cc1 -std=c++11 -verify %s
+
+template<int> 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<b<c>::* >(), 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<int> 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<typename, int=0> 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<int, int=0> struct a { static const int val = 0; operator int(); }; // expected-note {{here}}
+  static const int b = 0, c = 1, d = 2, goobar = 3;
+  template<int, typename> struct e { operator int(); };
+
+  int mp1 = 0 < 1,
+      a<b<c,b<c>::*mp2,
+      mp3 = 0 > a<b<c>::val,
+      a<b<c,b<c>::*mp4 = 0,
+      a<b<c,b<c>::*mp5 {0},
+      a<b<c,b<c>::*mp6;
+
+  int np1 = e<0, int a<b<c,b<c>::*>();
+
+  static const int T1 = 4;
+  template<int, int &(S::*)(int)> struct T2 { static const int val = 0; };
+};
+
+namespace NoAnnotationTokens {
+  template<bool> struct Bool { Bool(int); };
+  static const bool in_class = false;
+
+  struct Test {
+    // Check we don't keep around a Bool<false> annotation token here.
+    int f(Bool<true> = X<Y, Bool<in_class> >(0));
+
+    // But it's OK if we do here.
+    int g(Bool<true> = Z<Y, Bool<in_class> = Bool<false>(0));
+
+    static const bool in_class = true;
+    template<int, typename U> using X = U;
+    static const int Y = 0, Z = 0;
+  };
+}
+
+namespace ImplicitInstantiation {
+  template<typename T> struct HasError { typename T::error error; }; // expected-error {{has no members}}
+
+  struct S {
+    // This triggers the instantiation of the outer HasError<int> during
+    // disambiguation, even though it uses the inner HasError<int>.
+    void f(int a = X<Y, HasError<int>::Z >()); // expected-note {{in instantiation of}}
+
+    template<typename, typename> struct X { operator int(); };
+    typedef int Y;
+    template<typename> struct HasError { typedef int Z; };
+  };
+
+  HasError<int> hei;
+}
+
+namespace CWG325 {
+  template <int A, typename B> 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 <int A, typename B> struct T {static int i;};
+  };
+
+  const int a = 0;
+  typedef int b;
+  T<a,b> c;
+  struct E {
+    int n = T<a,b>(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<int, Fn, Fn, Fn> struct T1 { enum { val }; };
+    template<int, int, int> struct T2 { enum { val }; };
+    template<int, Fn> struct T3 { enum { val }; };
+    template<int, typename T> struct T4 : T {};
+  };
+}
+
+namespace ElaboratedTypeSpecifiers {
+  struct S {
+    int f(int x = T<a, struct S>());
+    int g(int x = T<a, class __declspec() C>());
+    int h(int x = T<a, union __attribute__(()) U>());
+    int i(int x = T<a, enum E>());
+    int j(int x = T<a, struct S::template T<0, enum E>>());
+    template <int, typename> struct T { operator int(); };
+    static const int a = 0;
+    enum E {};
+  };
+}
index 7fe8474142bac66f7957e580e5b83b2e89f1a663..36abf0d8cb381fd186c4660974444eb4fc9780ca 100644 (file)
@@ -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 <typename A, typename B> struct T1 { enum {V};};
+  template <int A, int B> struct T2 { enum {V}; };
+  template <int, int> static int func(int);
+
+
+  void f1(T1<int, int> = T1<int, int>());
+  void f2(T1<int, double> = T1<int, double>(), T2<0, 5> = T2<0, 5>());
+  void f3(int a = T2<0, (T1<int, int>::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<int,int>::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<int, bool>::V < 3);
+  void f8(int = func<0,1<2>(0), int = 1<0, T1<int,int>(int) = 0);
+};
index a324f974bcafe56be0977da581d8c75198f40d69..43e99b133646815c5687747467bd4f56e708fa98 100644 (file)
@@ -27,3 +27,13 @@ struct V1 {
   int a, b;
   V1() : a(), b{} {}
 };
+
+template <typename, typename> struct T1 { enum {V};};
+template <int, int> struct T2 { enum {V};};
+struct A {
+  T1<int, int> a1 = T1<int, int>(), *a2 = new T1<int,int>;
+  T2<0,0> b1 = T2<0,0>(), b2 = T2<0,0>(), b3;
+  bool c1 = 1 < 2, c2 = 2 < 1, c3 = false;
+  bool d1 = T1<int, T1<int, int>>::V < 3, d2;
+  T1<int, int()> e = T1<int, int()>();
+};