diff --git a/include/clang/AST/Decl.h b/include/clang/AST/Decl.h index 04c4b46c9f9bf4c289b1e5272380518970921748..8229078b505ba058217edbc5902e51e3d6c41113 100644 --- a/include/clang/AST/Decl.h +++ b/include/clang/AST/Decl.h @@ -3184,6 +3184,10 @@ public: return isCompleteDefinition() || isFixed(); } + /// \brief Retrieve the enum definition from which this enumeration could + /// be instantiated, if it is an instantiation (rather than a non-template). + EnumDecl *getTemplateInstantiationPattern() const; + /// \brief Returns the enumeration (declared within the template) /// from which this enumeration type was instantiated, or NULL if /// this enumeration was not instantiated from any template. diff --git a/include/clang/AST/DeclTemplate.h b/include/clang/AST/DeclTemplate.h index 7e6dfbba27c76de625a26a82f53a37fd3c69c1ee..d4e9f65e91439cda6e4d3d8d5262d8a5923c5b4d 100644 --- a/include/clang/AST/DeclTemplate.h +++ b/include/clang/AST/DeclTemplate.h @@ -1876,6 +1876,10 @@ public: cast<ClassTemplatePartialSpecializationDecl>(getFirstDecl()); return First->InstantiatedFromMember.getPointer(); } + ClassTemplatePartialSpecializationDecl * + getInstantiatedFromMemberTemplate() const { + return getInstantiatedFromMember(); + } void setInstantiatedFromMember( ClassTemplatePartialSpecializationDecl *PartialSpec) { @@ -2511,17 +2515,11 @@ public: /// it was instantiated. llvm::PointerUnion<VarTemplateDecl *, VarTemplatePartialSpecializationDecl *> getInstantiatedFrom() const { - if (getSpecializationKind() != TSK_ImplicitInstantiation && - getSpecializationKind() != TSK_ExplicitInstantiationDefinition && - getSpecializationKind() != TSK_ExplicitInstantiationDeclaration) + if (!isTemplateInstantiation(getSpecializationKind())) return llvm::PointerUnion<VarTemplateDecl *, VarTemplatePartialSpecializationDecl *>(); - if (SpecializedPartialSpecialization *PartialSpec = - SpecializedTemplate.dyn_cast<SpecializedPartialSpecialization *>()) - return PartialSpec->PartialSpecialization; - - return SpecializedTemplate.get<VarTemplateDecl *>(); + return getSpecializedTemplateOrPartial(); } /// \brief Retrieve the variable template or variable template partial diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 28612e6c70623e967249720418639daa649579dc..413d5939abdf4ca4adf0f1bc9a9f1cce71f96574 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -3710,6 +3710,8 @@ def err_template_spec_unknown_kind : Error< "class template">; def note_specialized_entity : Note< "explicitly specialized declaration is here">; +def note_explicit_specialization_declared_here : Note< + "explicit specialization declared here">; def err_template_spec_decl_function_scope : Error< "explicit specialization of %0 in function scope">; def err_template_spec_decl_class_scope : Error< @@ -3832,6 +3834,8 @@ def err_partial_spec_ordering_ambiguous : Error< def note_partial_spec_match : Note<"partial specialization matches %0">; def err_partial_spec_redeclared : Error< "class template partial specialization %0 cannot be redeclared">; +def note_partial_specialization_declared_here : Note< + "explicit specialization declared here">; def note_prev_partial_spec_here : Note< "previous declaration of class template partial specialization %0 is here">; def err_partial_spec_fully_specialized : Error< @@ -8262,14 +8266,17 @@ def err_module_private_local_class : Error< "local %select{struct|interface|union|class|enum}0 cannot be declared " "__module_private__">; def err_module_unimported_use : Error< - "%select{declaration|definition|default argument}0 of %1 must be imported " + "%select{declaration|definition|default argument|" + "explicit specialization|partial specialization}0 of %1 must be imported " "from module '%2' before it is required">; def err_module_unimported_use_header : Error< "missing '#include %3'; " - "%select{declaration|definition|default argument}0 of %1 must be imported " + "%select{declaration|definition|default argument|" + "explicit specialization|partial specialization}0 of %1 must be imported " "from module '%2' before it is required">; def err_module_unimported_use_multiple : Error< - "%select{declaration|definition|default argument}0 of %1 must be imported " + "%select{declaration|definition|default argument|" + "explicit specialization|partial specialization}0 of %1 must be imported " "from one of the following modules before it is required:%2">; def ext_module_import_in_extern_c : ExtWarn< "import of C++ module '%0' appears within extern \"C\" language linkage " diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 7731569fbce65422498ef3468f0be64b9b91c168..5765b9b1546236678098df0c3bff515b7045e48e 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -1404,6 +1404,16 @@ public: bool isVisible(const NamedDecl *D) { return !D->isHidden() || isVisibleSlow(D); } + + /// Determine whether any declaration of an entity is visible. + bool + hasVisibleDeclaration(const NamedDecl *D, + llvm::SmallVectorImpl<Module *> *Modules = nullptr) { + return isVisible(D) || hasVisibleDeclarationSlow(D, Modules); + } + bool hasVisibleDeclarationSlow(const NamedDecl *D, + llvm::SmallVectorImpl<Module *> *Modules); + bool hasVisibleMergedDefinition(NamedDecl *Def); /// Determine if \p D has a visible definition. If not, suggest a declaration @@ -1420,6 +1430,11 @@ public: hasVisibleDefaultArgument(const NamedDecl *D, llvm::SmallVectorImpl<Module *> *Modules = nullptr); + /// Determine if there is a visible declaration of \p D that is a member + /// specialization declaration (as opposed to an instantiated declaration). + bool hasVisibleMemberSpecialization( + const NamedDecl *D, llvm::SmallVectorImpl<Module *> *Modules = nullptr); + /// Determine if \p A and \p B are equivalent internal linkage declarations /// from different modules, and thus an ambiguity error can be downgraded to /// an extension warning. @@ -1864,17 +1879,31 @@ public: enum class MissingImportKind { Declaration, Definition, - DefaultArgument + DefaultArgument, + ExplicitSpecialization, + PartialSpecialization }; /// \brief Diagnose that the specified declaration needs to be visible but /// isn't, and suggest a module import that would resolve the problem. void diagnoseMissingImport(SourceLocation Loc, NamedDecl *Decl, - bool NeedDefinition, bool Recover = true); + MissingImportKind MIK, bool Recover = true); void diagnoseMissingImport(SourceLocation Loc, NamedDecl *Decl, SourceLocation DeclLoc, ArrayRef<Module *> Modules, MissingImportKind MIK, bool Recover); + /// \brief We've found a use of a templated declaration that would trigger an + /// implicit instantiation. Check that any relevant explicit specializations + /// and partial specializations are visible, and diagnose if not. + /// \return Whether a problem was diagnosed. + void checkSpecializationVisibility(SourceLocation Loc, NamedDecl *Spec); + + /// \brief We've found a use of a template specialization that would select a + /// partial specialization. Check that the partial specialization is visible, + /// and diagnose if not. + void checkPartialSpecializationVisibility(SourceLocation Loc, + NamedDecl *Spec); + /// \brief Retrieve a suitable printing policy. PrintingPolicy getPrintingPolicy() const { return getPrintingPolicy(Context, PP); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 9246f94e84514b530fef170ffe697c680100cfa0..98bc36bbbdd452d67982b3c95083581c4dc4aede 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -3687,6 +3687,21 @@ void EnumDecl::setTemplateSpecializationKind(TemplateSpecializationKind TSK, MSI->setPointOfInstantiation(PointOfInstantiation); } +EnumDecl *EnumDecl::getTemplateInstantiationPattern() const { + if (MemberSpecializationInfo *MSInfo = getMemberSpecializationInfo()) { + if (isTemplateInstantiation(MSInfo->getTemplateSpecializationKind())) { + EnumDecl *ED = getInstantiatedFromMemberEnum(); + while (auto *NewED = ED->getInstantiatedFromMemberEnum()) + ED = NewED; + return ED; + } + } + + assert(!isTemplateInstantiation(getTemplateSpecializationKind()) && + "couldn't find pattern for enum instantiation"); + return nullptr; +} + EnumDecl *EnumDecl::getInstantiatedFromMemberEnum() const { if (SpecializationInfo) return cast<EnumDecl>(SpecializationInfo->getInstantiatedFrom()); diff --git a/lib/Sema/SemaCXXScopeSpec.cpp b/lib/Sema/SemaCXXScopeSpec.cpp index 2e774dd28ce5bde148f2f66f62a22d4e4d1dbf87..ab0e709a97e7044e5158d1d4e8a49c0242429c8f 100644 --- a/lib/Sema/SemaCXXScopeSpec.cpp +++ b/lib/Sema/SemaCXXScopeSpec.cpp @@ -117,8 +117,18 @@ DeclContext *Sema::computeDeclContext(const CXXScopeSpec &SS, // specializations, we're entering into the definition of that // class template partial specialization. if (ClassTemplatePartialSpecializationDecl *PartialSpec - = ClassTemplate->findPartialSpecialization(ContextType)) + = ClassTemplate->findPartialSpecialization(ContextType)) { + // A declaration of the partial specialization must be visible. + // We can always recover here, because this only happens when we're + // entering the context, and that can't happen in a SFINAE context. + assert(!isSFINAEContext() && + "partial specialization scope specifier in SFINAE context?"); + if (!hasVisibleDeclaration(PartialSpec)) + diagnoseMissingImport(SS.getLastQualifierNameLoc(), PartialSpec, + MissingImportKind::PartialSpecialization, + /*Recover*/true); return PartialSpec; + } } } else if (const RecordType *RecordT = NNSType->getAs<RecordType>()) { // The nested name specifier refers to a member of a class template. @@ -195,6 +205,8 @@ bool Sema::RequireCompleteDeclContext(CXXScopeSpec &SS, TagDecl *tag = dyn_cast<TagDecl>(DC); // If this is a dependent type, then we consider it complete. + // FIXME: This is wrong; we should require a (visible) definition to + // exist in this case too. if (!tag || tag->isDependentContext()) return false; @@ -218,10 +230,23 @@ bool Sema::RequireCompleteDeclContext(CXXScopeSpec &SS, // Fixed enum types are complete, but they aren't valid as scopes // until we see a definition, so awkwardly pull out this special // case. - // FIXME: The definition might not be visible; complain if it is not. const EnumType *enumType = dyn_cast_or_null<EnumType>(tagType); - if (!enumType || enumType->getDecl()->isCompleteDefinition()) + if (!enumType) return false; + if (enumType->getDecl()->isCompleteDefinition()) { + // If we know about the definition but it is not visible, complain. + NamedDecl *SuggestedDef = nullptr; + if (!hasVisibleDefinition(enumType->getDecl(), &SuggestedDef, + /*OnlyNeedComplete*/false)) { + // If the user is going to see an error here, recover by making the + // definition visible. + bool TreatAsComplete = !isSFINAEContext(); + diagnoseMissingImport(loc, SuggestedDef, MissingImportKind::Definition, + /*Recover*/TreatAsComplete); + return !TreatAsComplete; + } + return false; + } // Try to instantiate the definition, if this is a specialization of an // enumeration temploid. diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 6b043368db1b2f483123dcf2e205be043002cf62..2b89b6bd039e905af9cbde01e5cb4951fdf05012 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -12859,39 +12859,53 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func, // set of overloaded functions [...]. // // We (incorrectly) mark overload resolution as an unevaluated context, so we - // can just check that here. Skip the rest of this function if we've already - // marked the function as used. + // can just check that here. bool OdrUse = MightBeOdrUse && IsPotentiallyEvaluatedContext(*this); - if (Func->isUsed(/*CheckUsedAttr=*/false) || !OdrUse) { - // C++11 [temp.inst]p3: - // Unless a function template specialization has been explicitly - // instantiated or explicitly specialized, the function template - // specialization is implicitly instantiated when the specialization is - // referenced in a context that requires a function definition to exist. - // - // We consider constexpr function templates to be referenced in a context - // that requires a definition to exist whenever they are referenced. - // - // FIXME: This instantiates constexpr functions too frequently. If this is - // really an unevaluated context (and we're not just in the definition of a - // function template or overload resolution or other cases which we - // incorrectly consider to be unevaluated contexts), and we're not in a - // subexpression which we actually need to evaluate (for instance, a - // template argument, array bound or an expression in a braced-init-list), - // we are not permitted to instantiate this constexpr function definition. - // - // FIXME: This also implicitly defines special members too frequently. They - // are only supposed to be implicitly defined if they are odr-used, but they - // are not odr-used from constant expressions in unevaluated contexts. - // However, they cannot be referenced if they are deleted, and they are - // deleted whenever the implicit definition of the special member would - // fail. - if (!Func->isConstexpr() || Func->getBody()) - return; - CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Func); - if (!Func->isImplicitlyInstantiable() && (!MD || MD->isUserProvided())) - return; - } + + // Determine whether we require a function definition to exist, per + // C++11 [temp.inst]p3: + // Unless a function template specialization has been explicitly + // instantiated or explicitly specialized, the function template + // specialization is implicitly instantiated when the specialization is + // referenced in a context that requires a function definition to exist. + // + // We consider constexpr function templates to be referenced in a context + // that requires a definition to exist whenever they are referenced. + // + // FIXME: This instantiates constexpr functions too frequently. If this is + // really an unevaluated context (and we're not just in the definition of a + // function template or overload resolution or other cases which we + // incorrectly consider to be unevaluated contexts), and we're not in a + // subexpression which we actually need to evaluate (for instance, a + // template argument, array bound or an expression in a braced-init-list), + // we are not permitted to instantiate this constexpr function definition. + // + // FIXME: This also implicitly defines special members too frequently. They + // are only supposed to be implicitly defined if they are odr-used, but they + // are not odr-used from constant expressions in unevaluated contexts. + // However, they cannot be referenced if they are deleted, and they are + // deleted whenever the implicit definition of the special member would + // fail (with very few exceptions). + CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Func); + bool NeedDefinition = + OdrUse || (Func->isConstexpr() && (Func->isImplicitlyInstantiable() || + (MD && !MD->isUserProvided()))); + + // C++14 [temp.expl.spec]p6: + // If a template [...] is explicitly specialized then that specialization + // shall be declared before the first use of that specialization that would + // cause an implicit instantiation to take place, in every translation unit + // in which such a use occurs + if (NeedDefinition && + (Func->getTemplateSpecializationKind() != TSK_Undeclared || + Func->getMemberSpecializationInfo())) + checkSpecializationVisibility(Loc, Func); + + // If we don't need to mark the function as used, and we don't need to + // try to provide a definition, there's nothing more to do. + if ((Func->isUsed(/*CheckUsedAttr=*/false) || !OdrUse) && + (!NeedDefinition || Func->getBody())) + return; // Note that this declaration has been used. if (CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(Func)) { @@ -13797,6 +13811,12 @@ static void DoMarkVarDeclReferenced(Sema &SemaRef, SourceLocation Loc, assert(!isa<VarTemplatePartialSpecializationDecl>(Var) && "Can't instantiate a partial template specialization."); + // If this might be a member specialization of a static data member, check + // the specialization is visible. We already did the checks for variable + // template specializations when we created them. + if (TSK != TSK_Undeclared && !isa<VarTemplateSpecializationDecl>(Var)) + SemaRef.checkSpecializationVisibility(Loc, Var); + // Perform implicit instantiation of static data members, static data member // templates of class templates, and variable template specializations. Delay // instantiations of variable templates, except for those that could be used @@ -13840,7 +13860,8 @@ static void DoMarkVarDeclReferenced(Sema &SemaRef, SourceLocation Loc, } } - if(!MarkODRUsed) return; + if (!MarkODRUsed) + return; // Per C++11 [basic.def.odr], a variable is odr-used "unless it satisfies // the requirements for appearing in a constant expression (5.19) and, if diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp index 66e3601f0475215267f7baf271906f267a22c8b7..eaf44861d320a845e67459104cf9142548301fbd 100644 --- a/lib/Sema/SemaLookup.cpp +++ b/lib/Sema/SemaLookup.cpp @@ -1482,6 +1482,35 @@ bool Sema::hasVisibleDefaultArgument(const NamedDecl *D, Modules); } +bool Sema::hasVisibleMemberSpecialization( + const NamedDecl *D, llvm::SmallVectorImpl<Module *> *Modules) { + assert(isa<CXXRecordDecl>(D->getDeclContext()) && + "not a member specialization"); + for (auto *Redecl : D->redecls()) { + // If the specialization is declared at namespace scope, then it's a member + // specialization declaration. If it's lexically inside the class + // definition then it was instantiated. + // + // FIXME: This is a hack. There should be a better way to determine this. + // FIXME: What about MS-style explicit specializations declared within a + // class definition? + if (Redecl->getLexicalDeclContext()->isFileContext()) { + auto *NonConstR = const_cast<NamedDecl*>(cast<NamedDecl>(Redecl)); + + if (isVisible(NonConstR)) + return true; + + if (Modules) { + Modules->push_back(getOwningModule(NonConstR)); + const auto &Merged = Context.getModulesWithMergedDefinition(NonConstR); + Modules->insert(Modules->end(), Merged.begin(), Merged.end()); + } + } + } + + return false; +} + /// \brief Determine whether a declaration is visible to name lookup. /// /// This routine determines whether the declaration D is visible in the current @@ -1586,18 +1615,36 @@ static NamedDecl *findAcceptableDecl(Sema &SemaRef, NamedDecl *D) { if (RD == D) continue; - if (auto ND = dyn_cast<NamedDecl>(RD)) { - // FIXME: This is wrong in the case where the previous declaration is not - // visible in the same scope as D. This needs to be done much more - // carefully. - if (LookupResult::isVisible(SemaRef, ND)) - return ND; - } + auto ND = cast<NamedDecl>(RD); + // FIXME: This is wrong in the case where the previous declaration is not + // visible in the same scope as D. This needs to be done much more + // carefully. + if (LookupResult::isVisible(SemaRef, ND)) + return ND; } return nullptr; } +bool Sema::hasVisibleDeclarationSlow(const NamedDecl *D, + llvm::SmallVectorImpl<Module *> *Modules) { + assert(!isVisible(D) && "not in slow case"); + + for (auto *Redecl : D->redecls()) { + auto *NonConstR = const_cast<NamedDecl*>(cast<NamedDecl>(Redecl)); + if (isVisible(NonConstR)) + return true; + + if (Modules) { + Modules->push_back(getOwningModule(NonConstR)); + const auto &Merged = Context.getModulesWithMergedDefinition(NonConstR); + Modules->insert(Modules->end(), Merged.begin(), Merged.end()); + } + } + + return false; +} + NamedDecl *LookupResult::getAcceptableDeclSlow(NamedDecl *D) const { if (auto *ND = dyn_cast<NamespaceDecl>(D)) { // Namespaces are a bit of a special case: we expect there to be a lot of @@ -4904,7 +4951,7 @@ static NamedDecl *getDefinitionToImport(NamedDecl *D) { } void Sema::diagnoseMissingImport(SourceLocation Loc, NamedDecl *Decl, - bool NeedDefinition, bool Recover) { + MissingImportKind MIK, bool Recover) { assert(!isVisible(Decl) && "missing import for non-hidden decl?"); // Suggest importing a module providing the definition of this entity, if @@ -4923,9 +4970,7 @@ void Sema::diagnoseMissingImport(SourceLocation Loc, NamedDecl *Decl, auto Merged = Context.getModulesWithMergedDefinition(Decl); OwningModules.insert(OwningModules.end(), Merged.begin(), Merged.end()); - diagnoseMissingImport(Loc, Decl, Decl->getLocation(), OwningModules, - NeedDefinition ? MissingImportKind::Definition - : MissingImportKind::Declaration, + diagnoseMissingImport(Loc, Decl, Decl->getLocation(), OwningModules, MIK, Recover); } @@ -4985,6 +5030,12 @@ void Sema::diagnoseMissingImport(SourceLocation UseLoc, NamedDecl *Decl, case MissingImportKind::DefaultArgument: DiagID = diag::note_default_argument_declared_here; break; + case MissingImportKind::ExplicitSpecialization: + DiagID = diag::note_explicit_specialization_declared_here; + break; + case MissingImportKind::PartialSpecialization: + DiagID = diag::note_partial_specialization_declared_here; + break; } Diag(DeclLoc, DiagID); @@ -5020,7 +5071,7 @@ void Sema::diagnoseTypo(const TypoCorrection &Correction, assert(Decl && "import required but no declaration to import"); diagnoseMissingImport(Correction.getCorrectionRange().getBegin(), Decl, - /*NeedDefinition*/ false, ErrorRecovery); + MissingImportKind::Declaration, ErrorRecovery); return; } diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index 9d3bf1c1605d9636aae772597a4b1d74577d2fca..4960a2d14408c9e782832354cbc0c72f623c709b 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -2746,9 +2746,11 @@ Sema::CheckVarTemplateId(VarTemplateDecl *Template, SourceLocation TemplateLoc, // corresponds to these arguments. void *InsertPos = nullptr; if (VarTemplateSpecializationDecl *Spec = Template->findSpecialization( - Converted, InsertPos)) + Converted, InsertPos)) { + checkSpecializationVisibility(TemplateNameLoc, Spec); // If we already have a variable template specialization, return it. return Spec; + } // This is the first time we have referenced this variable template // specialization. Create the canonical declaration and add it to @@ -2847,8 +2849,8 @@ Sema::CheckVarTemplateId(VarTemplateDecl *Template, SourceLocation TemplateLoc, } // 2. Create the canonical declaration. - // Note that we do not instantiate the variable just yet, since - // instantiation is handled in DoMarkVarDeclReferenced(). + // Note that we do not instantiate a definition until we see an odr-use + // in DoMarkVarDeclReferenced(). // FIXME: LateAttrs et al.? VarTemplateSpecializationDecl *Decl = BuildVarTemplateInstantiation( Template, InstantiationPattern, *InstantiationArgs, TemplateArgs, @@ -2876,6 +2878,8 @@ Sema::CheckVarTemplateId(VarTemplateDecl *Template, SourceLocation TemplateLoc, dyn_cast<VarTemplatePartialSpecializationDecl>(InstantiationPattern)) Decl->setInstantiationOf(D, InstantiationArgs); + checkSpecializationVisibility(TemplateNameLoc, Decl); + assert(Decl && "No variable template specialization?"); return Decl; } @@ -3741,7 +3745,7 @@ static bool diagnoseMissingArgument(Sema &S, SourceLocation Loc, S.diagnoseMissingImport(Loc, cast<NamedDecl>(TD), D->getDefaultArgumentLoc(), Modules, Sema::MissingImportKind::DefaultArgument, - /*Recover*/ true); + /*Recover*/true); return true; } @@ -8544,3 +8548,149 @@ bool Sema::IsInsideALocalClassWithinATemplateFunction() { } return false; } + +/// \brief Walk the path from which a declaration was instantiated, and check +/// that every explicit specialization along that path is visible. This enforces +/// C++ [temp.expl.spec]/6: +/// +/// If a template, a member template or a member of a class template is +/// explicitly specialized then that specialization shall be declared before +/// the first use of that specialization that would cause an implicit +/// instantiation to take place, in every translation unit in which such a +/// use occurs; no diagnostic is required. +/// +/// and also C++ [temp.class.spec]/1: +/// +/// A partial specialization shall be declared before the first use of a +/// class template specialization that would make use of the partial +/// specialization as the result of an implicit or explicit instantiation +/// in every translation unit in which such a use occurs; no diagnostic is +/// required. +class ExplicitSpecializationVisibilityChecker { + Sema &S; + SourceLocation Loc; + llvm::SmallVector<Module *, 8> Modules; + +public: + ExplicitSpecializationVisibilityChecker(Sema &S, SourceLocation Loc) + : S(S), Loc(Loc) {} + + void check(NamedDecl *ND) { + if (auto *FD = dyn_cast<FunctionDecl>(ND)) + return checkImpl(FD); + if (auto *RD = dyn_cast<CXXRecordDecl>(ND)) + return checkImpl(RD); + if (auto *VD = dyn_cast<VarDecl>(ND)) + return checkImpl(VD); + if (auto *ED = dyn_cast<EnumDecl>(ND)) + return checkImpl(ED); + } + +private: + void diagnose(NamedDecl *D, bool IsPartialSpec) { + auto Kind = IsPartialSpec ? Sema::MissingImportKind::PartialSpecialization + : Sema::MissingImportKind::ExplicitSpecialization; + const bool Recover = true; + + // If we got a custom set of modules (because only a subset of the + // declarations are interesting), use them, otherwise let + // diagnoseMissingImport intelligently pick some. + if (Modules.empty()) + S.diagnoseMissingImport(Loc, D, Kind, Recover); + else + S.diagnoseMissingImport(Loc, D, D->getLocation(), Modules, Kind, Recover); + } + + // Check a specific declaration. There are three problematic cases: + // + // 1) The declaration is an explicit specialization of a template + // specialization. + // 2) The declaration is an explicit specialization of a member of an + // templated class. + // 3) The declaration is an instantiation of a template, and that template + // is an explicit specialization of a member of a templated class. + // + // We don't need to go any deeper than that, as the instantiation of the + // surrounding class / etc is not triggered by whatever triggered this + // instantiation, and thus should be checked elsewhere. + template<typename SpecDecl> + void checkImpl(SpecDecl *Spec) { + bool IsHiddenExplicitSpecialization = false; + if (Spec->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) { + IsHiddenExplicitSpecialization = + Spec->getMemberSpecializationInfo() + ? !S.hasVisibleMemberSpecialization(Spec, &Modules) + : !S.hasVisibleDeclaration(Spec); + } else { + checkInstantiated(Spec); + } + + if (IsHiddenExplicitSpecialization) + diagnose(Spec->getMostRecentDecl(), false); + } + + void checkInstantiated(FunctionDecl *FD) { + if (auto *TD = FD->getPrimaryTemplate()) + checkTemplate(TD); + } + + void checkInstantiated(CXXRecordDecl *RD) { + auto *SD = dyn_cast<ClassTemplateSpecializationDecl>(RD); + if (!SD) + return; + + auto From = SD->getSpecializedTemplateOrPartial(); + if (auto *TD = From.dyn_cast<ClassTemplateDecl *>()) + checkTemplate(TD); + else if (auto *TD = + From.dyn_cast<ClassTemplatePartialSpecializationDecl *>()) { + if (!S.hasVisibleDeclaration(TD)) + diagnose(TD, true); + checkTemplate(TD); + } + } + + void checkInstantiated(VarDecl *RD) { + auto *SD = dyn_cast<VarTemplateSpecializationDecl>(RD); + if (!SD) + return; + + auto From = SD->getSpecializedTemplateOrPartial(); + if (auto *TD = From.dyn_cast<VarTemplateDecl *>()) + checkTemplate(TD); + else if (auto *TD = + From.dyn_cast<VarTemplatePartialSpecializationDecl *>()) { + if (!S.hasVisibleDeclaration(TD)) + diagnose(TD, true); + checkTemplate(TD); + } + } + + void checkInstantiated(EnumDecl *FD) {} + + template<typename TemplDecl> + void checkTemplate(TemplDecl *TD) { + if (TD->isMemberSpecialization()) { + if (!S.hasVisibleMemberSpecialization(TD, &Modules)) + diagnose(TD->getMostRecentDecl(), false); + } + } +}; + +void Sema::checkSpecializationVisibility(SourceLocation Loc, NamedDecl *Spec) { + if (!getLangOpts().Modules) + return; + + ExplicitSpecializationVisibilityChecker(*this, Loc).check(Spec); +} + +/// \brief Check whether a template partial specialization that we've discovered +/// is hidden, and produce suitable diagnostics if so. +void Sema::checkPartialSpecializationVisibility(SourceLocation Loc, + NamedDecl *Spec) { + llvm::SmallVector<Module *, 8> Modules; + if (!hasVisibleDeclaration(Spec, &Modules)) + diagnoseMissingImport(Loc, Spec, Spec->getLocation(), Modules, + MissingImportKind::PartialSpecialization, + /*Recover*/true); +} diff --git a/lib/Sema/SemaTemplateInstantiate.cpp b/lib/Sema/SemaTemplateInstantiate.cpp index 42344e487ce514f8fad619d95c24c06df43833fc..c9eaa243c6cff92339c8abc1621ff1fcf1776c04 100644 --- a/lib/Sema/SemaTemplateInstantiate.cpp +++ b/lib/Sema/SemaTemplateInstantiate.cpp @@ -1859,8 +1859,19 @@ static bool DiagnoseUninstantiableTemplate(Sema &S, TagDecl *PatternDef, TemplateSpecializationKind TSK, bool Complain = true) { - if (PatternDef && !PatternDef->isBeingDefined()) + if (PatternDef && !PatternDef->isBeingDefined()) { + NamedDecl *SuggestedDef = nullptr; + if (!S.hasVisibleDefinition(PatternDef, &SuggestedDef, + /*OnlyNeedComplete*/false)) { + // If we're allowed to diagnose this and recover, do so. + bool Recover = Complain && !S.isSFINAEContext(); + if (Complain) + S.diagnoseMissingImport(PointOfInstantiation, SuggestedDef, + Sema::MissingImportKind::Definition, Recover); + return !Recover; + } return false; + } if (!Complain || (PatternDef && PatternDef->isInvalidDecl())) { // Say nothing @@ -2591,7 +2602,7 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation, if (Enum->getDefinition()) continue; - EnumDecl *Pattern = Enum->getInstantiatedFromMemberEnum(); + EnumDecl *Pattern = Enum->getTemplateInstantiationPattern(); assert(Pattern && "Missing instantiated-from-template information"); if (TSK == TSK_ExplicitInstantiationDefinition) { diff --git a/lib/Sema/SemaTemplateInstantiateDecl.cpp b/lib/Sema/SemaTemplateInstantiateDecl.cpp index 66b274f62604264dc0761e257954aa448363d0f4..ee4a0b3331f137636e1839367501b1a48d32d369 100644 --- a/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -3585,6 +3585,10 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation, Pattern = PatternDecl->getBody(PatternDecl); } + // FIXME: Check that the definition is visible before trying to instantiate + // it. This requires us to track the instantiation stack in order to know + // which definitions should be visible. + if (!Pattern && !PatternDecl->isDefaulted()) { if (DefinitionRequired) { if (Function->getPrimaryTemplate()) @@ -4078,6 +4082,10 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation, Def = PatternDecl->getOutOfLineDefinition(); } + // FIXME: Check that the definition is visible before trying to instantiate + // it. This requires us to track the instantiation stack in order to know + // which definitions should be visible. + // If we don't have a definition of the variable template, we won't perform // any instantiation. Rather, we rely on the user to instantiate this // definition (or provide a specialization for it) in another translation diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp index 1ab43f909a81111ba919fe42b43c9ea2b3b37ddd..c8c8af109b57cf22ec63029dad8d2d0088c45927 100644 --- a/lib/Sema/SemaType.cpp +++ b/lib/Sema/SemaType.cpp @@ -6799,8 +6799,8 @@ bool Sema::hasVisibleDefinition(NamedDecl *D, NamedDecl **Suggested, RD = Pattern; D = RD->getDefinition(); } else if (auto *ED = dyn_cast<EnumDecl>(D)) { - while (auto *NewED = ED->getInstantiatedFromMemberEnum()) - ED = NewED; + if (auto *Pattern = ED->getTemplateInstantiationPattern()) + ED = Pattern; if (OnlyNeedComplete && ED->isFixed()) { // If the enum has a fixed underlying type, and we're only looking for a // complete type (not a definition), any visible declaration of it will @@ -6887,9 +6887,16 @@ bool Sema::RequireCompleteTypeImpl(SourceLocation Loc, QualType T, } } - // If we have a complete type, we're done. NamedDecl *Def = nullptr; - if (!T->isIncompleteType(&Def)) { + bool Incomplete = T->isIncompleteType(&Def); + + // Check that any necessary explicit specializations are visible. For an + // enum, we just need the declaration, so don't check this. + if (Def && !isa<EnumDecl>(Def)) + checkSpecializationVisibility(Loc, Def); + + // If we have a complete type, we're done. + if (!Incomplete) { // If we know about the definition but it is not visible, complain. NamedDecl *SuggestedDef = nullptr; if (Def && @@ -6898,7 +6905,7 @@ bool Sema::RequireCompleteTypeImpl(SourceLocation Loc, QualType T, // definition visible. bool TreatAsComplete = Diagnoser && !isSFINAEContext(); if (Diagnoser) - diagnoseMissingImport(Loc, SuggestedDef, /*NeedDefinition*/true, + diagnoseMissingImport(Loc, SuggestedDef, MissingImportKind::Definition, /*Recover*/TreatAsComplete); return !TreatAsComplete; } @@ -6991,6 +6998,9 @@ bool Sema::RequireCompleteTypeImpl(SourceLocation Loc, QualType T, } } + // FIXME: If we didn't instantiate a definition because of an explicit + // specialization declaration, check that it's visible. + if (!Diagnoser) return true; diff --git a/test/Modules/Inputs/cxx-templates-common.h b/test/Modules/Inputs/cxx-templates-common.h index a9ca624486718692492204f4276135d2647a9012..8e730c8a852c5d36e7737c004871bfa2f20e4a7f 100644 --- a/test/Modules/Inputs/cxx-templates-common.h +++ b/test/Modules/Inputs/cxx-templates-common.h @@ -53,4 +53,21 @@ template<typename T> struct WithAnonymousDecls { typedef int X; }; +namespace hidden_specializations { + template<typename T> void fn() {} + + template<typename T> struct cls { + static void nested_fn() {} + struct nested_cls {}; + static int nested_var; + enum class nested_enum {}; + + template<typename U> static void nested_fn_t() {} + template<typename U> struct nested_cls_t {}; + template<typename U> static int nested_var_t; + }; + + template<typename T> int var; +} + #include "cxx-templates-textual.h" diff --git a/test/Modules/Inputs/cxx-templates-unimported.h b/test/Modules/Inputs/cxx-templates-unimported.h new file mode 100644 index 0000000000000000000000000000000000000000..c2b6b915924b0f57bd2e1a3017dbcfa5e3349e02 --- /dev/null +++ b/test/Modules/Inputs/cxx-templates-unimported.h @@ -0,0 +1,43 @@ +#include "cxx-templates-common.h" + +namespace hidden_specializations { + // explicit specializations + template<> void fn<int>() {} + template<> struct cls<int> { + void nested_fn(); + struct nested_cls; + static int nested_var; + enum nested_enum : int; + }; + template<> int var<int>; + + // partial specializations + template<typename T> struct cls<T*> { + void nested_fn(); + struct nested_cls; + static int nested_var; + enum nested_enum : int; + }; + template<typename T> int var<T*>; + + // member specializations + template<> void cls<void>::nested_fn() {} + template<> struct cls<void>::nested_cls {}; + template<> int cls<void>::nested_var; + template<> enum class cls<void>::nested_enum { e }; + template<> template<typename U> void cls<void>::nested_fn_t() {} + template<> template<typename U> struct cls<void>::nested_cls_t {}; + template<> template<typename U> int cls<void>::nested_var_t; + + // specializations instantiated here are ok if their pattern is + inline void use_stuff() { + fn<char>(); + cls<char>(); + (void)var<char>; + cls<char*>(); + (void)var<char*>; + cls<void>::nested_fn_t<char>(); + cls<void>::nested_cls_t<char>(); + (void)cls<void>::nested_var_t<char>; + } +} diff --git a/test/Modules/Inputs/module.map b/test/Modules/Inputs/module.map index 66b52e9105e88012abeb1965e46cee6d86defe82..4db1cca925c03fdf9cfb50454bc49f367e38fa4e 100644 --- a/test/Modules/Inputs/module.map +++ b/test/Modules/Inputs/module.map @@ -215,6 +215,8 @@ module cxx_linkage_cache { module cxx_templates_common { header "cxx-templates-common.h" + + explicit module unimported { header "cxx-templates-unimported.h" } } module cxx_templates_a { diff --git a/test/Modules/cxx-templates.cpp b/test/Modules/cxx-templates.cpp index ef4e4e420d04f461e379801f0d8643ebc5120ba3..eea90774aa8c6c444b5caf7b8116568c107b84d5 100644 --- a/test/Modules/cxx-templates.cpp +++ b/test/Modules/cxx-templates.cpp @@ -1,9 +1,9 @@ // RUN: rm -rf %t -// RUN: not %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -std=c++11 -ast-dump-lookups | FileCheck %s --check-prefix=CHECK-GLOBAL -// RUN: not %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -std=c++11 -ast-dump-lookups -ast-dump-filter N | FileCheck %s --check-prefix=CHECK-NAMESPACE-N -// RUN: not %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -std=c++11 -ast-dump -ast-dump-filter SomeTemplate | FileCheck %s --check-prefix=CHECK-DUMP -// RUN: %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -verify -std=c++11 -// RUN: %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -verify -std=c++11 -DEARLY_IMPORT +// RUN: not %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -std=c++14 -ast-dump-lookups | FileCheck %s --check-prefix=CHECK-GLOBAL +// RUN: not %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -std=c++14 -ast-dump-lookups -ast-dump-filter N | FileCheck %s --check-prefix=CHECK-NAMESPACE-N +// RUN: not %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -std=c++14 -ast-dump -ast-dump-filter SomeTemplate | FileCheck %s --check-prefix=CHECK-DUMP +// RUN: %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -verify -std=c++14 +// RUN: %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps -fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -verify -std=c++14 -DEARLY_IMPORT #ifdef EARLY_IMPORT #include "cxx-templates-textual.h" @@ -105,7 +105,8 @@ void g() { TemplateInstantiationVisibility<char[1]> tiv1; TemplateInstantiationVisibility<char[2]> tiv2; - TemplateInstantiationVisibility<char[3]> tiv3; // expected-error 2{{must be imported from module 'cxx_templates_b_impl'}} + TemplateInstantiationVisibility<char[3]> tiv3; // expected-error 5{{must be imported from module 'cxx_templates_b_impl'}} + // expected-note@cxx-templates-b-impl.h:10 3{{explicit specialization declared here}} // expected-note@cxx-templates-b-impl.h:10 2{{previous definition is here}} TemplateInstantiationVisibility<char[4]> tiv4; @@ -172,6 +173,63 @@ bool testFriendInClassTemplate(Std::WithFriend<int> wfi) { return wfi != wfi; } +namespace hidden_specializations { + // expected-note@cxx-templates-unimported.h:* 1+{{here}} + void test() { + // For functions, uses that would trigger instantiations of definitions are + // not allowed. + fn<void>(); // ok + fn<char>(); // ok + fn<int>(); // expected-error 1+{{explicit specialization of 'fn<int>' must be imported}} + cls<void>::nested_fn(); // expected-error 1+{{explicit specialization of 'nested_fn' must be imported}} + cls<void>::nested_fn_t<int>(); // expected-error 1+{{explicit specialization of 'nested_fn_t' must be imported}} + cls<void>::nested_fn_t<char>(); // expected-error 1+{{explicit specialization of 'nested_fn_t' must be imported}} + + // For classes, uses that would trigger instantiations of definitions are + // not allowed. + cls<void> *k0; // ok + cls<char> *k1; // ok + cls<int> *k2; // ok + cls<int*> *k3; // ok + cls<void>::nested_cls *nk1; // ok + cls<void>::nested_cls_t<int> *nk2; // ok + cls<void>::nested_cls_t<char> *nk3; // ok + cls<int> uk1; // expected-error 1+{{explicit specialization of 'cls<int>' must be imported}} expected-error 1+{{definition of}} + cls<int*> uk3; // expected-error 1+{{partial specialization of 'cls<type-parameter-0-0 *>' must be imported}} expected-error 1+{{definition of}} + cls<char*> uk4; // expected-error 1+{{partial specialization of 'cls<type-parameter-0-0 *>' must be imported}} expected-error 1+{{definition of}} + cls<void>::nested_cls unk1; // expected-error 1+{{explicit specialization of 'nested_cls' must be imported}} expected-error 1+{{definition of}} + cls<void>::nested_cls_t<int> unk2; // expected-error 1+{{explicit specialization of 'nested_cls_t' must be imported}} expected-error 1+{{definition of}} + cls<void>::nested_cls_t<char> unk3; // expected-error 1+{{explicit specialization of 'nested_cls_t' must be imported}} + + // For enums, uses that would trigger instantiations of definitions are not + // allowed. + cls<void>::nested_enum e; // ok + (void)cls<void>::nested_enum::e; // expected-error 1+{{definition of 'nested_enum' must be imported}} expected-error 1+{{declaration of 'e'}} + + // For variable template specializations, no uses are allowed because + // specializations can change the type. + (void)sizeof(var<void>); // ok + (void)sizeof(var<char>); // ok + (void)sizeof(var<int>); // expected-error 1+{{explicit specialization of 'var<int>' must be imported}} + (void)sizeof(var<int*>); // expected-error 1+{{partial specialization of 'var<type-parameter-0-0 *>' must be imported}} + (void)sizeof(var<char*>); // expected-error 1+{{partial specialization of 'var<type-parameter-0-0 *>' must be imported}} + (void)sizeof(cls<void>::nested_var); // ok + (void)cls<void>::nested_var; // expected-error 1+{{explicit specialization of 'nested_var' must be imported}} + (void)sizeof(cls<void>::nested_var_t<int>); // expected-error 1+{{explicit specialization of 'nested_var_t' must be imported}} + (void)sizeof(cls<void>::nested_var_t<char>); // expected-error 1+{{explicit specialization of 'nested_var_t' must be imported}} + } + + void cls<int>::nested_fn() {} // expected-error 1+{{explicit specialization of 'cls<int>' must be imported}} expected-error 1+{{definition of}} + struct cls<int>::nested_cls {}; // expected-error 1+{{explicit specialization of 'cls<int>' must be imported}} expected-error 1+{{definition of}} + int cls<int>::nested_var; // expected-error 1+{{explicit specialization of 'cls<int>' must be imported}} expected-error 1+{{definition of}} + enum cls<int>::nested_enum : int {}; // expected-error 1+{{explicit specialization of 'cls<int>' must be imported}} expected-error 1+{{definition of}} + + template<typename T> void cls<T*>::nested_fn() {} // expected-error 1+{{partial specialization of 'cls<type-parameter-0-0 *>' must be imported}} + template<typename T> struct cls<T*>::nested_cls {}; // expected-error 1+{{partial specialization of 'cls<type-parameter-0-0 *>' must be imported}} + template<typename T> int cls<T*>::nested_var; // expected-error 1+{{partial specialization of 'cls<type-parameter-0-0 *>' must be imported}} + template<typename T> enum cls<T*>::nested_enum : int {}; // expected-error 1+{{partial specialization of 'cls<type-parameter-0-0 *>' must be imported}} +} + namespace Std { void g(); // expected-error {{functions that differ only in their return type cannot be overloaded}} // expected-note@cxx-templates-common.h:21 {{previous}}