Author: ykhatav Date: 2026-08-10T09:48:16-04:00 New Revision: dd297590de15d5a9a287425d74c67abb46bf9136
URL: https://github.com/llvm/llvm-project/commit/dd297590de15d5a9a287425d74c67abb46bf9136 DIFF: https://github.com/llvm/llvm-project/commit/dd297590de15d5a9a287425d74c67abb46bf9136.diff LOG: [Clang][OpenMP] Validate prefer_type fr()/attr() arguments in append_args clause (#212307) The OpenMP 6.0 spec states that append_args(interop(...)) accepts the same modifier-specification-list as the init clause, which includes prefer_type. However, the parser rejected prefer_type in append_args, and the semantic validation for fr()/attr() arguments was only performed for the init clause. This patch allows prefer_type in the parser for append_args and adds the corresponding semantic validation of fr()/attr() arguments to ActOnOpenMPDeclareVariantDirective. Added: clang/test/OpenMP/declare_variant_append_args_prefer_type_ast_print.cpp clang/test/OpenMP/declare_variant_append_args_prefer_type_messages.cpp Modified: clang/include/clang/Basic/DiagnosticParseKinds.td clang/lib/AST/AttrImpl.cpp clang/lib/Parse/ParseOpenMP.cpp clang/lib/Sema/SemaOpenMP.cpp clang/lib/Sema/SemaTemplateInstantiateDecl.cpp clang/utils/TableGen/ClangAttrEmitter.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 2672bfb2952c8..6a48d74079f4e 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1722,6 +1722,8 @@ def err_omp_expected_fr_or_attr_selector : Error< "expected 'fr' or 'attr' selector in 'prefer_type'">; def err_omp_expected_pref_spec : Error< "expected preference-specification in 'prefer_type'">; +def err_omp_append_args_prefer_type_60 : Error< + "'prefer_type' in 'append_args' requires OpenMP version 6.0 or later">; def err_expected_sequence_or_directive : Error< "expected an OpenMP 'directive' or 'sequence' attribute argument">; def ext_omp_attributes : ExtWarn< diff --git a/clang/lib/AST/AttrImpl.cpp b/clang/lib/AST/AttrImpl.cpp index cfd47e82b04b5..7272ad0de9a2c 100644 --- a/clang/lib/AST/AttrImpl.cpp +++ b/clang/lib/AST/AttrImpl.cpp @@ -233,11 +233,46 @@ void OMPDeclareVariantAttr::printPrettyPragma( OS << ")"; } - auto PrintInteropInfo = [&OS](OMPInteropInfo *Begin, OMPInteropInfo *End) { + auto PrintInteropInfo = [&OS, &Policy](OMPInteropInfo *Begin, + OMPInteropInfo *End) { for (OMPInteropInfo *I = Begin; I != End; ++I) { if (I != Begin) OS << ", "; OS << "interop("; + if (!I->Prefs.empty()) { + OS << "prefer_type("; + if (I->HasPreferAttrs) { + StringRef Sep = ""; + for (const auto &P : I->Prefs) { + OS << Sep << "{"; + if (P.Fr) { + OS << "fr("; + P.Fr->printPretty(OS, nullptr, Policy); + OS << ")"; + } + bool NeedSep = P.Fr != nullptr; + for (Expr *A : P.Attrs) { + if (NeedSep) + OS << ","; + OS << "attr("; + A->printPretty(OS, nullptr, Policy); + OS << ")"; + NeedSep = true; + } + OS << "}"; + Sep = ","; + } + } else { + StringRef Sep = ""; + for (const auto &P : I->Prefs) { + OS << Sep; + if (P.Fr) + P.Fr->printPretty(OS, nullptr, Policy); + Sep = ","; + } + } + OS << "),"; + } OS << getInteropTypeString(I); OS << ")"; } diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 6b4a6017669b2..f466b01975ac5 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -3767,9 +3767,10 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo &InteropInfo, bool IsTargetSync = false; while (Tok.is(tok::identifier)) { - // Currently prefer_type is only allowed with 'init' and it must be first. - bool PreferTypeAllowed = Kind == OMPC_init && InteropInfo.Prefs.empty() && - !IsTarget && !IsTargetSync; + // prefer_type is allowed with 'init' and 'append_args' and must be first. + bool PreferTypeAllowed = (Kind == OMPC_init || Kind == OMPC_append_args) && + InteropInfo.Prefs.empty() && !IsTarget && + !IsTargetSync; if (Tok.getIdentifierInfo()->isStr("target")) { // OpenMP 5.1 [2.15.1, interop Construct, Restrictions] // Each interop-type may be specified on an action-clause at most @@ -3785,6 +3786,10 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo &InteropInfo, ConsumeToken(); } else if (Tok.getIdentifierInfo()->isStr("prefer_type") && PreferTypeAllowed) { + if (Kind == OMPC_append_args && getLangOpts().OpenMP < 60) { + Diag(Tok, diag::err_omp_append_args_prefer_type_60); + HasError = true; + } ConsumeToken(); BalancedDelimiterTracker PT(*this, tok::l_paren, tok::annot_pragma_openmp_end); diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index 76bb0d38d428f..51164744b345a 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -7842,6 +7842,52 @@ SemaOpenMP::checkOpenMPDeclareVariantFunction(SemaOpenMP::DeclGroupPtrTy DG, return std::make_pair(FD, cast<Expr>(DRE)); } +/// Validate prefer_type fr() and attr() arguments in an OMPInteropInfo. +/// fr() must be a string literal or constant integer expression. +/// attr() must be a string literal starting with "ompx_" and containing no +/// commas. Returns true if valid; emits diagnostic and returns false on first +/// error. +static bool checkPreferTypeArgs(SemaOpenMP &S, const OMPInteropInfo &Info) { + auto isDependent = [](const Expr *E) { + return E->isValueDependent() || E->isTypeDependent() || + E->isInstantiationDependent() || + E->containsUnexpandedParameterPack(); + }; + for (const OMPInteropPref &P : Info.Prefs) { + const Expr *E = P.Fr; + if (!E) { + assert(Info.HasPreferAttrs && "null Fr requires OMP 6.0 syntax"); + } else if (!isDependent(E)) { + if (!E->isIntegerConstantExpr(S.getASTContext()) && + !isa<StringLiteral>(E)) { + S.Diag(E->getExprLoc(), diag::err_omp_interop_prefer_type); + return false; + } + } + for (const Expr *A : P.Attrs) { + if (isDependent(A)) + continue; + const auto *SL = dyn_cast<StringLiteral>(A); + if (!SL) { + S.Diag(A->getExprLoc(), diag::err_omp_interop_attr_not_string); + return false; + } + StringRef Str = SL->getString(); + if (!Str.starts_with("ompx_")) { + S.Diag(A->getExprLoc(), diag::err_omp_interop_attr_missing_ompx_prefix) + << Str; + return false; + } + if (Str.contains(',')) { + S.Diag(A->getExprLoc(), diag::err_omp_interop_attr_contains_comma) + << Str; + return false; + } + } + } + return true; +} + void SemaOpenMP::ActOnOpenMPDeclareVariantDirective( FunctionDecl *FD, Expr *VariantRef, OMPTraitInfo &TI, ArrayRef<Expr *> AdjustArgsNothing, @@ -7921,6 +7967,13 @@ void SemaOpenMP::ActOnOpenMPDeclareVariantDirective( } } + // OpenMP 6.0 [16.1.3] Check prefer_type fr()/attr() arguments in + // append_args. + for (const OMPInteropInfo &Info : AppendArgs) { + if (!checkPreferTypeArgs(*this, Info)) + return; + } + auto *NewAttr = OMPDeclareVariantAttr::CreateImplicit( getASTContext(), VariantRef, &TI, const_cast<Expr **>(AdjustArgsNothing.data()), AdjustArgsNothing.size(), @@ -19086,44 +19139,8 @@ OMPClause *SemaOpenMP::ActOnOpenMPInitClause( if (!isValidInteropVariable(SemaRef, InteropVar, VarLoc, OMPC_init)) return nullptr; - // Check prefer_type values. fr() arguments are either string literals or - // constant integral expressions; null Fr is only valid in OMP 6.0. - // attr() arguments must be ext-string-literals with the 'ompx_' prefix - // (OpenMP 6.0 spec, section 16.1.3). - for (const OMPInteropPref &P : InteropInfo.Prefs) { - const Expr *E = P.Fr; - if (!E) { - assert(InteropInfo.HasPreferAttrs && "null Fr requires OMP 6.0 syntax"); - } else if (!E->isValueDependent() && !E->isTypeDependent() && - !E->isInstantiationDependent() && - !E->containsUnexpandedParameterPack()) { - if (!E->isIntegerConstantExpr(getASTContext()) && - !isa<StringLiteral>(E)) { - Diag(E->getExprLoc(), diag::err_omp_interop_prefer_type); - return nullptr; - } - } - for (const Expr *A : P.Attrs) { - if (A->isValueDependent() || A->isTypeDependent() || - A->isInstantiationDependent() || A->containsUnexpandedParameterPack()) - continue; - const auto *SL = dyn_cast<StringLiteral>(A); - if (!SL) { - Diag(A->getExprLoc(), diag::err_omp_interop_attr_not_string); - return nullptr; - } - if (!SL->getString().starts_with("ompx_")) { - Diag(A->getExprLoc(), diag::err_omp_interop_attr_missing_ompx_prefix) - << SL->getString(); - return nullptr; - } - if (SL->getString().contains(',')) { - Diag(A->getExprLoc(), diag::err_omp_interop_attr_contains_comma) - << SL->getString(); - return nullptr; - } - } - } + if (!checkPreferTypeArgs(*this, InteropInfo)) + return nullptr; return OMPInitClause::Create(getASTContext(), InteropVar, InteropInfo, StartLoc, LParenLoc, VarLoc, EndLoc); diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index f1f97ca125f46..9eece865e9c11 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -578,8 +578,25 @@ static void instantiateOMPDeclareVariantAttr( NeedDeviceAddrExprs.push_back(ER.get()); } for (OMPInteropInfo &II : Attr.appendArgs()) { - // When prefer_type is implemented for append_args handle them here too. - AppendArgs.emplace_back(II.IsTarget, II.IsTargetSync); + OMPInteropInfo Info(II.IsTarget, II.IsTargetSync); + Info.HasPreferAttrs = II.HasPreferAttrs; + for (const OMPInteropPref &P : II.Prefs) { + Expr *SubstFr = nullptr; + if (P.Fr) { + ExprResult ER = Subst(P.Fr); + if (ER.isInvalid()) + continue; + SubstFr = ER.get(); + } + llvm::SmallVector<Expr *, 2> SubstAttrs; + for (Expr *A : P.Attrs) { + ExprResult ER = Subst(A); + if (!ER.isInvalid()) + SubstAttrs.push_back(ER.get()); + } + Info.Prefs.emplace_back(SubstFr, std::move(SubstAttrs)); + } + AppendArgs.push_back(Info); } S.OpenMP().ActOnOpenMPDeclareVariantDirective( diff --git a/clang/test/OpenMP/declare_variant_append_args_prefer_type_ast_print.cpp b/clang/test/OpenMP/declare_variant_append_args_prefer_type_ast_print.cpp new file mode 100644 index 0000000000000..b8ffe7c23d516 --- /dev/null +++ b/clang/test/OpenMP/declare_variant_append_args_prefer_type_ast_print.cpp @@ -0,0 +1,79 @@ +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -x c++ -std=c++14 -fsyntax-only -verify %s + +// expected-no-diagnostics + +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -x c++ -std=c++14 -ast-print %s | FileCheck %s --check-prefix=PRINT + +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -x c++ -std=c++14 -emit-pch -o %t %s + +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -x c++ -std=c++14 -include-pch %t -ast-print %s \ +// RUN: | FileCheck %s --check-prefix=PRINT + +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -x c++ -std=c++14 -ast-dump %s \ +// RUN: | FileCheck %s --check-prefix=DUMP + +#ifndef HEADER +#define HEADER + +typedef void *omp_interop_t; + +// Basic append_args with prefer_type (non-template). +void foo_v1(float *A, omp_interop_t IOp); + +// PRINT: #pragma omp declare variant(foo_v1) match(construct={dispatch}) append_args(interop(prefer_type({fr("cuda")}),target)) +#pragma omp declare variant(foo_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr("cuda")}), target)) +void foo(float *A) {} + +// append_args with prefer_type containing fr() + attr(). +void bar_v1(float *A, omp_interop_t IOp); + +// PRINT: #pragma omp declare variant(bar_v1) match(construct={dispatch}) append_args(interop(prefer_type({fr("sycl"),attr("ompx_gpu")}),targetsync)) +#pragma omp declare variant(bar_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr("sycl"), attr("ompx_gpu")}), targetsync)) +void bar(float *A) {} + +// Template: prefer_type with integer expression in fr(). +template <typename T> +void tmpl_v1(T *A, omp_interop_t IOp); + +template <typename T> +void tmpl_bar(T *A); + +// PRINT: #pragma omp declare variant(tmpl_v1<int>) match(construct={dispatch}) append_args(interop(prefer_type({fr(1)}),target)) +#pragma omp declare variant(tmpl_v1<int>) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr(1)}), target)) +void tmpl_bar(int *A) {} + +// Template with dependent expression in fr(). +template <int N> +void dep_v1(float *A, omp_interop_t IOp); + +template <int N> +void dep_bar(float *A); + +// PRINT: #pragma omp declare variant(dep_v1<N>) match(construct={dispatch}) append_args(interop(prefer_type({fr(N)}),target)) +#pragma omp declare variant(dep_v1<N>) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr(N)}), target)) +template <int N> +void dep_bar(float *A) {} + +// DUMP: FunctionDecl{{.*}}dep_bar 'void (float *)' explicit_instantiation_definition +// DUMP: OMPDeclareVariantAttr +// DUMP: IntegerLiteral{{.*}}'int' 4 +template void dep_bar<4>(float *); + +// Multiple prefer_type entries with attr() only. +void multi_v1(float *A, omp_interop_t IOp); + +// PRINT: #pragma omp declare variant(multi_v1) match(construct={dispatch}) append_args(interop(prefer_type({attr("ompx_propA")},{fr(2),attr("ompx_propB")}),target)) +#pragma omp declare variant(multi_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr("ompx_propA")}, {fr(2), attr("ompx_propB")}), target)) +void multi(float *A) {} + +#endif // HEADER diff --git a/clang/test/OpenMP/declare_variant_append_args_prefer_type_messages.cpp b/clang/test/OpenMP/declare_variant_append_args_prefer_type_messages.cpp new file mode 100644 index 0000000000000..7c514a1e24395 --- /dev/null +++ b/clang/test/OpenMP/declare_variant_append_args_prefer_type_messages.cpp @@ -0,0 +1,98 @@ +// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=60 -std=c++11 -o - %s + +typedef void *omp_interop_t; + +void foo_v1(float *A, float *B, omp_interop_t IOp); + +// expected-error@+2 {{prefer_list item must be a string literal or constant integral expression}} +#pragma omp declare variant(foo_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr(1.0)}), target)) +void foo_fr_float(float *A, float *B) {} + +void bar_v1(float *A, omp_interop_t IOp); + +// expected-error@+2 {{attr() argument must be a string literal}} +#pragma omp declare variant(bar_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr(1)}), target)) +void bar_attr_int(float *A) {} + +void baz_v1(float *A, omp_interop_t IOp); + +// expected-error@+2 {{attr() argument 'cuda_prop' must start with the 'ompx_' prefix}} +#pragma omp declare variant(baz_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr("cuda_prop")}), target)) +void baz_attr_no_prefix(float *A) {} + +void qux_v1(float *A, omp_interop_t IOp); + +// expected-error@+2 {{attr() argument 'ompx_a,b' must not contain a comma}} +#pragma omp declare variant(qux_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr("ompx_a,b")}), target)) +void qux_attr_comma(float *A) {} + +// Edge cases for attr() and fr(). +void edge_v1(float *A, omp_interop_t IOp); + +// expected-error@+2 {{attr() argument 'ompx_a,b,c' must not contain a comma}} +#pragma omp declare variant(edge_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr("ompx_a,b,c")}), target)) +void edge_attr_multi_commas(float *A) {} + +void edge_v2(float *A, omp_interop_t IOp); + +// expected-error@+2 {{attr() argument '' must start with the 'ompx_' prefix}} +#pragma omp declare variant(edge_v2) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr("")}), target)) +void edge_attr_empty(float *A) {} + +// Valid cases -- no diagnostics expected. +void valid_v1(float *A, omp_interop_t IOp); + +#pragma omp declare variant(valid_v1) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr("cuda")}), target)) +void valid_fr_string(float *A) {} + +void valid_v2(float *A, omp_interop_t IOp); + +#pragma omp declare variant(valid_v2) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr(1)}), target)) +void valid_fr_int(float *A) {} + +void valid_v3(float *A, omp_interop_t IOp); + +#pragma omp declare variant(valid_v3) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr("ompx_myattr")}), target)) +void valid_attr(float *A) {} + +void valid_v4(float *A, omp_interop_t IOp); + +#pragma omp declare variant(valid_v4) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr("ompx_prop")}), target)) +void valid_attr_only(float *A) {} + +void valid_v5(float *A, omp_interop_t IOp); + +#pragma omp declare variant(valid_v5) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr(1), attr("ompx_prop")}), target)) +void valid_combined(float *A) {} + +void valid_v6(float *A, omp_interop_t IOp); + +#pragma omp declare variant(valid_v6) match(construct={dispatch}) \ + append_args(interop(prefer_type({attr("ompx_")}), target)) +void valid_attr_prefix_only(float *A) {} + +void valid_v7(float *A, omp_interop_t IOp); + +#pragma omp declare variant(valid_v7) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr("")}), target)) +void valid_fr_empty_string(float *A) {} + +// Template case: fr() argument becomes invalid at instantiation. +template <typename T> +void tmpl_v1(T *A, omp_interop_t IOp); + +// expected-error@+2 {{prefer_list item must be a string literal or constant integral expression}} +#pragma omp declare variant(tmpl_v1<int>) match(construct={dispatch}) \ + append_args(interop(prefer_type({fr(1.5)}), target)) +void tmpl_fr_invalid(int *A) {} diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp index 5987cb4a0f16b..65a65f90969dd 100644 --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -905,8 +905,21 @@ namespace { OS << "I != E; ++I) {\n"; OS << " bool IsTarget = Record.readBool();\n"; OS << " bool IsTargetSync = Record.readBool();\n"; - OS << " " << getLowerName() - << ".emplace_back(IsTarget, IsTargetSync);\n"; + OS << " OMPInteropInfo Info(IsTarget, IsTargetSync);\n"; + OS << " Info.HasPreferAttrs = Record.readBool();\n"; + OS << " unsigned prefsSize = Record.readInt();\n"; + OS << " Info.Prefs.reserve(prefsSize);\n"; + OS << " for (unsigned J = 0; J < prefsSize; ++J) {\n"; + OS << " bool hasFr = Record.readBool();\n"; + OS << " Expr *Fr = hasFr ? Record.readExpr() : nullptr;\n"; + OS << " unsigned attrsSize = Record.readInt();\n"; + OS << " llvm::SmallVector<Expr *, 2> Attrs;\n"; + OS << " Attrs.reserve(attrsSize);\n"; + OS << " for (unsigned K = 0; K < attrsSize; ++K)\n"; + OS << " Attrs.push_back(Record.readExpr());\n"; + OS << " Info.Prefs.emplace_back(Fr, std::move(Attrs));\n"; + OS << " }\n"; + OS << " " << getLowerName() << ".push_back(Info);\n"; OS << " }\n"; } @@ -917,6 +930,41 @@ namespace { << getLowerName() << "_end(); I != E; ++I) {\n"; OS << " Record.writeBool(I->IsTarget);\n"; OS << " Record.writeBool(I->IsTargetSync);\n"; + OS << " Record.writeBool(I->HasPreferAttrs);\n"; + OS << " Record.push_back(I->Prefs.size());\n"; + OS << " for (auto &P : I->Prefs) {\n"; + OS << " Record.writeBool(P.Fr != nullptr);\n"; + OS << " if (P.Fr) Record.AddStmt(P.Fr);\n"; + OS << " Record.push_back(P.Attrs.size());\n"; + OS << " for (Expr *A : P.Attrs) Record.AddStmt(A);\n"; + OS << " }\n"; + OS << " }\n"; + } + + void writeASTVisitorTraversal(raw_ostream &OS) const override { + OS << " {\n"; + OS << " OMPInteropInfo *I = A->" << getLowerName() << "_begin();\n"; + OS << " " << getType() << " *E = A->" << getLowerName() << "_end();\n"; + OS << " for (; I != E; ++I) {\n"; + OS << " for (auto &P : I->Prefs) {\n"; + OS << " if (P.Fr && !getDerived().TraverseStmt(P.Fr))\n"; + OS << " return false;\n"; + OS << " for (Expr *A : P.Attrs)\n"; + OS << " if (!getDerived().TraverseStmt(A))\n"; + OS << " return false;\n"; + OS << " }\n"; + OS << " }\n"; + OS << " }\n"; + } + + void writeDumpChildren(raw_ostream &OS) const override { + OS << " for (" << getAttrName() << "Attr::" << getLowerName() + << "_iterator I = SA->" << getLowerName() << "_begin(), E = SA->" + << getLowerName() << "_end(); I != E; ++I) {\n"; + OS << " for (auto &P : I->Prefs) {\n"; + OS << " if (P.Fr) Visit(P.Fr);\n"; + OS << " for (Expr *A : P.Attrs) Visit(A);\n"; + OS << " }\n"; OS << " }\n"; } }; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
