Index: include/clang/AST/ASTMutationListener.h =================================================================== --- include/clang/AST/ASTMutationListener.h +++ include/clang/AST/ASTMutationListener.h @@ -111,7 +111,9 @@ /// previously marked as declaretarget. /// /// \param D the declaration marked OpenMP declaretarget. - virtual void DeclarationMarkedOpenMPDeclareTarget(const Decl *D) {} + /// \param Attr the added attribute. + virtual void DeclarationMarkedOpenMPDeclareTarget(const Decl *D, + const Attr *Attr) {} /// \brief A definition has been made visible by being redefined locally. /// Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -2345,8 +2345,17 @@ let Spellings = [Pragma<"omp", "declare target">]; let SemaHandler = 0; let Documentation = [OMPDeclareTargetDocs]; + let Args = [ + EnumArgument<"MapType", "MapTypeTy", + [ "to", "link" ], + [ "MT_To", "MT_Link" ]> + ]; let AdditionalMembers = [{ - void printPrettyPragma(raw_ostream &OS, const PrintingPolicy &Policy) const {} + void printPrettyPragma(raw_ostream &OS, const PrintingPolicy &Policy) const { + // Use fake syntax because it is for testing and debugging purpose only. + if (getMapType() != MT_To) + OS << ConvertMapTypeTyToStr(getMapType()) << " "; + } }]; } Index: include/clang/Basic/DiagnosticParseKinds.td =================================================================== --- include/clang/Basic/DiagnosticParseKinds.td +++ include/clang/Basic/DiagnosticParseKinds.td @@ -967,6 +967,8 @@ "unexpected '%0' clause, '%1' is specified already">; def err_expected_end_declare_target : Error< "expected '#pragma omp end declare target'">; +def err_omp_declare_target_unexpected_clause: Error< + "unexpected '%0' clause, only 'to' or 'link' clauses expected">; // Pragma loop support. def err_pragma_loop_missing_argument : Error< Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -7986,6 +7986,12 @@ InGroup; def err_omp_enclosed_declare_target : Error< "declare target region may not be enclosed within another declare target region">; +def err_omp_invalid_target_decl : Error< + "%0 used in declare target directive is not a variable or a function name">; +def err_omp_declare_target_multiple : Error< + "%0 appears multiple times in clauses on the same declare target directive">; +def err_omp_declare_target_to_and_link : Error< + "%0 must not appear in both clauses 'to' and 'link'">; def warn_omp_not_in_target_context : Warning< "declaration is not declared in any declare target region">, InGroup; Index: include/clang/Parse/Parser.h =================================================================== --- include/clang/Parse/Parser.h +++ include/clang/Parse/Parser.h @@ -2466,16 +2466,19 @@ Decl *TagDecl = nullptr); /// \brief Parse 'omp declare reduction' construct. DeclGroupPtrTy ParseOpenMPDeclareReductionDirective(AccessSpecifier AS); + /// \brief Parses simple list of variables. /// /// \param Kind Kind of the directive. - /// \param [out] VarList List of referenced variables. + /// \param Callback Callback function to be called for the list elements. /// \param AllowScopeSpecifier true, if the variables can have fully /// qualified names. /// - bool ParseOpenMPSimpleVarList(OpenMPDirectiveKind Kind, - SmallVectorImpl &VarList, - bool AllowScopeSpecifier); + bool ParseOpenMPSimpleVarList( + OpenMPDirectiveKind Kind, + const llvm::function_ref & + Callback, + bool AllowScopeSpecifier); /// \brief Parses declarative or executable directive. /// /// \param Allowed ACK_Any, if any directives are allowed, Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -7976,6 +7976,11 @@ bool ActOnStartOpenMPDeclareTargetDirective(SourceLocation Loc); /// Called at the end of target region i.e. '#pragme omp end declare target'. void ActOnFinishOpenMPDeclareTargetDirective(); + /// Called on correct id-expression from the '#pragma omp declare target'. + void ActOnOpenMPDeclareTargetName(Scope *CurScope, CXXScopeSpec &ScopeSpec, + const DeclarationNameInfo &Id, + OMPDeclareTargetDeclAttr::MapTypeTy MT, + NamedDeclSetType &SameDirectiveDecls); /// Check declaration inside target region. void checkDeclIsAllowedInOpenMPTarget(Expr *E, Decl *D); /// Return true inside OpenMP target region. Index: include/clang/Serialization/ASTWriter.h =================================================================== --- include/clang/Serialization/ASTWriter.h +++ include/clang/Serialization/ASTWriter.h @@ -682,7 +682,8 @@ const ObjCInterfaceDecl *IFD) override; void DeclarationMarkedUsed(const Decl *D) override; void DeclarationMarkedOpenMPThreadPrivate(const Decl *D) override; - void DeclarationMarkedOpenMPDeclareTarget(const Decl *D) override; + void DeclarationMarkedOpenMPDeclareTarget(const Decl *D, + const Attr *Attr) override; void RedefinedHiddenDefinition(const NamedDecl *D, Module *M) override; void AddedAttributeToRecord(const Attr *Attr, const RecordDecl *Record) override; Index: lib/Frontend/MultiplexConsumer.cpp =================================================================== --- lib/Frontend/MultiplexConsumer.cpp +++ lib/Frontend/MultiplexConsumer.cpp @@ -125,7 +125,8 @@ void FunctionDefinitionInstantiated(const FunctionDecl *D) override; void DeclarationMarkedUsed(const Decl *D) override; void DeclarationMarkedOpenMPThreadPrivate(const Decl *D) override; - void DeclarationMarkedOpenMPDeclareTarget(const Decl *D) override; + void DeclarationMarkedOpenMPDeclareTarget(const Decl *D, + const Attr *Attr) override; void RedefinedHiddenDefinition(const NamedDecl *D, Module *M) override; void AddedAttributeToRecord(const Attr *Attr, const RecordDecl *Record) override; @@ -221,9 +222,9 @@ Listeners[i]->DeclarationMarkedOpenMPThreadPrivate(D); } void MultiplexASTMutationListener::DeclarationMarkedOpenMPDeclareTarget( - const Decl *D) { + const Decl *D, const Attr *Attr) { for (auto *L : Listeners) - L->DeclarationMarkedOpenMPDeclareTarget(D); + L->DeclarationMarkedOpenMPDeclareTarget(D, Attr); } void MultiplexASTMutationListener::RedefinedHiddenDefinition(const NamedDecl *D, Module *M) { Index: lib/Parse/ParseOpenMP.cpp =================================================================== --- lib/Parse/ParseOpenMP.cpp +++ lib/Parse/ParseOpenMP.cpp @@ -40,6 +40,21 @@ OMPD_target_enter, OMPD_target_exit }; + +class ThreadprivateListParserHelper final { + SmallVector Identifiers; + Parser *P; + +public: + ThreadprivateListParserHelper(Parser *P) : P(P) {} + void operator()(CXXScopeSpec &SS, DeclarationNameInfo NameInfo) { + ExprResult Res = + P->getActions().ActOnOpenMPIdExpression(P->getCurScope(), SS, NameInfo); + if (Res.isUsable()) + Identifiers.push_back(Res.get()); + } + llvm::ArrayRef getIdentifiers() const { return Identifiers; } +}; } // namespace // Map token string to extended OMP token kind that are @@ -525,13 +540,13 @@ ParenBraceBracketBalancer BalancerRAIIObj(*this); SourceLocation Loc = ConsumeToken(); - SmallVector Identifiers; auto DKind = ParseOpenMPDirectiveKind(*this); switch (DKind) { - case OMPD_threadprivate: + case OMPD_threadprivate: { ConsumeToken(); - if (!ParseOpenMPSimpleVarList(OMPD_threadprivate, Identifiers, true)) { + ThreadprivateListParserHelper Helper(this); + if (!ParseOpenMPSimpleVarList(OMPD_threadprivate, Helper, true)) { // The last seen token is annot_pragma_openmp_end - need to check for // extra tokens. if (Tok.isNot(tok::annot_pragma_openmp_end)) { @@ -541,9 +556,11 @@ } // Skip the last annot_pragma_openmp_end. ConsumeToken(); - return Actions.ActOnOpenMPThreadprivateDirective(Loc, Identifiers); + return Actions.ActOnOpenMPThreadprivateDirective(Loc, + Helper.getIdentifiers()); } break; + } case OMPD_declare_reduction: ConsumeToken(); if (auto Res = ParseOpenMPDeclareReductionDirective(AS)) { @@ -599,10 +616,40 @@ case OMPD_declare_target: { SourceLocation DTLoc = ConsumeAnyToken(); if (Tok.isNot(tok::annot_pragma_openmp_end)) { - Diag(Tok, diag::warn_omp_extra_tokens_at_eol) - << getOpenMPDirectiveName(OMPD_declare_target); + // OpenMP 4.5 syntax with list of entities. + llvm::SmallSetVector SameDirectiveDecls; + while (Tok.isNot(tok::annot_pragma_openmp_end)) { + OMPDeclareTargetDeclAttr::MapTypeTy MT = + OMPDeclareTargetDeclAttr::MT_To; + if (Tok.is(tok::identifier)) { + IdentifierInfo *II = Tok.getIdentifierInfo(); + StringRef ClauseName = II->getName(); + // Parse 'to|link' clauses. + if (!OMPDeclareTargetDeclAttr::ConvertStrToMapTypeTy(ClauseName, + MT)) { + Diag(Tok, diag::err_omp_declare_target_unexpected_clause) + << ClauseName; + break; + } + ConsumeToken(); + } + auto Callback = [this, MT, &SameDirectiveDecls]( + CXXScopeSpec &SS, DeclarationNameInfo NameInfo) { + Actions.ActOnOpenMPDeclareTargetName(getCurScope(), SS, NameInfo, MT, + SameDirectiveDecls); + }; + if (ParseOpenMPSimpleVarList(OMPD_declare_target, Callback, true)) + break; + + // Consume optional ','. + if (Tok.is(tok::comma)) + ConsumeToken(); + } SkipUntil(tok::annot_pragma_openmp_end, StopBeforeMatch); + ConsumeAnyToken(); + return DeclGroupPtrTy(); } + // Skip the last annot_pragma_openmp_end. ConsumeAnyToken(); @@ -716,7 +763,6 @@ AllowedContsructsKind Allowed) { assert(Tok.is(tok::annot_pragma_openmp) && "Not an OpenMP directive!"); ParenBraceBracketBalancer BalancerRAIIObj(*this); - SmallVector Identifiers; SmallVector Clauses; SmallVector, OMPC_unknown + 1> FirstClauses(OMPC_unknown + 1); @@ -732,13 +778,14 @@ bool FlushHasClause = false; switch (DKind) { - case OMPD_threadprivate: + case OMPD_threadprivate: { if (Allowed != ACK_Any) { Diag(Tok, diag::err_omp_immediate_directive) << getOpenMPDirectiveName(DKind) << 0; } ConsumeToken(); - if (!ParseOpenMPSimpleVarList(OMPD_threadprivate, Identifiers, false)) { + ThreadprivateListParserHelper Helper(this); + if (!ParseOpenMPSimpleVarList(OMPD_threadprivate, Helper, false)) { // The last seen token is annot_pragma_openmp_end - need to check for // extra tokens. if (Tok.isNot(tok::annot_pragma_openmp_end)) { @@ -746,12 +793,13 @@ << getOpenMPDirectiveName(OMPD_threadprivate); SkipUntil(tok::annot_pragma_openmp_end, StopBeforeMatch); } - DeclGroupPtrTy Res = - Actions.ActOnOpenMPThreadprivateDirective(Loc, Identifiers); + DeclGroupPtrTy Res = Actions.ActOnOpenMPThreadprivateDirective( + Loc, Helper.getIdentifiers()); Directive = Actions.ActOnDeclStmt(Res, Loc, Tok.getLocation()); } SkipUntil(tok::annot_pragma_openmp_end); break; + } case OMPD_declare_reduction: ConsumeToken(); if (auto Res = ParseOpenMPDeclareReductionDirective(/*AS=*/AS_none)) { @@ -913,16 +961,15 @@ return Directive; } -/// \brief Parses list of simple variables for '#pragma omp threadprivate' -/// directive. -/// -/// simple-variable-list: -/// '(' id-expression {, id-expression} ')' -/// -bool Parser::ParseOpenMPSimpleVarList(OpenMPDirectiveKind Kind, - SmallVectorImpl &VarList, - bool AllowScopeSpecifier) { - VarList.clear(); +// Parses simple list: +// simple-variable-list: +// '(' id-expression {, id-expression} ')' +// +bool Parser::ParseOpenMPSimpleVarList( + OpenMPDirectiveKind Kind, + const llvm::function_ref & + Callback, + bool AllowScopeSpecifier) { // Parse '('. BalancedDelimiterTracker T(*this, tok::l_paren, tok::annot_pragma_openmp_end); if (T.expectAndConsume(diag::err_expected_lparen_after, @@ -959,11 +1006,7 @@ << tok::identifier << SourceRange(PrevTok.getLocation(), PrevTokLocation); } else { - DeclarationNameInfo NameInfo = Actions.GetNameFromUnqualifiedId(Name); - ExprResult Res = - Actions.ActOnOpenMPIdExpression(getCurScope(), SS, NameInfo); - if (Res.isUsable()) - VarList.push_back(Res.get()); + Callback(SS, Actions.GetNameFromUnqualifiedId(Name)); } // Consume ','. if (Tok.is(tok::comma)) { @@ -979,7 +1022,7 @@ // Parse ')'. IsCorrect = !T.consumeClose() && IsCorrect; - return !IsCorrect && VarList.empty(); + return !IsCorrect; } /// \brief Parsing of OpenMP clauses. Index: lib/Sema/SemaOpenMP.cpp =================================================================== --- lib/Sema/SemaOpenMP.cpp +++ lib/Sema/SemaOpenMP.cpp @@ -1101,6 +1101,23 @@ return false; } }; + +class VarOrFuncDeclFilterCCC : public CorrectionCandidateCallback { +private: + Sema &SemaRef; + +public: + explicit VarOrFuncDeclFilterCCC(Sema &S) : SemaRef(S) {} + bool ValidateCandidate(const TypoCorrection &Candidate) override { + NamedDecl *ND = Candidate.getCorrectionDecl(); + if (isa(ND) || isa(ND)) { + return SemaRef.isDeclInScope(ND, SemaRef.getCurLexicalContext(), + SemaRef.getCurScope()); + } + return false; + } +}; + } // namespace ExprResult Sema::ActOnOpenMPIdExpression(Scope *CurScope, @@ -10752,6 +10769,52 @@ IsInOpenMPDeclareTargetContext = false; } +void +Sema::ActOnOpenMPDeclareTargetName(Scope *CurScope, CXXScopeSpec &ScopeSpec, + const DeclarationNameInfo &Id, + OMPDeclareTargetDeclAttr::MapTypeTy MT, + NamedDeclSetType &SameDirectiveDecls) { + LookupResult Lookup(*this, Id, LookupOrdinaryName); + LookupParsedName(Lookup, CurScope, &ScopeSpec, true); + + if (Lookup.isAmbiguous()) + return; + Lookup.suppressDiagnostics(); + + if (!Lookup.isSingleResult()) { + if (TypoCorrection Corrected = + CorrectTypo(Id, LookupOrdinaryName, CurScope, nullptr, + llvm::make_unique(*this), + CTK_ErrorRecovery)) { + diagnoseTypo(Corrected, PDiag(diag::err_undeclared_var_use_suggest) + << Id.getName()); + checkDeclIsAllowedInOpenMPTarget(nullptr, Corrected.getCorrectionDecl()); + return; + } + + Diag(Id.getLoc(), diag::err_undeclared_var_use) << Id.getName(); + return; + } + + NamedDecl *ND = Lookup.getAsSingle(); + if (isa(ND) || isa(ND)) { + if (!SameDirectiveDecls.insert(cast(ND->getCanonicalDecl()))) + Diag(Id.getLoc(), diag::err_omp_declare_target_multiple) << Id.getName(); + + if (!ND->hasAttr()) { + Attr *A = OMPDeclareTargetDeclAttr::CreateImplicit(Context, MT); + ND->addAttr(A); + if (ASTMutationListener *ML = Context.getASTMutationListener()) + ML->DeclarationMarkedOpenMPDeclareTarget(ND, A); + checkDeclIsAllowedInOpenMPTarget(nullptr, ND); + } else if (ND->getAttr()->getMapType() != MT) { + Diag(Id.getLoc(), diag::err_omp_declare_target_to_and_link) + << Id.getName(); + } + } else + Diag(Id.getLoc(), diag::err_omp_invalid_target_decl) << Id.getName(); +} + static void checkDeclInTargetContext(SourceLocation SL, SourceRange SR, Sema &SemaRef, Decl *D) { if (!D) @@ -10765,9 +10828,11 @@ // If this is an implicit variable that is legal and we do not need to do // anything. if (cast(D)->isImplicit()) { - D->addAttr(OMPDeclareTargetDeclAttr::CreateImplicit(SemaRef.Context)); + Attr *A = OMPDeclareTargetDeclAttr::CreateImplicit( + SemaRef.Context, OMPDeclareTargetDeclAttr::MT_To); + D->addAttr(A); if (ASTMutationListener *ML = SemaRef.Context.getASTMutationListener()) - ML->DeclarationMarkedOpenMPDeclareTarget(D); + ML->DeclarationMarkedOpenMPDeclareTarget(D, A); return; } @@ -10780,9 +10845,11 @@ // target region (it can be e.g. a lambda) that is legal and we do not need // to do anything else. if (LD == D) { - D->addAttr(OMPDeclareTargetDeclAttr::CreateImplicit(SemaRef.Context)); + Attr *A = OMPDeclareTargetDeclAttr::CreateImplicit( + SemaRef.Context, OMPDeclareTargetDeclAttr::MT_To); + D->addAttr(A); if (ASTMutationListener *ML = SemaRef.Context.getASTMutationListener()) - ML->DeclarationMarkedOpenMPDeclareTarget(D); + ML->DeclarationMarkedOpenMPDeclareTarget(D, A); return; } } @@ -10810,9 +10877,11 @@ SemaRef.Diag(SL, diag::note_used_here) << SR; } // Mark decl as declared target to prevent further diagnostic. - D->addAttr(OMPDeclareTargetDeclAttr::CreateImplicit(SemaRef.Context)); + Attr *A = OMPDeclareTargetDeclAttr::CreateImplicit( + SemaRef.Context, OMPDeclareTargetDeclAttr::MT_To); + D->addAttr(A); if (ASTMutationListener *ML = SemaRef.Context.getASTMutationListener()) - ML->DeclarationMarkedOpenMPDeclareTarget(D); + ML->DeclarationMarkedOpenMPDeclareTarget(D, A); } } @@ -10846,9 +10915,11 @@ !checkValueDeclInTarget(SL, SR, *this, DSAStack, VD)) { // Mark decl as declared target to prevent further diagnostic. if (isa(VD) || isa(VD)) { - VD->addAttr(OMPDeclareTargetDeclAttr::CreateImplicit(Context)); + Attr *A = OMPDeclareTargetDeclAttr::CreateImplicit( + Context, OMPDeclareTargetDeclAttr::MT_To); + VD->addAttr(A); if (ASTMutationListener *ML = Context.getASTMutationListener()) - ML->DeclarationMarkedOpenMPDeclareTarget(VD); + ML->DeclarationMarkedOpenMPDeclareTarget(VD, A); } return; } @@ -10857,9 +10928,11 @@ // Checking declaration inside declare target region. if (!D->hasAttr() && (isa(D) || isa(D))) { - D->addAttr(OMPDeclareTargetDeclAttr::CreateImplicit(Context)); + Attr *A = OMPDeclareTargetDeclAttr::CreateImplicit( + Context, OMPDeclareTargetDeclAttr::MT_To); + D->addAttr(A); if (ASTMutationListener *ML = Context.getASTMutationListener()) - ML->DeclarationMarkedOpenMPDeclareTarget(D); + ML->DeclarationMarkedOpenMPDeclareTarget(D, A); } return; } Index: lib/Serialization/ASTReaderDecl.cpp =================================================================== --- lib/Serialization/ASTReaderDecl.cpp +++ lib/Serialization/ASTReaderDecl.cpp @@ -3891,11 +3891,6 @@ Reader.Context, ReadSourceRange(Record, Idx))); break; - case UPD_DECL_MARKED_OPENMP_DECLARETARGET: - D->addAttr(OMPDeclareTargetDeclAttr::CreateImplicit( - Reader.Context, ReadSourceRange(Record, Idx))); - break; - case UPD_DECL_EXPORTED: { unsigned SubmoduleID = readSubmoduleID(Record, Idx); auto *Exported = cast(D); @@ -3921,6 +3916,7 @@ break; } + case UPD_DECL_MARKED_OPENMP_DECLARETARGET: case UPD_ADDED_ATTR_TO_RECORD: AttrVec Attrs; Reader.ReadAttributes(F, Attrs, Record, Idx); Index: lib/Serialization/ASTWriter.cpp =================================================================== --- lib/Serialization/ASTWriter.cpp +++ lib/Serialization/ASTWriter.cpp @@ -5816,12 +5816,14 @@ DeclUpdates[D].push_back(DeclUpdate(UPD_DECL_MARKED_OPENMP_THREADPRIVATE)); } -void ASTWriter::DeclarationMarkedOpenMPDeclareTarget(const Decl *D) { +void ASTWriter::DeclarationMarkedOpenMPDeclareTarget(const Decl *D, + const Attr *Attr) { assert(!WritingAST && "Already writing the AST!"); if (!D->isFromASTFile()) return; - DeclUpdates[D].push_back(DeclUpdate(UPD_DECL_MARKED_OPENMP_DECLARETARGET)); + DeclUpdates[D].push_back( + DeclUpdate(UPD_DECL_MARKED_OPENMP_DECLARETARGET, Attr)); } void ASTWriter::RedefinedHiddenDefinition(const NamedDecl *D, Module *M) { Index: test/OpenMP/declare_target_ast_print.cpp =================================================================== --- test/OpenMP/declare_target_ast_print.cpp +++ test/OpenMP/declare_target_ast_print.cpp @@ -79,6 +79,51 @@ #pragma omp end declare target // CHECK: #pragma omp end declare target +int a1; +void f1() { +} +#pragma omp declare target (a1, f1) +// CHECK: #pragma omp declare target +// CHECK: int a1; +// CHECK: #pragma omp end declare target +// CHECK: #pragma omp declare target +// CHECK: void f1() +// CHECK: #pragma omp end declare target + +int b1, b2, b3; +void f2() { +} +#pragma omp declare target to(b1) to(b2), to(b3, f2) +// CHECK: #pragma omp declare target +// CHECK: int b1; +// CHECK: #pragma omp end declare target +// CHECK: #pragma omp declare target +// CHECK: int b2; +// CHECK: #pragma omp end declare target +// CHECK: #pragma omp declare target +// CHECK: int b3; +// CHECK: #pragma omp end declare target +// CHECK: #pragma omp declare target +// CHECK: void f2() +// CHECK: #pragma omp end declare target + +int c1, c2, c3; +void f3() { +} +#pragma omp declare target link(c1) link(c2), link(c3, f3) +// CHECK: #pragma omp declare target link +// CHECK: int c1; +// CHECK: #pragma omp end declare target +// CHECK: #pragma omp declare target link +// CHECK: int c2; +// CHECK: #pragma omp end declare target +// CHECK: #pragma omp declare target link +// CHECK: int c3; +// CHECK: #pragma omp end declare target +// CHECK: #pragma omp declare target link +// CHECK: void f3() +// CHECK: #pragma omp end declare target + int main (int argc, char **argv) { foo(); foo_c(); Index: test/OpenMP/declare_target_messages.cpp =================================================================== --- test/OpenMP/declare_target_messages.cpp +++ test/OpenMP/declare_target_messages.cpp @@ -4,9 +4,15 @@ int a, b; // expected-warning {{declaration is not declared in any declare target region}} __thread int t; // expected-note {{defined as threadprivate or thread local}} -#pragma omp declare target private(a) // expected-warning {{extra tokens at the end of '#pragma omp declare target' are ignored}} + +#pragma omp declare target . // expected-error {{expected '(' after 'declare target'}} + +#pragma omp declare target void f(); #pragma omp end declare target shared(a) // expected-warning {{extra tokens at the end of '#pragma omp end declare target' are ignored}} + +#pragma omp declare target map(a) // expected-error {{unexpected 'map' clause, only 'to' or 'link' clauses expected}} + void c(); // expected-warning {{declaration is not declared in any declare target region}} extern int b; @@ -86,4 +92,10 @@ } // expected-error {{expected '#pragma omp end declare target'}} #pragma omp end declare target // expected-error {{unexpected OpenMP directive '#pragma omp end declare target'}} +#pragma omp declare target link(S) // expected-error {{'S' used in declare target directive is not a variable or a function name}} + +#pragma omp declare target (x, x) // expected-error {{'x' appears multiple times in clauses on the same declare target directive}} +#pragma omp declare target to(x) to(x) // expected-error {{'x' appears multiple times in clauses on the same declare target directive}} +#pragma omp declare target link(x) // expected-error {{'x' must not appear in both clauses 'to' and 'link'}} + #pragma omp declare target // expected-error {{expected '#pragma omp end declare target'}} expected-note {{to match this '#pragma omp declare target'}}