Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -1529,12 +1529,13 @@ ParsedType &SuggestedType, bool AllowClassTemplates = false); - /// \brief For compatibility with MSVC, we delay parsing of some default - /// template type arguments until instantiation time. Emits a warning and - /// returns a synthesized DependentNameType that isn't really dependent on any - /// other template arguments. - ParsedType ActOnDelayedDefaultTemplateArg(const IdentifierInfo &II, - SourceLocation NameLoc); + /// Attempt to behave like MSVC in situations where lookup of an unqualified + /// type name has failed in a dependent context. In these situations, we + /// automatically form a DependentTypeName that will retry lookup in a related + /// scope during instantiation. + ParsedType ActOnMSVCUnknownTypeName(const IdentifierInfo &II, + SourceLocation NameLoc, + bool IsTemplateTypeArg); /// \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 @@ -2279,6 +2279,24 @@ return false; } + if (getLangOpts().CPlusPlus && (!SS || SS->isEmpty()) && + getLangOpts().MSVCCompat) { + // Lookup of an unqualified type name has failed in MSVC compatibility mode. + // Give Sema a chance to recover if we are in a template with dependent base + // classes. + if (ParsedType T = Actions.ActOnMSVCUnknownTypeName( + *Tok.getIdentifierInfo(), Tok.getLocation(), + DSC == DSC_template_type_arg)) { + const char *PrevSpec; + unsigned DiagID; + DS.SetTypeSpecType(DeclSpec::TST_typename, Loc, PrevSpec, DiagID, T, + Actions.getASTContext().getPrintingPolicy()); + DS.SetRangeEnd(Tok.getLocation()); + ConsumeToken(); + return false; + } + } + // Otherwise, if we don't consume this token, we are going to emit an // error anyway. Try to recover from various common problems. Check // to see if this was a reference to a tag name without a tag specified. @@ -2986,16 +3004,6 @@ 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 this is not a typedef name, don't parse it as part of the declspec, // it must be an implicit int or an error. if (!TypeRep) { Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -473,17 +473,53 @@ llvm_unreachable("something isn't in TU scope?"); } -ParsedType Sema::ActOnDelayedDefaultTemplateArg(const IdentifierInfo &II, - SourceLocation NameLoc) { - // 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; - - // Build a fake DependentNameType that will perform lookup into CurContext at - // instantiation time. The name specifier isn't dependent, so template - // instantiation won't transform it. It will retry the lookup, however. - NestedNameSpecifier *NNS = - synthesizeCurrentNestedNameSpecifier(Context, CurContext); +/// Find the parent class with dependent bases of the innermost enclosing method +/// context. Do not look for enclosing CXXRecordDecls directly, or we will end +/// up allowing unqualified dependent type names at class-level, which MSVC +/// correctly rejects. +static const CXXRecordDecl * +findRecordWithDependentBasesOfEnclosingMethod(const DeclContext *DC) { + for (; DC && DC->isDependentContext(); DC = DC->getLookupParent()) { + DC = DC->getPrimaryContext(); + if (const auto *MD = dyn_cast(DC)) + if (MD->getParent()->hasAnyDependentBases()) + return MD->getParent(); + } + return nullptr; +} + +ParsedType Sema::ActOnMSVCUnknownTypeName(const IdentifierInfo &II, + SourceLocation NameLoc, + bool IsTemplateTypeArg) { + assert(getLangOpts().MSVCCompat && "shouldn't be called in non-MSVC mode"); + + NestedNameSpecifier *NNS = nullptr; + if (IsTemplateTypeArg && getCurScope()->isTemplateParamScope()) { + // If we weren't able to parse a default template argument, delay lookup + // until instantiation time by making a non-dependent DependentTypeName. We + // pretend we saw a NestedNameSpecifier referring to the current scope, and + // lookup is retried. + // FIXME: This hurts our diagnostic quality, since we get errors like "no + // type named 'Foo' in 'current_namespace'" when the user didn't write any + // name specifiers. + NNS = synthesizeCurrentNestedNameSpecifier(Context, CurContext); + Diag(NameLoc, diag::ext_ms_delayed_template_argument) << &II; + } else if (const CXXRecordDecl *RD = + findRecordWithDependentBasesOfEnclosingMethod(CurContext)) { + // Build a DependentNameType that will perform lookup into RD at + // instantiation time. + NNS = NestedNameSpecifier::Create(Context, nullptr, RD->isTemplateDecl(), + RD->getTypeForDecl()); + + // 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; + } else { + // This is not a situation that we should recover from. + return ParsedType(); + } + QualType T = Context.getDependentNameType(ETK_None, NNS, &II); // Build type location information. We synthesized the qualifier, so we have 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,33 @@ 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 { + 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}} + _Atomic(TheType) a; // expected-warning {{unqualified lookup}} expected-error {{no type}} + } + void out_of_line(); +}; +template +void UseUnqualifiedTypeNames::out_of_line() { + void *p = new TheType; // 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}}