From f44d2a8a3e05ef27e72ea8f2e07e1a7266e4b4c9 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 21 May 2013 22:21:19 +0000 Subject: [PATCH] PR16094: I should have known Obj-C init-capture disambiguation couldn't be *that* easy... Try a bit harder to disambiguate. This is mostly straightforward, but for =-style initializers, we actually need to know where an expression ends: [foo = bar baz] is a message send, whereas [foo = bar + baz] is a lambda-introducer. Handle this by parsing the expression eagerly, and replacing it with an annotation token. By chance, we use the *exact same* parsing rules in both cases (except that we need to assume we're inside a message send for the parse, to turn off various forms of inapplicable error recovery). llvm-svn: 182432 --- clang/include/clang/Lex/Preprocessor.h | 7 ++ clang/include/clang/Parse/Parser.h | 3 +- clang/lib/Parse/ParseExprCXX.cpp | 80 +++++++++++++++++-- .../Parser/objcxx0x-lambda-expressions.mm | 16 +++- 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 89cb696f62a5..e70eaa8fc860 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -828,6 +828,13 @@ public: AnnotatePreviousCachedTokens(Tok); } + /// Get the location of the last cached token, suitable for setting the end + /// location of an annotation token. + SourceLocation getLastCachedTokenLocation() const { + assert(CachedLexPos != 0); + return CachedTokens[CachedLexPos-1].getLocation(); + } + /// \brief Replace the last token with an annotation token. /// /// Like AnnotateCachedTokens(), this routine replaces an diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 1a1d6d2ae1a4..5a5b0669b205 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -1329,7 +1329,8 @@ private: // [...] () -> type {...} ExprResult ParseLambdaExpression(); ExprResult TryParseLambdaExpression(); - Optional ParseLambdaIntroducer(LambdaIntroducer &Intro); + Optional ParseLambdaIntroducer(LambdaIntroducer &Intro, + bool *SkippedInits = 0); bool TryParseLambdaIntroducer(LambdaIntroducer &Intro); ExprResult ParseLambdaExpressionAfterIntroducer( LambdaIntroducer &Intro); diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index 8bc9a796fe11..9704b98d48ee 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -679,10 +679,17 @@ ExprResult Parser::TryParseLambdaExpression() { return ParseLambdaExpressionAfterIntroducer(Intro); } -/// ParseLambdaExpression - Parse a lambda introducer. -/// -/// Returns a DiagnosticID if it hit something unexpected. -Optional Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro) { +/// \brief Parse a lambda introducer. +/// \param Intro A LambdaIntroducer filled in with information about the +/// contents of the lambda-introducer. +/// \param SkippedInits If non-null, we are disambiguating between an Obj-C +/// message send and a lambda expression. In this mode, we will +/// sometimes skip the initializers for init-captures and not fully +/// populate \p Intro. This flag will be set to \c true if we do so. +/// \return A DiagnosticID if it hit something unexpected. The location for +/// for the diagnostic is that of the current token. +Optional Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro, + bool *SkippedInits) { typedef Optional DiagResult; assert(Tok.is(tok::l_square) && "Lambda expressions begin with '['."); @@ -781,7 +788,10 @@ Optional Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro) { ExprVector Exprs; CommaLocsTy Commas; - if (ParseExpressionList(Exprs, Commas)) { + if (SkippedInits) { + Parens.skipToEnd(); + *SkippedInits = true; + } else if (ParseExpressionList(Exprs, Commas)) { Parens.skipToEnd(); Init = ExprError(); } else { @@ -794,7 +804,53 @@ Optional Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro) { if (Tok.is(tok::equal)) ConsumeToken(); - Init = ParseInitializer(); + if (!SkippedInits) + Init = ParseInitializer(); + else if (Tok.is(tok::l_brace)) { + BalancedDelimiterTracker Braces(*this, tok::l_brace); + Braces.consumeOpen(); + Braces.skipToEnd(); + *SkippedInits = true; + } else { + // We're disambiguating this: + // + // [..., x = expr + // + // We need to find the end of the following expression in order to + // determine whether this is an Obj-C message send's receiver, or a + // lambda init-capture. + // + // Parse the expression to find where it ends, and annotate it back + // onto the tokens. We would have parsed this expression the same way + // in either case: both the RHS of an init-capture and the RHS of an + // assignment expression are parsed as an initializer-clause, and in + // neither case can anything be added to the scope between the '[' and + // here. + // + // FIXME: This is horrible. Adding a mechanism to skip an expression + // would be much cleaner. + // FIXME: If there is a ',' before the next ']' or ':', we can skip to + // that instead. (And if we see a ':' with no matching '?', we can + // classify this as an Obj-C message send.) + SourceLocation StartLoc = Tok.getLocation(); + InMessageExpressionRAIIObject MaybeInMessageExpression(*this, true); + Init = ParseInitializer(); + + if (Tok.getLocation() != StartLoc) { + // Back out the lexing of the token after the initializer. + PP.RevertCachedTokens(1); + + // Replace the consumed tokens with an appropriate annotation. + Tok.setLocation(StartLoc); + Tok.setKind(tok::annot_primary_expr); + setExprAnnotation(Tok, Init); + Tok.setAnnotationEndLoc(PP.getLastCachedTokenLocation()); + PP.AnnotateCachedTokens(Tok); + + // Consume the annotated initializer. + ConsumeToken(); + } + } } else if (Tok.is(tok::ellipsis)) EllipsisLoc = ConsumeToken(); } @@ -814,13 +870,23 @@ Optional Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro) { bool Parser::TryParseLambdaIntroducer(LambdaIntroducer &Intro) { TentativeParsingAction PA(*this); - Optional DiagID(ParseLambdaIntroducer(Intro)); + bool SkippedInits = false; + Optional DiagID(ParseLambdaIntroducer(Intro, &SkippedInits)); if (DiagID) { PA.Revert(); return true; } + if (SkippedInits) { + // Parse it again, but this time parse the init-captures too. + PA.Revert(); + Intro = LambdaIntroducer(); + DiagID = ParseLambdaIntroducer(Intro); + assert(!DiagID && "parsing lambda-introducer failed on reparse"); + return false; + } + PA.Commit(); return false; } diff --git a/clang/test/Parser/objcxx0x-lambda-expressions.mm b/clang/test/Parser/objcxx0x-lambda-expressions.mm index b2a75f2edbb6..905bd6b1e8b4 100644 --- a/clang/test/Parser/objcxx0x-lambda-expressions.mm +++ b/clang/test/Parser/objcxx0x-lambda-expressions.mm @@ -1,9 +1,10 @@ // RUN: %clang_cc1 -fsyntax-only -verify -Wno-unused-value -std=c++11 %s class C { + id get(int); void f() { - int foo, bar; + int foo, bar, baz; // fail to parse as a lambda introducer, so we get objc message parsing errors instead [foo,+] {}; // expected-error {{expected expression}} @@ -24,9 +25,18 @@ class C { [foo = {bar}] () {}; // expected-error {{}} [foo(bar) baz] () {}; // expected-error {{called object type 'int' is not a function}} + [foo(bar), baz] () {}; // ok - // FIXME: These are some appalling diagnostics. - [foo = bar baz]; // expected-error {{missing '['}} expected-warning 2{{receiver type 'int'}} expected-warning 2{{instance method '-baz'}} + [foo = bar baz]; // expected-warning {{receiver type 'int'}} expected-warning {{instance method '-baz'}} + + [get(bar) baz]; // expected-warning {{instance method '-baz'}} + [get(bar), baz]; // expected-error {{expected body of lambda}} + + [foo = bar ++ baz]; // expected-warning {{receiver type 'int'}} expected-warning {{instance method '-baz'}} + [foo = bar + baz]; // expected-error {{expected body of lambda}} + [foo = { bar, baz }]; // expected-error {{}} expected-error {{expected body of lambda}} + [foo = { bar } baz ]; // expected-warning {{receiver type 'int'}} expected-warning {{instance method '-baz'}} + [foo = { bar }, baz ]; // expected-error {{}} expected-error {{expected body of lambda}} } };