Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -1535,6 +1535,8 @@ /// other template arguments. ParsedType ActOnDelayedDefaultTemplateArg(const IdentifierInfo &II, SourceLocation NameLoc); + ParsedType ActOnMSVCUnknownTypeName(const IdentifierInfo &II, + SourceLocation NameLoc); /// \brief Describes the result of the name lookup and resolution performed /// by \c ClassifyName(). Index: lib/Parse/ParseDecl.cpp =================================================================== --- lib/Parse/ParseDecl.cpp +++ lib/Parse/ParseDecl.cpp @@ -2986,14 +2986,21 @@ Actions.getTypeName(*Tok.getIdentifierInfo(), Tok.getLocation(), getCurScope()); - // MSVC: If we weren't able to parse a default template argument, and it's - // just a simple identifier, create a DependentNameType. This will allow - // us to defer the name lookup to template instantiation time, as long we - // forge a NestedNameSpecifier for the current context. - if (!TypeRep && DSContext == DSC_template_type_arg && - getLangOpts().MSVCCompat && getCurScope()->isTemplateParamScope()) { - TypeRep = Actions.ActOnDelayedDefaultTemplateArg( - *Tok.getIdentifierInfo(), Tok.getLocation()); + if (!TypeRep && getLangOpts().MSVCCompat) { + if (DSContext == DSC_type_specifier) { + // Lookup failed in a context where we know we need a type. Try to + // recover if this was type from a dependent base class. + TypeRep = Actions.ActOnMSVCUnknownTypeName(*Tok.getIdentifierInfo(), + Tok.getLocation()); + } else if (DSContext == DSC_template_type_arg && + getCurScope()->isTemplateParamScope()) { + // If we weren't able to parse a default template argument, and it's + // just a simple identifier, create a DependentNameType. This will + // allow us to defer the name lookup to template instantiation time, + // as long we forge a NestedNameSpecifier for the current context. + TypeRep = Actions.ActOnDelayedDefaultTemplateArg( + *Tok.getIdentifierInfo(), Tok.getLocation()); + } } // If this is not a typedef name, don't parse it as part of the declspec, Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -475,6 +475,8 @@ ParsedType Sema::ActOnDelayedDefaultTemplateArg(const IdentifierInfo &II, SourceLocation NameLoc) { + assert(getLangOpts().MSVCCompat && "shouldn't be called in non-MSVC mode"); + // Accepting an undeclared identifier as a default argument for a template // type parameter is a Microsoft extension. Diag(NameLoc, diag::ext_ms_delayed_template_argument) << &II; @@ -500,6 +502,50 @@ return CreateParsedType(T, Builder.getTypeSourceInfo(Context, T)); } +static const CXXRecordDecl * +findRecordParentWithDependentBases(const DeclContext *DC) { + for (;; DC = DC->getLookupParent()) { + DC = DC->getPrimaryContext(); + if (auto *RD = dyn_cast(DC)) + if (RD->hasAnyDependentBases()) + return RD; + } + return nullptr; +} + +ParsedType Sema::ActOnMSVCUnknownTypeName(const IdentifierInfo &II, + SourceLocation NameLoc) { + assert(getLangOpts().MSVCCompat && "shouldn't be called in non-MSVC mode"); + + const CXXRecordDecl *RD = findRecordParentWithDependentBases(CurContext); + if (!RD) + return ParsedType(); + + // Diagnose that this identifier was undeclared, and retry the lookup during + // template instantiation. + Diag(NameLoc, diag::ext_undeclared_unqual_id_with_dependent_base) << &II + << RD; + + // Build a DependentNameType that will perform lookup into RD at instantiation + // time. + NestedNameSpecifier *NNS = NestedNameSpecifier::Create( + Context, nullptr, RD->isTemplateDecl(), RD->getTypeForDecl()); + QualType T = Context.getDependentNameType(ETK_None, NNS, &II); + + // Build type location information. We synthesized the qualifier, so we have + // to build a fake NestedNameSpecifierLoc. + NestedNameSpecifierLocBuilder NNSLocBuilder; + NNSLocBuilder.MakeTrivial(Context, NNS, SourceRange(NameLoc)); + NestedNameSpecifierLoc QualifierLoc = NNSLocBuilder.getWithLocInContext(Context); + + TypeLocBuilder Builder; + DependentNameTypeLoc DepTL = Builder.push(T); + DepTL.setNameLoc(NameLoc); + DepTL.setElaboratedKeywordLoc(SourceLocation()); + DepTL.setQualifierLoc(QualifierLoc); + return CreateParsedType(T, Builder.getTypeSourceInfo(Context, T)); +} + /// isTagName() - This method is called *for error recovery purposes only* /// to determine if the specified name is a valid tag name ("struct foo"). If /// so, this returns the TST for the tag corresponding to it (TST_enum, Index: test/SemaTemplate/ms-delayed-default-template-args.cpp =================================================================== --- test/SemaTemplate/ms-delayed-default-template-args.cpp +++ test/SemaTemplate/ms-delayed-default-template-args.cpp @@ -55,6 +55,15 @@ typedef int Weber; } +// MSVC accepts this, but Clang doesn't. +namespace test_scope_spec { +template // expected-error {{use of undeclared identifier 'ns'}} +struct Foo { + static_assert(sizeof(T) == 4, "Bar should have gotten int"); +}; +namespace ns { typedef int Bar; } +} + #ifdef __clang__ // These are negative test cases that MSVC doesn't compile either. Try to use // unique undeclared identifiers so typo correction doesn't find types declared Index: test/SemaTemplate/ms-lookup-template-base-classes.cpp =================================================================== --- test/SemaTemplate/ms-lookup-template-base-classes.cpp +++ test/SemaTemplate/ms-lookup-template-base-classes.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++1y -fms-compatibility -fno-spell-checking -fsyntax-only -verify %s +// RUN: %clang_cc1 -fcxx-exceptions -fexceptions -std=c++1y -fms-compatibility -fno-spell-checking -fsyntax-only -verify %s template @@ -573,3 +573,28 @@ template decltype(h(T())) check2(); // expected-note{{candidate template ignored: substitution failure [with T = int]: no matching function for call to 'h'}} decltype(check2()) y; // expected-error{{no matching function for call to 'check2'}} } + +// We also allow unqualified lookup into bases in contexts where the we know the +// undeclared identifier *must* be a type, such as a new expression or catch +// parameter type. +template +struct UseUnqualifiedTypeNames : T { + _Atomic (TheType) f2; // expected-warning {{unqualified lookup}} expected-error {{no type}} + void foo() { + void *P = new TheType; // expected-warning {{unqualified lookup}} expected-error {{no type}} + size_t x = __builtin_offsetof(TheType, f2); // expected-warning {{unqualified lookup}} expected-error {{no type}} + try { + } catch (TheType) { // expected-warning {{unqualified lookup}} expected-error {{no type}} + } + enum E : IntegerType { E0 = 42 }; // expected-warning {{unqualified lookup}} expected-error {{no type}} + } +}; +struct Base { + typedef int IntegerType; + struct TheType { + int f1, f2; + }; +}; +template struct UseUnqualifiedTypeNames; +struct BadBase { }; +template struct UseUnqualifiedTypeNames; // expected-note-re 2 {{in instantiation {{.*}} requested here}}