From a734ab9808658310b63e5a945f21837076dd14c7 Mon Sep 17 00:00:00 2001 From: Faisal Vali Date: Sat, 26 Mar 2016 16:11:37 +0000 Subject: [PATCH] [Cxx1z-constexpr-lambda-P0170R1] Support parsing of constexpr specifier (and its inference) on lambda expressions Support the constexpr specifier on lambda expressions - and support its inference from the lambda call operator's body. i.e. auto L = [] () constexpr { return 5; }; static_assert(L() == 5); // OK auto Implicit = [] (auto a) { return a; }; static_assert(Implicit(5) == 5); We do not support evaluation of lambda's within constant expressions just yet. Implementation Strategy: - teach ParseLambdaExpressionAfterIntroducer to expect a constexpr specifier and mark the invented function call operator's declarator's decl-specifier with it; Have it emit fixits for multiple decl-specifiers (mutable or constexpr) in this location. - for cases where constexpr is not explicitly specified, have buildLambdaExpr check whether the invented function call operator satisfies the requirements of a constexpr function, by calling CheckConstexprFunctionDecl/Body. Much obliged to Richard Smith for his patience and his care, in ensuring the code is clang-worthy. llvm-svn: 264513 --- clang/include/clang/Basic/DiagnosticASTKinds.td | 6 +++ clang/include/clang/Basic/DiagnosticParseKinds.td | 11 +++- clang/include/clang/Sema/Sema.h | 3 +- clang/lib/AST/ExprConstant.cpp | 18 ++++++- clang/lib/Parse/ParseExprCXX.cpp | 65 +++++++++++++++++++++-- clang/lib/Sema/SemaLambda.cpp | 21 ++++++-- clang/lib/Sema/TreeTransform.h | 4 +- clang/test/Parser/cxx1z-constexpr-lambdas.cpp | 31 +++++++++++ clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp | 36 +++++++++++++ 9 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 clang/test/Parser/cxx1z-constexpr-lambdas.cpp create mode 100644 clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 0b37030..d2b0618 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -153,6 +153,12 @@ def warn_integer_constant_overflow : Warning< "overflow in expression; result is %0 with type %1">, InGroup>; +// This is a temporary diagnostic, and shall be removed once our +// implementation is complete, and like the preceding constexpr notes belongs +// in Sema. +def note_unimplemented_constexpr_lambda_feature_ast : Note< + "unimplemented constexpr lambda feature: %0 (coming soon!)">; + // inline asm related. let CategoryName = "Inline Assembly Issue" in { def err_asm_invalid_escape : Error< diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 1116f32..2f30e43 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -765,11 +765,20 @@ def warn_cxx98_compat_lambda : Warning< InGroup, DefaultIgnore; def err_lambda_missing_parens : Error< "lambda requires '()' before %select{'mutable'|return type|" - "attribute specifier}0">; + "attribute specifier|'constexpr'}0">; +def err_lambda_decl_specifier_repeated : Error< + "%select{'mutable'|'constexpr'}0 cannot appear multiple times in a lambda declarator">; // C++1z lambda expressions def err_expected_star_this_capture : Error< "expected 'this' following '*' in lambda capture list">; +// C++1z constexpr lambda expressions +def warn_cxx14_compat_constexpr_on_lambda : Warning< + "constexpr on lambda expressions is incompatible with C++ standards before C++1z">, + InGroup, DefaultIgnore; +def ext_constexpr_on_lambda_cxx1z : ExtWarn< + "'constexpr' on lambda expressions is a C++1z extension">, InGroup; + // Availability attribute def err_expected_version : Error< "expected a version of the form 'major[.minor[.subminor]]'">; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 9c48e0e..c003f72 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5013,7 +5013,8 @@ public: SourceRange IntroducerRange, TypeSourceInfo *MethodType, SourceLocation EndLoc, - ArrayRef Params); + ArrayRef Params, + bool IsConstexprSpecified); /// \brief Endow the lambda scope info with the relevant properties. void buildLambdaScope(sema::LambdaScopeInfo *LSI, diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 262c97a..3969f31 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -36,6 +36,7 @@ #include "clang/AST/APValue.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" +#include "clang/AST/ASTLambda.h" #include "clang/AST/CharUnits.h" #include "clang/AST/Expr.h" #include "clang/AST/RecordLayout.h" @@ -2045,7 +2046,22 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // If this is a local variable, dig out its value. if (Frame) { Result = Frame->getTemporary(VD); - assert(Result && "missing value for local variable"); + if (!Result) { + // Assume variables referenced within a lambda's call operator that were + // not declared within the call operator are captures and during checking + // of a potential constant expression, assume they are unknown constant + // expressions. + assert(isLambdaCallOperator(Frame->Callee) && + (VD->getDeclContext() != Frame->Callee || VD->isInitCapture()) && + "missing value for local variable"); + if (Info.checkingPotentialConstantExpression()) + return false; + // FIXME: implement capture evaluation during constant expr evaluation. + Info.Diag(E->getLocStart(), + diag::note_unimplemented_constexpr_lambda_feature_ast) + << "captures not currently allowed"; + return false; + } return true; } diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index 8e5f35a..afed9b1 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -1050,6 +1050,58 @@ bool Parser::TryParseLambdaIntroducer(LambdaIntroducer &Intro) { return false; } +static void +tryConsumeMutableOrConstexprToken(Parser &P, SourceLocation &MutableLoc, + SourceLocation &ConstexprLoc, + SourceLocation &DeclEndLoc) { + assert(MutableLoc.isInvalid()); + assert(ConstexprLoc.isInvalid()); + // Consume constexpr-opt mutable-opt in any sequence, and set the DeclEndLoc + // to the final of those locations. Emit an error if we have multiple + // copies of those keywords and recover. + + while (true) { + switch (P.getCurToken().getKind()) { + case tok::kw_mutable: { + if (MutableLoc.isValid()) { + P.Diag(P.getCurToken().getLocation(), + diag::err_lambda_decl_specifier_repeated) + << 0 << FixItHint::CreateRemoval(P.getCurToken().getLocation()); + } + MutableLoc = P.ConsumeToken(); + DeclEndLoc = MutableLoc; + break /*switch*/; + } + case tok::kw_constexpr: + if (ConstexprLoc.isValid()) { + P.Diag(P.getCurToken().getLocation(), + diag::err_lambda_decl_specifier_repeated) + << 1 << FixItHint::CreateRemoval(P.getCurToken().getLocation()); + } + ConstexprLoc = P.ConsumeToken(); + DeclEndLoc = ConstexprLoc; + break /*switch*/; + default: + return; + } + } +} + +static void +addConstexprToLambdaDeclSpecifier(Parser &P, SourceLocation ConstexprLoc, + DeclSpec &DS) { + if (ConstexprLoc.isValid()) { + P.Diag(ConstexprLoc, !P.getLangOpts().CPlusPlus1z + ? diag::ext_constexpr_on_lambda_cxx1z + : diag::warn_cxx14_compat_constexpr_on_lambda); + const char *PrevSpec = nullptr; + unsigned DiagID = 0; + DS.SetConstexprSpec(ConstexprLoc, PrevSpec, DiagID); + assert(PrevSpec == nullptr && DiagID == 0 && + "Constexpr cannot have been set previously!"); + } +} + /// ParseLambdaExpressionAfterIntroducer - Parse the rest of a lambda /// expression. ExprResult Parser::ParseLambdaExpressionAfterIntroducer( @@ -1108,10 +1160,13 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer( // compatible with MSVC. MaybeParseMicrosoftDeclSpecs(Attr, &DeclEndLoc); - // Parse 'mutable'[opt]. + // Parse mutable-opt and/or constexpr-opt, and update the DeclEndLoc. SourceLocation MutableLoc; - if (TryConsumeToken(tok::kw_mutable, MutableLoc)) - DeclEndLoc = MutableLoc; + SourceLocation ConstexprLoc; + tryConsumeMutableOrConstexprToken(*this, MutableLoc, ConstexprLoc, + DeclEndLoc); + + addConstexprToLambdaDeclSpecifier(*this, ConstexprLoc, DS); // Parse exception-specification[opt]. ExceptionSpecificationType ESpecType = EST_None; @@ -1169,7 +1224,8 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer( LParenLoc, FunLocalRangeEnd, D, TrailingReturnType), Attr, DeclEndLoc); - } else if (Tok.isOneOf(tok::kw_mutable, tok::arrow, tok::kw___attribute) || + } else if (Tok.isOneOf(tok::kw_mutable, tok::arrow, tok::kw___attribute, + tok::kw_constexpr) || (Tok.is(tok::l_square) && NextToken().is(tok::l_square))) { // It's common to forget that one needs '()' before 'mutable', an attribute // specifier, or the result type. Deal with this. @@ -1179,6 +1235,7 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer( case tok::arrow: TokKind = 1; break; case tok::kw___attribute: case tok::l_square: TokKind = 2; break; + case tok::kw_constexpr: TokKind = 3; break; default: llvm_unreachable("Unknown token kind"); } diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp index 1dd37bd..88754e2 100644 --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -355,7 +355,8 @@ CXXMethodDecl *Sema::startLambdaDefinition(CXXRecordDecl *Class, SourceRange IntroducerRange, TypeSourceInfo *MethodTypeInfo, SourceLocation EndLoc, - ArrayRef Params) { + ArrayRef Params, + const bool IsConstexprSpecified) { QualType MethodType = MethodTypeInfo->getType(); TemplateParameterList *TemplateParams = getGenericLambdaTemplateParameterList(getCurLambda(), *this); @@ -392,7 +393,7 @@ CXXMethodDecl *Sema::startLambdaDefinition(CXXRecordDecl *Class, MethodType, MethodTypeInfo, SC_None, /*isInline=*/true, - /*isConstExpr=*/false, + IsConstexprSpecified, EndLoc); Method->setAccess(AS_public); @@ -879,8 +880,9 @@ void Sema::ActOnStartOfLambdaDefinition(LambdaIntroducer &Intro, CXXRecordDecl *Class = createLambdaClosureType(Intro.Range, MethodTyInfo, KnownDependent, Intro.Default); - CXXMethodDecl *Method = startLambdaDefinition(Class, Intro.Range, - MethodTyInfo, EndLoc, Params); + CXXMethodDecl *Method = + startLambdaDefinition(Class, Intro.Range, MethodTyInfo, EndLoc, Params, + ParamInfo.getDeclSpec().isConstexprSpecified()); if (ExplicitParams) CheckCXXDefaultArguments(Method); @@ -1600,6 +1602,17 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc, CaptureInits, ArrayIndexVars, ArrayIndexStarts, EndLoc, ContainsUnexpandedParameterPack); + // If the lambda expression's call operator is not explicitly marked constexpr + // and we are not in a dependent context, analyze the call operator to infer + // its constexpr-ness, supressing diagnostics while doing so. + if (getLangOpts().CPlusPlus1z && !CallOperator->isInvalidDecl() && + !CallOperator->isConstexpr() && + !Class->getDeclContext()->isDependentContext()) { + TentativeAnalysisScope DiagnosticScopeGuard(*this); + CallOperator->setConstexpr( + CheckConstexprFunctionDecl(CallOperator) && + CheckConstexprFunctionBody(CallOperator, CallOperator->getBody())); + } if (!CurContext->isDependentContext()) { switch (ExprEvalContexts.back().Context) { diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 7ed52ebb..58cf67c 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -10054,7 +10054,9 @@ TreeTransform::TransformLambdaExpr(LambdaExpr *E) { CXXMethodDecl *NewCallOperator = getSema().startLambdaDefinition( Class, E->getIntroducerRange(), NewCallOpTSI, E->getCallOperator()->getLocEnd(), - NewCallOpTSI->getTypeLoc().castAs().getParams()); + NewCallOpTSI->getTypeLoc().castAs().getParams(), + E->getCallOperator()->isConstexpr()); + LSI->CallOperator = NewCallOperator; getDerived().transformAttrs(E->getCallOperator(), NewCallOperator); diff --git a/clang/test/Parser/cxx1z-constexpr-lambdas.cpp b/clang/test/Parser/cxx1z-constexpr-lambdas.cpp new file mode 100644 index 0000000..ea000e3 --- /dev/null +++ b/clang/test/Parser/cxx1z-constexpr-lambdas.cpp @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -std=c++1z %s -verify +// RUN: %clang_cc1 -std=c++14 %s -verify +// RUN: %clang_cc1 -std=c++11 %s -verify + + +auto XL0 = [] constexpr { }; //expected-error{{requires '()'}} expected-error{{expected body}} +auto XL1 = [] () mutable + mutable //expected-error{{cannot appear multiple times}} + mutable { }; //expected-error{{cannot appear multiple times}} + +#if __cplusplus > 201402L +auto XL2 = [] () constexpr mutable constexpr { }; //expected-error{{cannot appear multiple times}} +auto L = []() mutable constexpr { }; +auto L2 = []() constexpr { }; +auto L4 = []() constexpr mutable { }; +auto XL16 = [] () constexpr + mutable + constexpr //expected-error{{cannot appear multiple times}} + mutable //expected-error{{cannot appear multiple times}} + mutable //expected-error{{cannot appear multiple times}} + constexpr //expected-error{{cannot appear multiple times}} + constexpr //expected-error{{cannot appear multiple times}} + { }; + +#else +auto L = []() mutable constexpr {return 0; }; //expected-warning{{is a C++1z extension}} +auto L2 = []() constexpr { return 0;};//expected-warning{{is a C++1z extension}} +auto L4 = []() constexpr mutable { return 0; }; //expected-warning{{is a C++1z extension}} +#endif + + diff --git a/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp b/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp new file mode 100644 index 0000000..8e7657f --- /dev/null +++ b/clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp @@ -0,0 +1,36 @@ +// RUN: %clang_cc1 -std=c++1z -verify -fsyntax-only -fblocks %s +// RUN: %clang_cc1 -std=c++1z -verify -fsyntax-only -fblocks -fdelayed-template-parsing %s +// RUN: %clang_cc1 -std=c++1z -verify -fsyntax-only -fblocks -fms-extensions %s +// RUN: %clang_cc1 -std=c++1z -verify -fsyntax-only -fblocks -fdelayed-template-parsing -fms-extensions %s + +namespace test_constexpr_checking { + +namespace ns1 { + struct NonLit { ~NonLit(); }; //expected-note{{not literal}} + auto L = [](NonLit NL) constexpr { }; //expected-error{{not a literal type}} +} // end ns1 + +namespace ns2 { + auto L = [](int I) constexpr { asm("non-constexpr"); }; //expected-error{{not allowed in constexpr function}} +} // end ns1 + +} // end ns test_constexpr_checking + +namespace test_constexpr_call { + +namespace ns1 { + auto L = [](int I) { return I; }; + static_assert(L(3) == 3); +} // end ns1 +namespace ns2 { + auto L = [](auto a) { return a; }; + static_assert(L(3) == 3); + static_assert(L(3.14) == 3.14); +} +namespace ns3 { + auto L = [](auto a) { asm("non-constexpr"); return a; }; //expected-note{{declared here}} + constexpr int I = //expected-error{{must be initialized by a constant expression}} + L(3); //expected-note{{non-constexpr function}} +} + +} // end ns test_constexpr_call \ No newline at end of file -- 2.7.4