diff options
| -rw-r--r-- | clang/include/clang/AST/TemplateBase.h | 1 | ||||
| -rw-r--r-- | clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 | ||||
| -rw-r--r-- | clang/lib/AST/ASTContext.cpp | 7 | ||||
| -rw-r--r-- | clang/lib/AST/ASTImporter.cpp | 2 | ||||
| -rw-r--r-- | clang/lib/AST/DumpXML.cpp | 3 | ||||
| -rw-r--r-- | clang/lib/AST/ExprConstant.cpp | 6 | ||||
| -rw-r--r-- | clang/lib/AST/ItaniumMangle.cpp | 14 | ||||
| -rw-r--r-- | clang/lib/AST/TemplateBase.cpp | 35 | ||||
| -rw-r--r-- | clang/lib/AST/TypePrinter.cpp | 5 | ||||
| -rw-r--r-- | clang/lib/Sema/SemaExpr.cpp | 3 | ||||
| -rw-r--r-- | clang/lib/Sema/SemaTemplate.cpp | 108 | ||||
| -rw-r--r-- | clang/lib/Sema/SemaTemplateDeduction.cpp | 10 | ||||
| -rw-r--r-- | clang/lib/Sema/SemaTemplateInstantiate.cpp | 24 | ||||
| -rw-r--r-- | clang/test/CXX/temp/temp.arg/temp.arg.nontype/p1-11.cpp | 55 | ||||
| -rw-r--r-- | clang/test/CodeGenCXX/mangle-nullptr-arg.cpp | 13 | ||||
| -rw-r--r-- | clang/test/FixIt/fixit-cxx0x.cpp | 4 | 
16 files changed, 231 insertions, 61 deletions
diff --git a/clang/include/clang/AST/TemplateBase.h b/clang/include/clang/AST/TemplateBase.h index 06e4181e713..65f54600c00 100644 --- a/clang/include/clang/AST/TemplateBase.h +++ b/clang/include/clang/AST/TemplateBase.h @@ -101,7 +101,6 @@ public:    /// declaration, which is either an external declaration or a    /// template declaration.    TemplateArgument(Decl *D) : Kind(Declaration) { -    // FIXME: Need to be sure we have the "canonical" declaration!      TypeOrValue = reinterpret_cast<uintptr_t>(D);    } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6735bfcb457..ffe7be300a3 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2339,6 +2339,8 @@ def err_template_arg_not_integral_or_enumeral : Error<  def err_template_arg_not_ice : Error<    "non-type template argument of type %0 is not an integral constant "    "expression">; +def err_template_arg_untyped_null_constant : Error< +  "null non-type template argument must be cast to template parameter type %0">;  def err_deduced_non_type_template_arg_type_mismatch : Error<    "deduced non-type template argument does not have the same type as the "    "its corresponding template parameter (%0 vs %1)">; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 15f85ba9c48..acf5e0bbc9a 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -3313,8 +3313,11 @@ ASTContext::getCanonicalTemplateArgument(const TemplateArgument &Arg) const {      case TemplateArgument::Expression:        return Arg; -    case TemplateArgument::Declaration: -      return TemplateArgument(Arg.getAsDecl()->getCanonicalDecl()); +    case TemplateArgument::Declaration: { +      if (Decl *D = Arg.getAsDecl()) +          return TemplateArgument(D->getCanonicalDecl()); +      return TemplateArgument((Decl*)0); +    }      case TemplateArgument::Template:        return TemplateArgument(getCanonicalTemplateName(Arg.getAsTemplate())); diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 621658c7f0a..3879907ec6d 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -325,6 +325,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,      return IsSameValue(*Arg1.getAsIntegral(), *Arg2.getAsIntegral());    case TemplateArgument::Declaration: +    if (!Arg1.getAsDecl() || !Arg2.getAsDecl()) +      return !Arg1.getAsDecl() && !Arg2.getAsDecl();      return Context.IsStructurallyEquivalent(Arg1.getAsDecl(), Arg2.getAsDecl());    case TemplateArgument::Template: diff --git a/clang/lib/AST/DumpXML.cpp b/clang/lib/AST/DumpXML.cpp index b180e808ab4..4c7cd8a6793 100644 --- a/clang/lib/AST/DumpXML.cpp +++ b/clang/lib/AST/DumpXML.cpp @@ -320,7 +320,8 @@ struct XMLDumper : public XMLDeclVisitor<XMLDumper>,        break;      case TemplateArgument::Declaration: { -      visitDeclRef(A.getAsDecl()); +      if (Decl *D = A.getAsDecl()) +        visitDeclRef(D);        break;      }      case TemplateArgument::Integral: { diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 0d8490e137d..4ef169d1899 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1743,8 +1743,10 @@ static bool HandleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv,      // parameters are constant expressions even if they're non-const.      // In C, such things can also be folded, although they are not ICEs.      const VarDecl *VD = dyn_cast<VarDecl>(D); -    if (const VarDecl *VDef = VD->getDefinition(Info.Ctx)) -      VD = VDef; +    if (VD) { +      if (const VarDecl *VDef = VD->getDefinition(Info.Ctx)) +        VD = VDef; +    }      if (!VD || VD->isInvalidDecl()) {        Info.Diag(Conv);        return false; diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index 5457036920b..d7b63545401 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -3118,12 +3118,22 @@ void CXXNameMangler::mangleTemplateArg(const NamedDecl *P,    case TemplateArgument::Declaration: {      assert(P && "Missing template parameter for declaration argument");      //  <expr-primary> ::= L <mangled-name> E # external name - +    //  <expr-primary> ::= L <type> 0 E      // Clang produces AST's where pointer-to-member-function expressions      // and pointer-to-function expressions are represented as a declaration not      // an expression. We compensate for it here to produce the correct mangling. -    NamedDecl *D = cast<NamedDecl>(A.getAsDecl());      const NonTypeTemplateParmDecl *Parameter = cast<NonTypeTemplateParmDecl>(P); + +    // Handle NULL pointer arguments. +    if (!A.getAsDecl()) { +      Out << "L"; +      mangleType(Parameter->getType()); +      Out << "0E"; +      break; +    } +     + +    NamedDecl *D = cast<NamedDecl>(A.getAsDecl());      bool compensateMangling = !Parameter->getType()->isReferenceType();      if (compensateMangling) {        Out << 'X'; diff --git a/clang/lib/AST/TemplateBase.cpp b/clang/lib/AST/TemplateBase.cpp index 7e6bae2b267..531e03e302b 100644 --- a/clang/lib/AST/TemplateBase.cpp +++ b/clang/lib/AST/TemplateBase.cpp @@ -80,9 +80,13 @@ bool TemplateArgument::isDependent() const {      return true;    case Declaration: -    if (DeclContext *DC = dyn_cast<DeclContext>(getAsDecl())) -      return DC->isDependentContext(); -    return getAsDecl()->getDeclContext()->isDependentContext(); +    if (Decl *D = getAsDecl()) { +      if (DeclContext *DC = dyn_cast<DeclContext>(D)) +        return DC->isDependentContext(); +      return D->getDeclContext()->isDependentContext(); +    } +       +    return false;    case Integral:      // Never dependent @@ -118,10 +122,13 @@ bool TemplateArgument::isInstantiationDependent() const {      return true;    case Declaration: -    if (DeclContext *DC = dyn_cast<DeclContext>(getAsDecl())) -      return DC->isDependentContext(); -    return getAsDecl()->getDeclContext()->isDependentContext(); -     +    if (Decl *D = getAsDecl()) { +      if (DeclContext *DC = dyn_cast<DeclContext>(D)) +        return DC->isDependentContext(); +      return D->getDeclContext()->isDependentContext(); +    } +    return false; +          case Integral:      // Never dependent      return false; @@ -322,16 +329,14 @@ void TemplateArgument::print(const PrintingPolicy &Policy,    }    case Declaration: { -    bool Unnamed = true;      if (NamedDecl *ND = dyn_cast_or_null<NamedDecl>(getAsDecl())) {        if (ND->getDeclName()) { -        Unnamed = false;          Out << *ND; +      } else { +        Out << "<anonymous>";        } -    } -     -    if (Unnamed) { -      Out << "<anonymous>"; +    } else { +      Out << "nullptr";      }      break;    } @@ -488,7 +493,9 @@ const DiagnosticBuilder &clang::operator<<(const DiagnosticBuilder &DB,      return DB << Arg.getAsType();    case TemplateArgument::Declaration: -    return DB << Arg.getAsDecl(); +    if (Decl *D = Arg.getAsDecl()) +      return DB << D; +    return DB << "nullptr";    case TemplateArgument::Integral:      return DB << Arg.getAsIntegral()->toString(10); diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index 664a658160e..3bf80e79724 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -264,8 +264,9 @@ void TypePrinter::printRValueReference(const RValueReferenceType *T,  void TypePrinter::printMemberPointer(const MemberPointerType *T,                                        std::string &S) {  -  std::string C; -  print(QualType(T->getClass(), 0), C); +  PrintingPolicy InnerPolicy(Policy); +  Policy.SuppressTag = true; +  std::string C = QualType(T->getClass(), 0).getAsString(InnerPolicy);    C += "::*";    S = C + S; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 82d23783f91..fea0fe1e62b 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -10480,7 +10480,8 @@ namespace {  bool MarkReferencedDecls::TraverseTemplateArgument(    const TemplateArgument &Arg) {    if (Arg.getKind() == TemplateArgument::Declaration) { -    S.MarkAnyDeclReferenced(Loc, Arg.getAsDecl()); +    if (Decl *D = Arg.getAsDecl()) +      S.MarkAnyDeclReferenced(Loc, D);    }    return Inherited::TraverseTemplateArgument(Arg); diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 4f6c8793175..14558a695cd 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -3527,20 +3527,21 @@ CheckTemplateArgumentAddressOfObjectOrFunction(Sema &S,             dyn_cast<SubstNonTypeTemplateParmExpr>(Arg))      Arg = subst->getReplacement()->IgnoreImpCasts(); -  DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Arg); -  if (!DRE) { -    S.Diag(Arg->getLocStart(), diag::err_template_arg_not_decl_ref) -      << Arg->getSourceRange(); -    S.Diag(Param->getLocation(), diag::note_template_param_here); -    return true; -  } -    // Stop checking the precise nature of the argument if it is value dependent,    // it should be checked when instantiated.    if (Arg->isValueDependent()) {      Converted = TemplateArgument(ArgIn);      return false;    } +   +  DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Arg); +  if (!DRE) { +    S.Diag(Arg->getLocStart(), diag::err_template_arg_not_decl_ref) +    << Arg->getSourceRange(); +    S.Diag(Param->getLocation(), diag::note_template_param_here); +    return true; +  } +      if (!isa<ValueDecl>(DRE->getDecl())) {      S.Diag(Arg->getLocStart(), @@ -4048,21 +4049,74 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param,    QualType ArgType = Arg->getType();    DeclAccessPair FoundResult; // temporary for ResolveOverloadedFunction -  // C++0x [temp.arg.nontype]p5 bullets 2, 4 and 6 permit conversion -  // from a template argument of type std::nullptr_t to a non-type -  // template parameter of type pointer to object, pointer to -  // function, or pointer-to-member, respectively. -  if (ArgType->isNullPtrType()) { -    if (ParamType->isPointerType() || ParamType->isMemberPointerType()) { -      Converted = TemplateArgument((NamedDecl *)0); -      return Owned(Arg); +  // C++11 [temp.arg.nontype]p1: +  //   - a constant expression that evaluates to a null pointer value (4.10); or +  //   - a constant expression that evaluates to a null member pointer value +  //     (4.11); or +  //   - an address constant expression of type std::nullptr_t +  if (getLangOpts().CPlusPlus0x && +      (ParamType->isPointerType() || ParamType->isMemberPointerType() || +       ParamType->isNullPtrType()) && +      !Arg->isValueDependent() && !Arg->isTypeDependent()) { +    if (Expr::NullPointerConstantKind NPC +          = Arg->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent)){ +      if (NPC != Expr::NPCK_CXX0X_nullptr) { +        // C++11 [temp.arg.nontype]p5b2: +        //   if the template-argument is of type std::nullptr_t, the null +        //   pointer conversion (4.10) is applied. [ Note: In particular, +        //   neither the null pointer conversion for a zero-valued integral +        //   constant expression (4.10) nor the derived-to-base conversion +        //   (4.10) are applied. Although 0 is a valid template-argument for a +        //   non-type template-parameter of integral type, it is not a valid +        //   template-argument for a non-type template-parameter of pointer +        //   type. However, both (int*)0 and nullptr are valid +        //   template-arguments for a non-type template-parameter of type +        //   "pointer to int." — end note ] +        bool ObjCLifetimeConversion; +        if (!Context.hasSameUnqualifiedType(ArgType, ParamType) && +            !IsQualificationConversion(ArgType, ParamType, false, +                                       ObjCLifetimeConversion)) { +          { +            SemaDiagnosticBuilder DB +              = Diag(Arg->getExprLoc(), +                     diag::err_template_arg_untyped_null_constant); +            DB << ParamType; +                 +            if (ArgType->isIntegralType(Context)) { +              std::string Code = "(" + ParamType.getAsString() + ")"; +              DB << FixItHint::CreateInsertion(Arg->getLocStart(), Code); +            } +          } +          Diag(Param->getLocation(), diag::note_template_param_here); +        } +      } +       +      Converted = TemplateArgument((Decl *)0); +      return false;      } -     -    if (ParamType->isNullPtrType()) { -      llvm::APSInt Zero(Context.getTypeSize(Context.NullPtrTy), true); -      Converted = TemplateArgument(Zero, Context.NullPtrTy); -      return Owned(Arg); + +    // Check for a null (member) pointer value. +    Expr::EvalResult EvalResult; +    if (Arg->EvaluateAsRValue(EvalResult, Context) && +        ((EvalResult.Val.isLValue() && !EvalResult.Val.getLValueBase()) || +         (EvalResult.Val.isMemberPointer() && +          !EvalResult.Val.getMemberPointerDecl()))) { +      Converted = TemplateArgument((Decl *)0); +      return false; +    } +  } +   +  // If we haven't dealt with a null pointer-typed parameter yet, do so now. +  if (ParamType->isNullPtrType()) { +    if (Arg->isTypeDependent() || Arg->isValueDependent()) { +      Converted = TemplateArgument(Arg); +      return false;      } +     +    Diag(Arg->getExprLoc(), diag::err_template_arg_not_convertible) +      << Arg->getType() << ParamType; +    Diag(Param->getLocation(), diag::note_template_param_here); +    return true;    }    // Handle pointer-to-function, reference-to-function, and @@ -4255,6 +4309,18 @@ Sema::BuildExpressionFromDeclTemplateArgument(const TemplateArgument &Arg,                                                SourceLocation Loc) {    assert(Arg.getKind() == TemplateArgument::Declaration &&           "Only declaration template arguments permitted here"); +   +  // For a NULL non-type template argument, return nullptr casted to the +  // parameter's type. +  if (!Arg.getAsDecl()) { +    return ImpCastExprToType( +             new (Context) CXXNullPtrLiteralExpr(Context.NullPtrTy, Loc), +                             ParamType, +                             ParamType->getAs<MemberPointerType>() +                               ? CK_NullToMemberPointer +                               : CK_NullToPointer); +  } +      ValueDecl *VD = cast<ValueDecl>(Arg.getAsDecl());    if (VD->getDeclContext()->isRecord() && diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index b4486333812..2ea1e6ff934 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -1586,8 +1586,7 @@ DeduceTemplateArguments(Sema &S,    case TemplateArgument::Declaration:      if (Arg.getKind() == TemplateArgument::Declaration && -        Param.getAsDecl()->getCanonicalDecl() == -          Arg.getAsDecl()->getCanonicalDecl()) +        isSameDeclaration(Param.getAsDecl(), Arg.getAsDecl()))        return Sema::TDK_Success;      Info.FirstArg = Param; @@ -1858,8 +1857,7 @@ static bool isSameTemplateArg(ASTContext &Context,               Context.getCanonicalType(Y.getAsType());      case TemplateArgument::Declaration: -      return X.getAsDecl()->getCanonicalDecl() == -             Y.getAsDecl()->getCanonicalDecl(); +      return isSameDeclaration(X.getAsDecl(), Y.getAsDecl());      case TemplateArgument::Template:      case TemplateArgument::TemplateExpansion: @@ -1925,7 +1923,7 @@ getTrivialTemplateArgumentLoc(Sema &S,    case TemplateArgument::Declaration: {      Expr *E        = S.BuildExpressionFromDeclTemplateArgument(Arg, NTTPType, Loc) -    .takeAs<Expr>(); +          .takeAs<Expr>();      return TemplateArgumentLoc(TemplateArgument(E), E);    } @@ -4410,7 +4408,7 @@ MarkUsedTemplateParameters(ASTContext &Ctx,    switch (TemplateArg.getKind()) {    case TemplateArgument::Null:    case TemplateArgument::Integral: -    case TemplateArgument::Declaration: +  case TemplateArgument::Declaration:      break;    case TemplateArgument::Type: diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 307cccce8bc..4740145fd5a 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -1113,15 +1113,21 @@ ExprResult TemplateInstantiator::transformNonTypeTemplateParmRef(      type = argExpr->getType();    } else if (arg.getKind() == TemplateArgument::Declaration) { -    ValueDecl *VD = cast<ValueDecl>(arg.getAsDecl()); - -    // Find the instantiation of the template argument.  This is -    // required for nested templates. -    VD = cast_or_null<ValueDecl>( -                       getSema().FindInstantiatedDecl(loc, VD, TemplateArgs)); -    if (!VD) -      return ExprError(); - +    ValueDecl *VD; +    if (Decl *D = arg.getAsDecl()) { +      VD = cast<ValueDecl>(D); + +      // Find the instantiation of the template argument.  This is +      // required for nested templates. +      VD = cast_or_null<ValueDecl>( +             getSema().FindInstantiatedDecl(loc, VD, TemplateArgs)); +      if (!VD) +        return ExprError(); +    } else { +      // Propagate NULL template argument. +      VD = 0; +    } +          // Derive the type we want the substituted decl to have.  This had      // better be non-dependent, or these checks will have serious problems.      if (parm->isExpandedParameterPack()) { diff --git a/clang/test/CXX/temp/temp.arg/temp.arg.nontype/p1-11.cpp b/clang/test/CXX/temp/temp.arg/temp.arg.nontype/p1-11.cpp new file mode 100644 index 00000000000..d72f26ecdbc --- /dev/null +++ b/clang/test/CXX/temp/temp.arg/temp.arg.nontype/p1-11.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -std=c++11 %s -verify + +namespace std { +  typedef decltype(nullptr) nullptr_t; +} + +template<int *ip> struct IP {  // expected-note 2 {{template parameter is declared here}} +  IP<ip> *ip2; +}; + +constexpr std::nullptr_t get_nullptr() { return nullptr; } + +std::nullptr_t np; + +IP<0> ip0; // expected-error{{null non-type template argument must be cast to template parameter type 'int *'}} +IP<(0)> ip1; // expected-error{{null non-type template argument must be cast to template parameter type 'int *'}} +IP<nullptr> ip2; +IP<get_nullptr()> ip3; +IP<(int*)0> ip4; +IP<np> ip5; + +struct X { }; +template<int X::*pm> struct PM { // expected-note 2 {{template parameter is declared here}} +  PM<pm> *pm2; +}; + +PM<0> pm0; // expected-error{{null non-type template argument must be cast to template parameter type 'int X::*'}} +PM<(0)> pm1; // expected-error{{null non-type template argument must be cast to template parameter type 'int X::*'}} +PM<nullptr> pm2; +PM<get_nullptr()> pm3; +PM<(int X::*)0> pm4; +PM<np> pm5; + +template<int (X::*pmf)(int)> struct PMF { // expected-note 2 {{template parameter is declared here}} +  PMF<pmf> *pmf2; +}; + +PMF<0> pmf0; // expected-error{{null non-type template argument must be cast to template parameter type 'int (X::*)(int)'}} +PMF<(0)> pmf1; // expected-error{{null non-type template argument must be cast to template parameter type 'int (X::*)(int)'}} +PMF<nullptr> pmf2; +PMF<get_nullptr()> pmf3; +PMF<(int (X::*)(int))0> pmf4; +PMF<np> pmf5; + + +template<std::nullptr_t np> struct NP { // expected-note 2{{template parameter is declared here}} +  NP<np> *np2; +}; + +NP<nullptr> np1; +NP<np> np2; +NP<get_nullptr()> np3; +NP<0> np4; // expected-error{{null non-type template argument must be cast to template parameter type 'std::nullptr_t' (aka 'nullptr_t')}} +constexpr int i = 7; +NP<i> np5; // expected-error{{non-type template argument of type 'const int' cannot be converted to a value of type 'std::nullptr_t'}} diff --git a/clang/test/CodeGenCXX/mangle-nullptr-arg.cpp b/clang/test/CodeGenCXX/mangle-nullptr-arg.cpp new file mode 100644 index 00000000000..393de0b0ece --- /dev/null +++ b/clang/test/CodeGenCXX/mangle-nullptr-arg.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -std=c++11 -emit-llvm -o - %s | FileCheck %s + +template<int *ip> struct IP {}; + +// CHECK: define void @_Z5test12IPILPi0EE +void test1(IP<nullptr>) {} + +struct X{ }; +template<int X::*pm> struct PM {}; + +// CHECK: define void @_Z5test22PMILM1Xi0EE +void test2(PM<nullptr>) { } + diff --git a/clang/test/FixIt/fixit-cxx0x.cpp b/clang/test/FixIt/fixit-cxx0x.cpp index 997d73abfb5..b6cc2c08b03 100644 --- a/clang/test/FixIt/fixit-cxx0x.cpp +++ b/clang/test/FixIt/fixit-cxx0x.cpp @@ -104,3 +104,7 @@ namespace TestMisplacedEllipsisRecovery {  template<template<typename> ...Foo, // expected-error {{template template parameter requires 'class' after the parameter list}}           template<template<template<typename>>>> // expected-error 3 {{template template parameter requires 'class' after the parameter list}}  void func(); + +template<int *ip> struct IP { }; // expected-note{{declared here}} +IP<0> ip0; // expected-error{{null non-type template argument must be cast to template parameter type 'int *'}} +  | 

