Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -1329,7 +1329,7 @@ let Documentation = [OverloadableDocs]; } -def Override : InheritableAttr { +def Override : InheritableAttr { let Spellings = [Keyword<"override">]; let SemaHandler = 0; let Documentation = [Undocumented]; @@ -1406,7 +1406,7 @@ def WorkGroupSizeHint : InheritableAttr { let Spellings = [GNU<"work_group_size_hint">]; - let Args = [UnsignedArgument<"XDim">, + let Args = [UnsignedArgument<"XDim">, UnsignedArgument<"YDim">, UnsignedArgument<"ZDim">]; let Subjects = SubjectList<[Function], ErrorDiag>; @@ -1665,6 +1665,12 @@ let Documentation = [Undocumented]; } +def UniqueInstantiation : InheritableAttr { + let Spellings = [GNU<"unique_instantiation">]; + let Subjects = SubjectList<[Var, Function, CXXRecord], ErrorDiag>; + let Documentation = [UniqueInstantiationDocs]; +} + def WeakImport : InheritableAttr { let Spellings = [GNU<"weak_import">]; let Documentation = [Undocumented]; Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -2300,7 +2300,40 @@ return callee(); // This call is tail-call optimized. } }; + }]; +} + +def UniqueInstantiationDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``unique_instantiation`` attribute may only be applied to explicit template +declarations and definitions, i.e. expressions of the form: + + .. code-block:: c++ + + // Explicit template declaration (usually found in a .h file) + extern template struct __attribute__((unique_instantiation)) my_template; + + // Explicit template definition (in exactly **ONE** .cpp file) + template struct __attribute__((unique_instantiation)) my_template; + + +When the unique_instantiation attribute is specified on an explicit template +instantiation, the compiler is given license to emit strong symbols for +this specific explicit template instantiation. + +If the attribute is present on one such definition or declaration for a given +entity, it must be present on all. + +Note that to ensure correct execution the user **MUST** make certain that no +other translation unit has an implicit instantiation of the same entity. In +particular this means that any usage of the entity has to be preceeded by an +appropriate explicit template declaration or definition. +It is thus recommended that explicit template declarations are placed in headers +to suppress any potential implicit instantiation of the entity. In order to +encourage this programming style, any explicit template definition with this +attribute **MUST** be preceeded by an appropriate declaration. }]; } Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2622,7 +2622,9 @@ "|variables, enums, fields and typedefs" "|functions, methods, enums, and classes" "|structs, classes, variables, functions, and inline namespaces" - "|variables, functions, methods, types, enumerations, enumerators, labels, and non-static data members}1">, + "|variables, functions, methods, types, enumerations, enumerators, labels, and non-static data members" + "|explicit template declarations or definitions" + "|functions and classes}1">, InGroup; def err_attribute_wrong_decl_type : Error; def warn_type_attribute_wrong_type : Warning< @@ -2739,8 +2741,14 @@ "%plural{0:no parameters to index into|" "1:can only be 1, since there is one parameter|" ":must be between 1 and %2}2">; - -// Thread Safety Analysis +def err_unique_instantiation_no_declaration : Error< + "'unique_instantiation' attribute on an explicit instantiation requires" + " a previous explicit instantiation declaration">; +def err_unique_instantiation_not_previous : Error< + "'unique_instantiation' attribute must be specified for all declarations and" + " definitions of this explicit template instantiation">; + +// Thread Safety Analysis def warn_unlock_but_no_lock : Warning<"releasing %0 '%1' that was not held">, InGroup, DefaultIgnore; def warn_unlock_kind_mismatch : Warning< Index: include/clang/Sema/AttributeList.h =================================================================== --- include/clang/Sema/AttributeList.h +++ include/clang/Sema/AttributeList.h @@ -924,7 +924,9 @@ ExpectedVariableEnumFieldOrTypedef, ExpectedFunctionMethodEnumOrClass, ExpectedStructClassVariableFunctionOrInlineNamespace, - ExpectedForMaybeUnused + ExpectedForMaybeUnused, + ExpectedExplicitInstantiation, + ExpectedFunctionOrClass }; } // end namespace clang Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -5504,6 +5504,9 @@ ClassTemplateSpecializationDecl *BaseTemplateSpec, SourceLocation BaseLoc); + void checkClassLevelUniqueInstantiation(CXXRecordDecl *Record); + void propagateUniqueInstantiation(CXXRecordDecl *Decl); + void CheckCompletedCXXClass(CXXRecordDecl *Record); void ActOnFinishCXXMemberSpecification(Scope* S, SourceLocation RLoc, Decl *TagDecl, Index: lib/AST/ASTContext.cpp =================================================================== --- lib/AST/ASTContext.cpp +++ lib/AST/ASTContext.cpp @@ -8728,6 +8728,12 @@ if (!FD->isExternallyVisible()) return GVA_Internal; + // If the attribute is present, this translation unit is + // responsible for emitting a unique instantiation of this + // function regardless of any of the normal considerations + if (FD->hasAttr()) + return GVA_StrongExternal; + GVALinkage External = GVA_StrongExternal; switch (FD->getTemplateSpecializationKind()) { case TSK_Undeclared: @@ -8809,6 +8815,9 @@ if (!VD->isExternallyVisible()) return GVA_Internal; + if (VD->hasAttr()) + return GVA_StrongExternal; + if (VD->isStaticLocal()) { GVALinkage StaticLocalLinkage = GVA_DiscardableODR; const DeclContext *LexicalContext = VD->getParentFunctionOrMethod(); Index: lib/CodeGen/CGVTables.cpp =================================================================== --- lib/CodeGen/CGVTables.cpp +++ lib/CodeGen/CGVTables.cpp @@ -718,6 +718,9 @@ if (keyFunction->hasBody(def)) keyFunction = cast(def); + if (keyFunction->hasAttr()) + return llvm::GlobalValue::ExternalLinkage; + switch (keyFunction->getTemplateSpecializationKind()) { case TSK_Undeclared: case TSK_ExplicitSpecialization: @@ -735,6 +738,8 @@ return llvm::GlobalVariable::ExternalLinkage; case TSK_ImplicitInstantiation: + // If the key function has strong linkage (say due to + // UniqueInstantiationAttr), the VTable should too. return !Context.getLangOpts().AppleKext ? llvm::GlobalVariable::LinkOnceODRLinkage : llvm::Function::InternalLinkage; @@ -758,7 +763,10 @@ llvm::GlobalValue::LinkOnceODRLinkage; llvm::GlobalVariable::LinkageTypes NonDiscardableODRLinkage = llvm::GlobalValue::WeakODRLinkage; - if (RD->hasAttr()) { + if (RD->hasAttr()) { + DiscardableODRLinkage = llvm::GlobalVariable::ExternalLinkage; + NonDiscardableODRLinkage = llvm::GlobalVariable::ExternalLinkage; + } else if (RD->hasAttr()) { // Cannot discard exported vtables. DiscardableODRLinkage = NonDiscardableODRLinkage; } else if (RD->hasAttr()) { Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -2249,6 +2249,19 @@ return AnyAdded; } +static void checkUniqueInstantiationAttrs(Sema &S, const Decl *New, + const Decl *Old) { + Attr *NewAttr = New->getAttr(); + + // Check that any previous definitions also had this attribute set. + if (Old->hasAttr() == (NewAttr != nullptr)) + return; + + SourceLocation NewLoc = NewAttr ? NewAttr->getLocation() : New->getLocStart(); + S.Diag(NewLoc, diag::err_unique_instantiation_not_previous); + S.Diag(Old->getLocStart(), diag::note_previous_explicit_instantiation); +} + static bool mergeDeclAttribute(Sema &S, NamedDecl *D, const InheritableAttr *Attr, Sema::AvailabilityMergeKind AMK) { @@ -2367,6 +2380,18 @@ if (!New->hasAttrs()) return; + // Explicit template instantiations need special handling because in certain + // ABIs explicit template definitions may add attributes over explicit + // template declarations. In clang getDefinition() will get the + // ClassTemplateSpecializationDecl associated with the class template + // declaration, so we'd give incorrect warnings here. + if (auto *CTSD = dyn_cast(New)) { + TemplateSpecializationKind Kind = CTSD->getSpecializationKind(); + if (Kind == TSK_ExplicitInstantiationDeclaration || + Kind == TSK_ExplicitInstantiationDefinition) + return; + } + const Decl *Def = getDefinition(Old); if (!Def || Def == New) return; @@ -2493,6 +2518,8 @@ } } + checkUniqueInstantiationAttrs(*this, New, Old); + if (!Old->hasAttrs()) return; Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -5385,6 +5385,38 @@ << Attr.getName() << "2.0"; } +static void handleUniqueInstantiation(Sema &S, Decl *D, + const AttributeList &Attr) { + if (auto *CTSD = dyn_cast(D)) { + // If this is an explicit instantiation definition. Check that it was + // preceeded by an ExplicitInstantiationDeclaration. Note, this + // requirement encourages a programming style that uses unique explicit + // instantiation declarations (typically in a header) to suppress + // implicit instantiations of a template or its members, so that the + // unique explicit instantiation definition of that template or its members + // is unique. + if (CTSD->getSpecializationKind() == TSK_ExplicitInstantiationDefinition) { + if (!CTSD->getPreviousDecl()) + S.Diag(Attr.getLoc(), diag::err_unique_instantiation_no_declaration); + } + return handleSimpleAttribute(S, D, Attr); + } else if (auto *FD = dyn_cast(D)) { + if (FD->getTemplateSpecializationKind() == + TSK_ExplicitInstantiationDefinition || + FD->getTemplateSpecializationKind() == + TSK_ExplicitInstantiationDeclaration) + return handleSimpleAttribute(S, D, Attr); + } else if (auto *VD = dyn_cast(D)) { + if (VD->getTemplateSpecializationKind() == + TSK_ExplicitInstantiationDefinition || + VD->getTemplateSpecializationKind() == + TSK_ExplicitInstantiationDeclaration) + return handleSimpleAttribute(S, D, Attr); + } + S.Diag(Attr.getLoc(), diag::err_attribute_wrong_decl_type) + << Attr.getName() << ExpectedExplicitInstantiation; +} + /// Handles semantic checking for features that are common to all attributes, /// such as checking whether a parameter was properly specified, or the correct /// number of arguments were passed, etc. @@ -5812,6 +5844,9 @@ case AttributeList::AT_Weak: handleSimpleAttribute(S, D, Attr); break; + case AttributeList::AT_UniqueInstantiation: + handleUniqueInstantiation(S, D, Attr); + break; case AttributeList::AT_WeakRef: handleWeakRefAttr(S, D, Attr); break; Index: lib/Sema/SemaDeclCXX.cpp =================================================================== --- lib/Sema/SemaDeclCXX.cpp +++ lib/Sema/SemaDeclCXX.cpp @@ -5663,6 +5663,54 @@ } } +/// \brief Check if the new class needs a unique instantiation attribute +/// based on whether its containing function or class has it +void Sema::checkClassLevelUniqueInstantiation(CXXRecordDecl *Record) { + Decl *D = Record; + if (!D || D->hasAttr()) + return; + while (DeclContext *DC = D ? D->getDeclContext() : nullptr) { + if (isa(DC) || isa(DC)) { + if (InheritableAttr *Attr = + cast(DC)->getAttr()) { + auto *NewAttr = cast(Attr->clone(getASTContext())); + NewAttr->setInherited(true); + Record->addAttr(NewAttr); + propagateUniqueInstantiation(Record); + } + return; + } + D = dyn_cast(DC); + } +} + +void Sema::propagateUniqueInstantiation(CXXRecordDecl *Class) { + InheritableAttr *Attr = Class->getAttr(); + if (!Attr) + return; + for (Decl *Member : Class->decls()) { + VarDecl *VD = dyn_cast(Member); + CXXMethodDecl *MD = dyn_cast(Member); + CXXRecordDecl *RD = dyn_cast(Member); + + if (!VD && !MD && !RD) + continue; + + if (MD) { + // Don't process deleted methods. + if (MD->isDeleted()) + continue; + } + + if (!cast(Member)->isExternallyVisible()) + continue; + + auto *NewAttr = cast(Attr->clone(getASTContext())); + NewAttr->setInherited(true); + Member->addAttr(NewAttr); + } +} + /// \brief Perform semantic checks on a class definition that has been /// completing, introducing implicitly-declared members, checking for /// abstract types, etc. @@ -5807,6 +5855,8 @@ } checkClassLevelDLLAttribute(Record); + + checkClassLevelUniqueInstantiation(Record); } /// Look up the special member function that would be called by a special Index: lib/Sema/SemaTemplate.cpp =================================================================== --- lib/Sema/SemaTemplate.cpp +++ lib/Sema/SemaTemplate.cpp @@ -7622,10 +7622,14 @@ Specialization->setExternLoc(ExternLoc); Specialization->setTemplateKeywordLoc(TemplateLoc); Specialization->setBraceRange(SourceRange()); + Specialization->setTemplateSpecializationKind(TSK); if (Attr) ProcessDeclAttributeList(S, Specialization, Attr); + if (PrevDecl) + mergeDeclAttributes(Specialization, PrevDecl); + // Add the explicit instantiation into its lexical context. However, // since explicit instantiations are never found by name lookup, we // just put it into the declaration context directly. @@ -7634,8 +7638,6 @@ // Syntax is now OK, so return if it has no other effect on semantics. if (HasNoEffect) { - // Set the template specialization kind. - Specialization->setTemplateSpecializationKind(TSK); return Specialization; } @@ -7695,14 +7697,8 @@ } } - // Set the template specialization kind. Make sure it is set before - // instantiating the members which will trigger ASTConsumer callbacks. - Specialization->setTemplateSpecializationKind(TSK); InstantiateClassTemplateSpecializationMembers(TemplateNameLoc, Def, TSK); - } else { - - // Set the template specialization kind. - Specialization->setTemplateSpecializationKind(TSK); + propagateUniqueInstantiation(Def); } return Specialization; @@ -7832,6 +7828,26 @@ return TagD; } +static void +DiagnoseUniqueInstantiation(Sema &S, Declarator &D, + TemplateSpecializationKind TSK, bool HadDeclaration, + UniqueInstantiationAttr *NewUniqueInstantiation, + UniqueInstantiationAttr *OldUniqueInstantiation, + clang::SourceLocation OldUniqueInstantiationLoc) { + if (NewUniqueInstantiation) { + if (TSK == TSK_ExplicitInstantiationDefinition && !HadDeclaration) + S.Diag(NewUniqueInstantiation->getLocation(), + diag::err_unique_instantiation_no_declaration); + else if (HadDeclaration && !OldUniqueInstantiation) + S.Diag(NewUniqueInstantiation->getLocation(), + diag::err_unique_instantiation_not_previous); + } else if (OldUniqueInstantiation) { + S.Diag(D.getIdentifierLoc(), diag::err_unique_instantiation_not_previous); + S.Diag(OldUniqueInstantiationLoc, + diag::note_previous_explicit_instantiation); + } +} + DeclResult Sema::ActOnExplicitInstantiation(Scope *S, SourceLocation ExternLoc, SourceLocation TemplateLoc, @@ -7915,6 +7931,11 @@ LookupResult Previous(*this, NameInfo, LookupOrdinaryName); LookupParsedName(Previous, S, &D.getCXXScopeSpec()); + // For diagnosing incorrect uses of unique_instantiation + UniqueInstantiationAttr *OldUniqueInstantiation = nullptr; + SourceLocation OldUniqueInstantiationLoc; + bool HadDeclaration; + if (!R->isFunctionType()) { // C++ [temp.explicit]p1: // A [...] static data member of a class template can be explicitly @@ -7996,6 +8017,14 @@ // Ignore access control bits, we don't need them for redeclaration // checking. Prev = cast(Res.get()); + + HadDeclaration = Prev->getTemplateSpecializationKind() == + TSK_ExplicitInstantiationDeclaration; + OldUniqueInstantiation = Prev->getAttr(); + if (OldUniqueInstantiation) { + OldUniqueInstantiationLoc = OldUniqueInstantiation->getLocation(); + Prev->dropAttr(); + } } // C++0x [temp.explicit]p2: @@ -8028,10 +8057,14 @@ // Instantiate static data member or variable template. Prev->setTemplateSpecializationKind(TSK, D.getIdentifierLoc()); + // Merge attributes. + if (AttributeList *Attr = D.getDeclSpec().getAttributes().getList()) + ProcessDeclAttributeList(S, Prev, Attr); if (PrevTemplate) { - // Merge attributes. - if (AttributeList *Attr = D.getDeclSpec().getAttributes().getList()) - ProcessDeclAttributeList(S, Prev, Attr); + DiagnoseUniqueInstantiation(*this, D, TSK, HadDeclaration, + Prev->getAttr(), + OldUniqueInstantiation, + OldUniqueInstantiationLoc); } if (TSK == TSK_ExplicitInstantiationDefinition) InstantiateVariableDefinition(D.getIdentifierLoc(), Prev); @@ -8171,11 +8204,24 @@ return (Decl*) nullptr; } + HadDeclaration = Specialization->getTemplateSpecializationKind() == + TSK_ExplicitInstantiationDeclaration; + OldUniqueInstantiation = Specialization->getAttr(); + if (OldUniqueInstantiation) { + OldUniqueInstantiationLoc = OldUniqueInstantiation->getLocation(); + Specialization->dropAttr(); + } + Specialization->setTemplateSpecializationKind(TSK, D.getIdentifierLoc()); AttributeList *Attr = D.getDeclSpec().getAttributes().getList(); if (Attr) ProcessDeclAttributeList(S, Specialization, Attr); + DiagnoseUniqueInstantiation( + *this, D, TSK, HadDeclaration, + Specialization->getAttr(), + OldUniqueInstantiation, OldUniqueInstantiationLoc); + if (Specialization->isDefined()) { // Let the ASTConsumer know that this function has been explicitly // instantiated now, and its linkage might have changed. Index: test/CodeGenCXX/unique-instantiation.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/unique-instantiation.cpp @@ -0,0 +1,74 @@ +// RUN: %clang -std=c++14 -emit-llvm -c -S -o - %s | FileCheck %s + +// CHECK: @_Z2piIfE = global float +// CHECK-NOT: @_Z2piIfE = weak_odr global float +template +T pi = T(3.1415926535897932385); +extern template __attribute__((unique_instantiation)) float pi; +template __attribute__((unique_instantiation)) float pi; + +template +struct foo { + T x; + T getX() { return x; } + struct bar { + T y; + bar(T y) : y(y) {} + }; +}; +template +T bar(); + +// CHECK: _ZZ12functemplateIiEDaT_E11innerStatic = global i32 0, align 4 +// CHECK: @_ZTVZ12functemplateIiEDaT_E5inner = unnamed_addr +// CHECK: @_ZTSZ12functemplateIiEDaT_E5inner = constant +// CHECK: @_ZTIZ12functemplateIiEDaT_E5inner = constant +// CHECK-NOT: @_ZZ12functemplateIiEDaT_E11innerStatic = linkonce_odr global +// CHECK-NOT: @_ZTVZ12functemplateIiEDaT_E5inner = linkoce_odr unnamed_addr +// CHECK-NOT: @_ZTSZ12functemplateIiEDaT_E5inner = linkoce_odr unnamed_addr +// CHECK-NOT: @_ZTIZ12functemplateIiEDaT_E5inner = linkoce_odr unnamed_addr +template +auto functemplate(T x) { + static T innerStatic = x; + class inner { + T y; + + public: + virtual ~inner() {} + inner(T y) : y(y) {} + T getY() { return y; } + }; + return inner{x}.getY(); +} + +// CHECK: define i32 @_Z12functemplateIiEDaT_ +// CHECK: define i32 @_ZZ12functemplateIiEDaT_EN5inner4getYEv +// CHECK-NOT: define weak_odr i32 @_Z12functemplateIiEDaT_ +// CHECK-NOT: define linkonce_odr i32 @_ZZ12functemplateIiEDaT_EN5inner4getYEv +extern template auto __attribute__((unique_instantiation)) functemplate(int); +template auto __attribute__((unique_instantiation)) functemplate(int); + +// CHECK: define i32 @_ZN3fooIiE4getXEv +// CHECK: define void @_ZN3fooIiE3barC2Ei +// CHECK-NOT: define weak_odr i32 @_ZN3fooIiE4getXEv +// CHECK-NOT: define weak_odr void @_ZN3fooIiE3barC2Ei +extern template struct __attribute__((unique_instantiation)) foo; +template struct __attribute__((unique_instantiation)) foo; + +extern template __attribute__((unique_instantiation)) int bar(); + +template +T bar() { + return (T)0; +} + +// CHECK: define i32 @_Z3barIiET_v() +// CHECK-NOT: define weak_odr i32 @_Z3barIiET_v() +template __attribute__((unique_instantiation)) int bar(); + +int footest() { + auto var = foo{5}; + auto var2 = foo::bar{5}; + auto x = bar(); + return var.getX(); +} Index: test/SemaCXX/unique-instantiations.cpp =================================================================== --- /dev/null +++ test/SemaCXX/unique-instantiations.cpp @@ -0,0 +1,48 @@ +// RUN: %clang -cc1 -std=c++14 -fsyntax-only -verify %s + +// Correct usage +template +struct foo {}; +extern template struct __attribute__((unique_instantiation)) foo; +template struct __attribute__((unique_instantiation)) foo; + +template +T pi = T(3.1415926535897932385); +extern template __attribute__((unique_instantiation)) float pi; +template __attribute__((unique_instantiation)) float pi; + +// Usages on non-templates +float __attribute__((unique_instantiation)) notpi(2.71828182845904523536028747135); // expected-error{{'unique_instantiation' attribute only applies to explicit template declarations or definitions}} +struct __attribute__((unique_instantiation)) bar {}; // expected-error{{'unique_instantiation' attribute only applies to explicit template declarations or definitions}} +void __attribute__((unique_instantiation)) func() {} // expected-error{{'unique_instantiation' attribute only applies to explicit template declarations or definitions}} + +// Usages that violate one of the conditions required conditions +template +struct foo1 {}; +template struct __attribute__((unique_instantiation)) foo1; // expected-error{{'unique_instantiation' attribute on an explicit instantiation requires a previous explicit instantiation declaration}} + +template +T pi1 = T(3.1415926535897932385); +template __attribute__((unique_instantiation)) float pi1; // expected-error{{'unique_instantiation' attribute on an explicit instantiation requires a previous explicit instantiation declaration}} + +template +struct foo2 {}; +extern template struct foo2; // expected-note{{previous explicit instantiation is here}} +template struct __attribute__((unique_instantiation)) foo2; // expected-error{{'unique_instantiation' attribute must be specified for all declarations and definitions of this explicit template instantiation}} + +template +struct foo3 {}; +extern template struct __attribute__((unique_instantiation)) foo3; // expected-note{{previous explicit instantiation is here}} +extern template struct foo3; // expected-error{{'unique_instantiation' attribute must be specified for all declarations and definitions of this explicit template instantiation}} + +template +struct __attribute__((unique_instantiation)) foo4 {}; // expected-error{{'unique_instantiation' attribute only applies to explicit template declarations or definitions}} + +template +struct foo5 {}; +extern template struct __attribute__((unique_instantiation)) foo5; // expected-note{{previous explicit instantiation is here}} +template struct foo5; // expected-error{{'unique_instantiation' attribute must be specified for all declarations and definitions of this explicit template instantiation}} + +template +struct foo6 {}; +extern template struct __attribute__((unique_instantiation(16))) foo6; // expected-error{{'unique_instantiation' attribute takes no arguments}} Index: utils/TableGen/ClangAttrEmitter.cpp =================================================================== --- utils/TableGen/ClangAttrEmitter.cpp +++ utils/TableGen/ClangAttrEmitter.cpp @@ -2662,6 +2662,8 @@ case Func | ObjCMethod | Param: return "ExpectedFunctionMethodOrParameter"; case Func | ObjCMethod: return "ExpectedFunctionOrMethod"; case Func | Var: return "ExpectedVariableOrFunction"; + case Func | Class: + return "ExpectedFunctionOrClass"; // If not compiling for C++, the class portion does not apply. case Func | Var | Class: