]> granicus.if.org Git - clang/commitdiff
Add support for editor placeholders to Clang
authorAlex Lorenz <arphaman@gmail.com>
Wed, 19 Apr 2017 08:58:56 +0000 (08:58 +0000)
committerAlex Lorenz <arphaman@gmail.com>
Wed, 19 Apr 2017 08:58:56 +0000 (08:58 +0000)
This commit teaches Clang to recognize editor placeholders that are produced
when an IDE like Xcode inserts a code-completion result that includes a
placeholder. Now when the lexer sees a placeholder token, it emits an
'editor placeholder in source file' error and creates an identifier token
that represents the placeholder. The parser/sema can now recognize the
placeholders and can suppress the diagnostics related to the placeholders. This
ensures that live issues in an IDE like Xcode won't get spurious diagnostics
related to placeholders.

This commit also adds a new compiler option named '-fallow-editor-placeholders'
that silences the 'editor placeholder in source file' error. This is useful
for an IDE like Xcode as we don't want to display those errors in live issues.

rdar://31581400

Differential Revision: https://reviews.llvm.org/D32081

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@300667 91177308-0d34-0410-b5e6-96231b3b80d8

16 files changed:
include/clang/Basic/DiagnosticLexKinds.td
include/clang/Basic/IdentifierTable.h
include/clang/Basic/LangOptions.def
include/clang/Driver/Options.td
include/clang/Lex/Lexer.h
include/clang/Lex/Token.h
lib/Driver/ToolChains/Clang.cpp
lib/Frontend/CompilerInvocation.cpp
lib/Lex/Lexer.cpp
lib/Parse/Parser.cpp
lib/Sema/SemaCXXScopeSpec.cpp
lib/Sema/SemaDecl.cpp
lib/Sema/SemaExpr.cpp
test/Driver/clang_f_opts.c
test/Parser/editor-placeholder-recovery.cpp [new file with mode: 0644]
test/Parser/placeholder-recovery.m

index b5b5acbc7809280682fe47c294d085bac6f0b112..cf33d5fba3d774e1100b6ad211db999e2550ce40 100644 (file)
@@ -242,6 +242,7 @@ def warn_bad_character_encoding : ExtWarn<
   "illegal character encoding in character literal">,
   InGroup<InvalidSourceEncoding>;
 def err_lexing_string : Error<"failure when lexing a string">;
+def err_placeholder_in_source : Error<"editor placeholder in source file">;
 
 
 //===----------------------------------------------------------------------===//
index a5fd14104d3c0a1b98de7768ffbdf94c25284ead..9b1ba4a98e6fd948ef18f86ecc968b9991e1b5c1 100644 (file)
@@ -355,6 +355,19 @@ public:
       RecomputeNeedsHandleIdentifier();
   }
 
+  /// Return true if this identifier is an editor placeholder.
+  ///
+  /// Editor placeholders are produced by the code-completion engine and are
+  /// represented as characters between '<#' and '#>' in the source code. An
+  /// example of auto-completed call with a placeholder parameter is shown
+  /// below:
+  /// \code
+  ///   function(<#int x#>);
+  /// \endcode
+  bool isEditorPlaceholder() const {
+    return getName().startswith("<#") && getName().endswith("#>");
+  }
+
   /// \brief Provide less than operator for lexicographical sorting.
   bool operator<(const IdentifierInfo &RHS) const {
     return getName() < RHS.getName();
index c8e1972997547ece06eb4a6eb608ab8b0102fa6b..6ae34a89fe2853cf1fb087e2193399a5840d8bf7 100644 (file)
@@ -266,6 +266,8 @@ LANGOPT(SanitizeAddressFieldPadding, 2, 0, "controls how aggressive is ASan "
 
 LANGOPT(XRayInstrument, 1, 0, "controls whether to do XRay instrumentation")
 
+LANGOPT(AllowEditorPlaceholders, 1, 0, "allow editor placeholders in source")
+
 #undef LANGOPT
 #undef COMPATIBLE_LANGOPT
 #undef BENIGN_LANGOPT
index c6c9d5d8891d642ddd6f76e58315f84f419bb2d3..6f1ccfb00172014d39243bc51d6fef9187d4fef3 100644 (file)
@@ -1487,6 +1487,12 @@ def fstrict_return : Flag<["-"], "fstrict-return">, Group<f_Group>,
 def fno_strict_return : Flag<["-"], "fno-strict-return">, Group<f_Group>,
   Flags<[CC1Option]>;
 
+def fallow_editor_placeholders : Flag<["-"], "fallow-editor-placeholders">,
+  Group<f_Group>, Flags<[CC1Option]>,
+  HelpText<"Treat editor placeholders as valid source code">;
+def fno_allow_editor_placeholders : Flag<["-"],
+  "fno-allow-editor-placeholders">, Group<f_Group>;
+
 def fdebug_types_section: Flag <["-"], "fdebug-types-section">, Group<f_Group>,
   Flags<[CC1Option]>, HelpText<"Place debug types in their own section (ELF Only)">;
 def fno_debug_types_section: Flag<["-"], "fno-debug-types-section">, Group<f_Group>,
index 830c25a2e4d29fc6fcead442fe7b9e35ace03f3e..6ac6316d1248b39d7efd6fbd94894fe444deee35 100644 (file)
@@ -638,6 +638,8 @@ private:
   bool IsStartOfConflictMarker(const char *CurPtr);
   bool HandleEndOfConflictMarker(const char *CurPtr);
 
+  bool lexEditorPlaceholder(Token &Result, const char *CurPtr);
+
   bool isCodeCompletionPoint(const char *CurPtr) const;
   void cutOffLexing() { BufferPtr = BufferEnd; }
 
index 4393e205ffaf53d0727ba2319fec0d298de4c1aa..02a1fef70f2bc76adbf9233e31774fd1a9cae672 100644 (file)
@@ -84,6 +84,7 @@ public:
     StringifiedInMacro = 0x100, // This string or character literal is formed by
                                 // macro stringizing or charizing operator.
     CommaAfterElided = 0x200, // The comma following this token was elided (MS).
+    IsEditorPlaceholder = 0x400, // This identifier is a placeholder.
   };
 
   tok::TokenKind getKind() const { return Kind; }
@@ -298,6 +299,13 @@ public:
 
   /// Returns true if the comma after this token was elided.
   bool commaAfterElided() const { return getFlag(CommaAfterElided); }
+
+  /// Returns true if this token is an editor placeholder.
+  ///
+  /// Editor placeholders are produced by the code-completion engine and are
+  /// represented as characters between '<#' and '#>' in the source code. The
+  /// lexer uses identifier tokens to represent placeholders.
+  bool isEditorPlaceholder() const { return getFlag(IsEditorPlaceholder); }
 };
 
 /// \brief Information about the conditional stack (\#if directives)
index b6887b875848fe700798dd002f431b84d631a451..49708e7d7242b07e630d7fdbbac3541023bd9881 100644 (file)
@@ -2306,6 +2306,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   if (!Args.hasFlag(options::OPT_fstrict_return, options::OPT_fno_strict_return,
                     true))
     CmdArgs.push_back("-fno-strict-return");
+  if (Args.hasFlag(options::OPT_fallow_editor_placeholders,
+                   options::OPT_fno_allow_editor_placeholders, false))
+    CmdArgs.push_back("-fallow-editor-placeholders");
   if (Args.hasFlag(options::OPT_fstrict_vtable_pointers,
                    options::OPT_fno_strict_vtable_pointers,
                    false))
index 05db4577ab9987e13faf8dfb7baf7ea83bc61fb1..0e0eb40eb33425f93a004298ada4cc304cf5aea5 100644 (file)
@@ -2322,6 +2322,9 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK,
       Args.getAllArgValues(OPT_fxray_always_instrument);
   Opts.XRayNeverInstrumentFiles =
       Args.getAllArgValues(OPT_fxray_never_instrument);
+
+  // -fallow-editor-placeholders
+  Opts.AllowEditorPlaceholders = Args.hasArg(OPT_fallow_editor_placeholders);
 }
 
 static void ParsePreprocessorArgs(PreprocessorOptions &Opts, ArgList &Args,
index dc911ef91b6caeb731cad8ddeb1f2a5ae9b27f15..003c9b5eed1b52a2032d82713ed53b9cbd531085 100644 (file)
@@ -2716,6 +2716,37 @@ bool Lexer::HandleEndOfConflictMarker(const char *CurPtr) {
   return false;
 }
 
+static const char *findPlaceholderEnd(const char *CurPtr,
+                                      const char *BufferEnd) {
+  if (CurPtr == BufferEnd)
+    return nullptr;
+  BufferEnd -= 1; // Scan until the second last character.
+  for (; CurPtr != BufferEnd; ++CurPtr) {
+    if (CurPtr[0] == '#' && CurPtr[1] == '>')
+      return CurPtr + 2;
+  }
+  return nullptr;
+}
+
+bool Lexer::lexEditorPlaceholder(Token &Result, const char *CurPtr) {
+  assert(CurPtr[-1] == '<' && CurPtr[0] == '#' && "Not a placeholder!");
+  if (!PP || LexingRawMode)
+    return false;
+  const char *End = findPlaceholderEnd(CurPtr + 1, BufferEnd);
+  if (!End)
+    return false;
+  const char *Start = CurPtr - 1;
+  if (!LangOpts.AllowEditorPlaceholders)
+    Diag(Start, diag::err_placeholder_in_source);
+  Result.startToken();
+  FormTokenWithChars(Result, End, tok::raw_identifier);
+  Result.setRawIdentifierData(Start);
+  PP->LookUpIdentifierInfo(Result);
+  Result.setFlag(Token::IsEditorPlaceholder);
+  BufferPtr = End;
+  return true;
+}
+
 bool Lexer::isCodeCompletionPoint(const char *CurPtr) const {
   if (PP && PP->isCodeCompletionEnabled()) {
     SourceLocation Loc = FileLoc.getLocWithOffset(CurPtr-BufferStart);
@@ -3473,6 +3504,8 @@ LexNextToken:
     } else if (LangOpts.Digraphs && Char == '%') {     // '<%' -> '{'
       CurPtr = ConsumeChar(CurPtr, SizeTmp, Result);
       Kind = tok::l_brace;
+    } else if (Char == '#' && lexEditorPlaceholder(Result, CurPtr)) {
+      return true;
     } else {
       Kind = tok::less;
     }
index 265c12d7d5e75e32a9b352f03e6d4112fba3d51f..edbfc636bc46866ea616500398b5c70e36938c31 100644 (file)
@@ -851,6 +851,10 @@ Parser::ParseExternalDeclaration(ParsedAttributesWithRange &attrs,
 
   default:
   dont_know:
+    if (Tok.isEditorPlaceholder()) {
+      ConsumeToken();
+      return nullptr;
+    }
     // We can't tell whether this is a function-definition or declaration yet.
     return ParseDeclarationOrFunctionDefinition(attrs, DS);
   }
@@ -1679,6 +1683,8 @@ bool Parser::TryAnnotateTypeOrScopeToken() {
           return false;
         }
       }
+      if (Tok.isEditorPlaceholder())
+        return true;
 
       Diag(Tok.getLocation(), diag::err_expected_qualified_after_typename);
       return true;
index 57471de78d3eb61f5a4ad7a56a16c41ea539a41e..6da4d2a26191b2587344d77d4d919cade2a72e6d 100644 (file)
@@ -480,6 +480,8 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
                                        bool ErrorRecoveryLookup,
                                        bool *IsCorrectedToColon,
                                        bool OnlyNamespace) {
+  if (IdInfo.Identifier->isEditorPlaceholder())
+    return true;
   LookupResult Found(*this, IdInfo.Identifier, IdInfo.IdentifierLoc,
                      OnlyNamespace ? LookupNamespaceName
                                    : LookupNestedNameSpecifierName);
index 075e87b75cdcf4331bc6c89304f545ac8220d069..f3ffcf5d696c965e1fea7e4733c0b9c88bdc2316 100644 (file)
@@ -628,6 +628,9 @@ void Sema::DiagnoseUnknownTypeName(IdentifierInfo *&II,
                                    CXXScopeSpec *SS,
                                    ParsedType &SuggestedType,
                                    bool AllowClassTemplates) {
+  // Don't report typename errors for editor placeholders.
+  if (II->isEditorPlaceholder())
+    return;
   // We don't have anything to suggest (yet).
   SuggestedType = nullptr;
 
index bb174521c72c7cb5be8e98dbc01b50e2b1a1f0ca..5a56f709377753378d183f8997cc6a488fabd904 100644 (file)
@@ -2129,6 +2129,12 @@ Sema::ActOnIdExpression(Scope *S, CXXScopeSpec &SS,
   IdentifierInfo *II = Name.getAsIdentifierInfo();
   SourceLocation NameLoc = NameInfo.getLoc();
 
+  if (II && II->isEditorPlaceholder()) {
+    // FIXME: When typed placeholders are supported we can create a typed
+    // placeholder expression node.
+    return ExprError();
+  }
+
   // C++ [temp.dep.expr]p3:
   //   An id-expression is type-dependent if it contains:
   //     -- an identifier that was declared with a dependent type,
index 15c1eacb9617e659f991f51ffe4977500d22267d..c54c618df1128dd71ae2d1655091d14bcf44f9c4 100644 (file)
 // RUN: %clang -### -S -fdebug-info-for-profiling -fno-debug-info-for-profiling %s 2>&1 | FileCheck -check-prefix=CHECK-NO-PROFILE-DEBUG %s
 // CHECK-PROFILE-DEBUG: -fdebug-info-for-profiling
 // CHECK-NO-PROFILE-DEBUG-NOT: -fdebug-info-for-profiling
+
+// RUN: %clang -### -S -fallow-editor-placeholders %s 2>&1 | FileCheck -check-prefix=CHECK-ALLOW-PLACEHOLDERS %s
+// RUN: %clang -### -S -fno-allow-editor-placeholders %s 2>&1 | FileCheck -check-prefix=CHECK-NO-ALLOW-PLACEHOLDERS %s
+// CHECK-ALLOW-PLACEHOLDERS: -fallow-editor-placeholders
+// CHECK-NO-ALLOW-PLACEHOLDERS-NOT: -fallow-editor-placeholders
diff --git a/test/Parser/editor-placeholder-recovery.cpp b/test/Parser/editor-placeholder-recovery.cpp
new file mode 100644 (file)
index 0000000..48c290e
--- /dev/null
@@ -0,0 +1,71 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -fallow-editor-placeholders -DSUPPRESS -verify %s
+
+struct Struct {
+public:
+    void method(Struct &x);
+};
+
+struct <#struct name#> {
+  int <#field-name#>;
+#ifndef SUPPRESS
+  // expected-error@-3 {{editor placeholder in source file}}
+  // expected-error@-3 {{editor placeholder in source file}}
+#endif
+};
+
+typename <#typename#>::<#name#>;
+decltype(<#expression#>) foobar;
+typedef <#type#> <#name#>;
+#ifndef SUPPRESS
+  // expected-error@-4 2 {{editor placeholder in source file}}
+  // expected-error@-4 {{editor placeholder in source file}}
+  // expected-error@-4 2 {{editor placeholder in source file}}
+#endif
+
+namespace <#identifier#> {
+  <#declarations#>
+#ifndef SUPPRESS
+  // expected-error@-3 {{editor placeholder in source file}}
+  // expected-error@-3 {{editor placeholder in source file}}
+#endif
+
+}
+
+using <#qualifier#>::<#name#>;
+#ifndef SUPPRESS
+  // expected-error@-2 2 {{editor placeholder in source file}}
+#endif
+
+void avoidPlaceholderErrors(Struct &obj) {
+    static_cast< <#type#> >(<#expression#>);
+    while (<#condition#>) {
+        <#statements#>
+    }
+    obj.method(<#Struct &x#>);
+#ifndef SUPPRESS
+  // expected-error@-6 2 {{editor placeholder in source file}}
+  // expected-error@-6 {{editor placeholder in source file}}
+  // expected-error@-6 {{editor placeholder in source file}}
+  // expected-error@-5 {{editor placeholder in source file}}
+#endif
+    switch (<#expression#>) {
+        case <#constant#>:
+            <#statements#>
+#ifndef SUPPRESS
+  // expected-error@-4 {{editor placeholder in source file}}
+  // expected-error@-4 {{editor placeholder in source file}}
+  // expected-error@-4 {{editor placeholder in source file}}
+#endif
+            break;
+
+        default:
+            break;
+    }
+}
+
+void Struct::method(<#Struct &x#>, noSupressionHere) { // expected-error {{unknown type name 'noSupressionHere'}} expected-error {{C++ requires a type specifier for all declarations}}
+#ifndef SUPPRESS
+  // expected-error@-2 {{editor placeholder in source file}}
+#endif
+}
index b43b0e4a57cb49fe8e282c1ff02bbad6f5dc4b8f..4f22ea770da920c8cb59ae1ceab059f494cbfe4f 100644 (file)
@@ -1,11 +1,14 @@
 // RUN: %clang_cc1 -fsyntax-only -verify %s
 
+@protocol NSObject
+@end
+
+@protocol <#protocol name#> <NSObject> // expected-error {{editor placeholder in source file}}
+// expected-note@-1 {{protocol started here}}
+
 // FIXME: We could do much better with this, if we recognized
 // placeholders somehow. However, we're content with not generating
 // bogus 'archaic' warnings with bad location info.
-@protocol <#protocol name#> <NSObject> // expected-error {{expected identifier or '('}} \
-// expected-error 2{{expected identifier}} \
-// expected-warning{{protocol has no object type specified; defaults to qualified 'id'}}
-<#methods#>
+<#methods#> // expected-error {{editor placeholder in source file}}
 
-@end
+@end // expected-error {{prefix attribute must be followed by an interface or protocol}} expected-error {{missing '@end'}}