arphaman created this revision.

This patch is based on the RFC that I've sent out earlier 
(http://lists.llvm.org/pipermail/cfe-dev/2017-April/053394.html).
It teaches Clang's Lexer to recognize the editor placeholders that are produced 
by some editors like Xcode when expanding code-completion results that include 
placeholders. When the Lexer sees a placeholder, it emits an error 'editor 
placeholder in source file', suppresses all diagnostics in the placeholder 
range and lexes the tokens in the placeholder.

This patch also includes a new compiler option called 
'-fsuppress-editor-placeholder-error' that surpasses the 'editor placeholder in 
source file' error. This option is useful for an IDE like Xcode as we don't 
want to display those errors as live diagnostics in the editor.

There are still certain things that can be improved. For example, in the future 
we may want to add support for different kinds of placeholders (e.g. typed, it 
would produce special typed expressions) that would actually produce a special 
token instead of lexing the contents of the placeholder in order to improve the 
parser's recovery. But they will require changes to the code-completion code as 
well, so I left them for later to keep this patch small.


Repository:
  rL LLVM

https://reviews.llvm.org/D32081

Files:
  include/clang/Basic/Diagnostic.h
  include/clang/Basic/DiagnosticLexKinds.td
  include/clang/Basic/DiagnosticOptions.def
  include/clang/Driver/Options.td
  include/clang/Lex/Lexer.h
  lib/Basic/Diagnostic.cpp
  lib/Basic/DiagnosticIDs.cpp
  lib/Driver/ToolChains/Clang.cpp
  lib/Frontend/CompilerInvocation.cpp
  lib/Lex/Lexer.cpp
  test/Driver/clang_f_opts.c
  test/Lexer/editor-placeholder.cpp
  test/Parser/placeholder-recovery.m

Index: test/Parser/placeholder-recovery.m
===================================================================
--- test/Parser/placeholder-recovery.m
+++ test/Parser/placeholder-recovery.m
@@ -3,9 +3,9 @@
 // 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#>
+@protocol <#protocol name#> <NSObject> // expected-error {{editor placeholder in source file}}
+// expected-error@-1 {{expected identifier or '('}}
+
+<#methods#> // expected-error {{editor placeholder in source file}}
 
 @end
Index: test/Lexer/editor-placeholder.cpp
===================================================================
--- /dev/null
+++ test/Lexer/editor-placeholder.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only  -fsuppress-editor-placeholder-error -DSUPPRESS -verify %s
+
+struct Struct {
+public:
+    void method(Struct &x);
+};
+
+void avoidPlaceholderErrors(Struct &obj) {
+    while (<#condition#>) {
+        <#statements#>
+    }
+    obj.method(<#Struct &x#>);
+#ifndef SUPPRESS
+  // expected-error@-5 {{editor placeholder in source file}}
+  // expected-error@-5 {{editor placeholder in source file}}
+  // expected-error@-4 {{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'}}
+#ifndef SUPPRESS
+  // expected-error@-2 {{editor placeholder in source file}}
+#endif
+}
Index: test/Driver/clang_f_opts.c
===================================================================
--- test/Driver/clang_f_opts.c
+++ test/Driver/clang_f_opts.c
@@ -494,3 +494,8 @@
 // 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 -fsuppress-editor-placeholder-error %s 2>&1 | FileCheck -check-prefix=CHECK-SUPPRESS-PLACEHOLDER-ERROR %s
+// RUN: %clang -### -S -fno-suppress-editor-placeholder-error %s 2>&1 | FileCheck -check-prefix=CHECK-NO-SUPPRESS-PLACEHOLDER-ERROR %s
+// CHECK-SUPPRESS-PLACEHOLDER-ERROR: -fsuppress-editor-placeholder-error
+// CHECK-NO-SUPPRESS-PLACEHOLDER-ERROR-NOT: -fsuppress-editor-placeholder-error
Index: lib/Lex/Lexer.cpp
===================================================================
--- lib/Lex/Lexer.cpp
+++ lib/Lex/Lexer.cpp
@@ -108,6 +108,8 @@
 
   // Default to not keeping comments.
   ExtendedTokenMode = 0;
+
+  CurrentPlaceholderEnd = nullptr;
 }
 
 /// Lexer constructor - Create a new lexer object for the specified buffer
@@ -2722,6 +2724,46 @@
   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::lexEditorPlaceholderStart(const char *CurPtr) {
+  assert(CurPtr[-1] == '<' && CurPtr[0] == '#' && "Not a placeholder!");
+  if (!PP || CurrentPlaceholderEnd)
+    return false;
+  CurrentPlaceholderEnd = findPlaceholderEnd(CurPtr + 1, BufferEnd);
+  if (!CurrentPlaceholderEnd)
+    return false;
+  BufferPtr = CurPtr + 1;
+  if (!PP->getDiagnostics()
+           .getDiagnosticOptions()
+           .SuppressEditorPlaceholderError)
+    Diag(CurPtr - 1, diag::err_placeholder_in_source);
+  // Suppress the diagnostics in range of the editor placeholder.
+  PP->getDiagnostics().suppressDiagnosticsInRange(SourceRange(
+      FileLoc.getLocWithOffset(CurPtr - 1 - BufferStart),
+      FileLoc.getLocWithOffset(CurrentPlaceholderEnd - BufferStart)));
+  return true;
+}
+
+bool Lexer::lexEditorPlaceholderEnd(const char *CurPtr) {
+  assert(CurPtr[-1] == '#' && CurPtr[0] == '>' && "Not a placeholder!");
+  if (CurrentPlaceholderEnd != CurPtr + 1)
+    return false;
+  BufferPtr = CurPtr + 1;
+  CurrentPlaceholderEnd = nullptr;
+  return true;
+}
+
 bool Lexer::isCodeCompletionPoint(const char *CurPtr) const {
   if (PP && PP->isCodeCompletionEnabled()) {
     SourceLocation Loc = FileLoc.getLocWithOffset(CurPtr-BufferStart);
@@ -3479,6 +3521,10 @@
     } else if (LangOpts.Digraphs && Char == '%') {     // '<%' -> '{'
       CurPtr = ConsumeChar(CurPtr, SizeTmp, Result);
       Kind = tok::l_brace;
+    } else if (Char == '#' && lexEditorPlaceholderStart(CurPtr)) {
+      // If this is '<#' and we're in an editor placeholder, then lex the tokens
+      // in the placeholder.
+      goto LexNextToken;
     } else {
       Kind = tok::less;
     }
@@ -3581,6 +3627,8 @@
       if (!isLexingRawMode())
         Diag(BufferPtr, diag::ext_charize_microsoft);
       CurPtr = ConsumeChar(CurPtr, SizeTmp, Result);
+    } else if (Char == '>' && lexEditorPlaceholderEnd(CurPtr)) {
+      goto LexNextToken;
     } else {
       // We parsed a # character.  If this occurs at the start of the line,
       // it's actually the start of a preprocessing directive.  Callback to
Index: lib/Frontend/CompilerInvocation.cpp
===================================================================
--- lib/Frontend/CompilerInvocation.cpp
+++ lib/Frontend/CompilerInvocation.cpp
@@ -1092,6 +1092,8 @@
       << Opts.TabStop << DiagnosticOptions::DefaultTabStop;
   }
   Opts.MessageLength = getLastArgIntValue(Args, OPT_fmessage_length, 0, Diags);
+  Opts.SuppressEditorPlaceholderError =
+      Args.hasArg(OPT_fsuppress_editor_placeholder_error);
   addDiagnosticArgs(Args, OPT_W_Group, OPT_W_value_Group, Opts.Warnings);
   addDiagnosticArgs(Args, OPT_R_Group, OPT_R_value_Group, Opts.Remarks);
 
Index: lib/Driver/ToolChains/Clang.cpp
===================================================================
--- lib/Driver/ToolChains/Clang.cpp
+++ lib/Driver/ToolChains/Clang.cpp
@@ -2290,6 +2290,9 @@
   if (!Args.hasFlag(options::OPT_fstrict_return, options::OPT_fno_strict_return,
                     true))
     CmdArgs.push_back("-fno-strict-return");
+  if (Args.hasFlag(options::OPT_fsuppress_editor_placeholder_error,
+                   options::OPT_fno_suppress_editor_placeholder_error, false))
+    CmdArgs.push_back("-fsuppress-editor-placeholder-error");
   if (Args.hasFlag(options::OPT_fstrict_vtable_pointers,
                    options::OPT_fno_strict_vtable_pointers,
                    false))
Index: lib/Basic/DiagnosticIDs.cpp
===================================================================
--- lib/Basic/DiagnosticIDs.cpp
+++ lib/Basic/DiagnosticIDs.cpp
@@ -477,6 +477,18 @@
           Diag.getSourceManager().getExpansionLoc(Loc)))
     return diag::Severity::Ignored;
 
+  // Check if the diagnostic is in a suppressed range.
+  if (Loc.isValid() && Diag.hasSourceManager()) {
+    const SourceManager &SM = Diag.getSourceManager();
+    for (const SourceRange &R : Diag.getSuppressionRanges()) {
+      if (Loc == R.getBegin() || Loc == R.getEnd())
+        return diag::Severity::Ignored;
+      if (SM.isBeforeInTranslationUnit(R.getBegin(), Loc) &&
+          SM.isBeforeInTranslationUnit(Loc, R.getEnd()))
+        return diag::Severity::Ignored;
+    }
+  }
+
   return Result;
 }
 
Index: lib/Basic/Diagnostic.cpp
===================================================================
--- lib/Basic/Diagnostic.cpp
+++ lib/Basic/Diagnostic.cpp
@@ -374,6 +374,10 @@
       setSeverity(Diag, Map, Loc);
 }
 
+void DiagnosticsEngine::suppressDiagnosticsInRange(SourceRange R) {
+  SuppressionRanges.push_back(R);
+}
+
 void DiagnosticsEngine::Report(const StoredDiagnostic &storedDiag) {
   assert(CurDiagID == ~0U && "Multiple diagnostics in flight at once!");
 
Index: include/clang/Lex/Lexer.h
===================================================================
--- include/clang/Lex/Lexer.h
+++ include/clang/Lex/Lexer.h
@@ -89,6 +89,9 @@
   // CurrentConflictMarkerState - The kind of conflict marker we are handling.
   ConflictMarkerKind CurrentConflictMarkerState;
 
+  /// The pointer to the end of the current placeholder.
+  const char *CurrentPlaceholderEnd;
+
   Lexer(const Lexer &) = delete;
   void operator=(const Lexer &) = delete;
   friend class Preprocessor;
@@ -638,6 +641,9 @@
   bool IsStartOfConflictMarker(const char *CurPtr);
   bool HandleEndOfConflictMarker(const char *CurPtr);
 
+  bool lexEditorPlaceholderStart(const char *CurPtr);
+  bool lexEditorPlaceholderEnd(const char *CurPtr);
+
   bool isCodeCompletionPoint(const char *CurPtr) const;
   void cutOffLexing() { BufferPtr = BufferEnd; }
 
Index: include/clang/Driver/Options.td
===================================================================
--- include/clang/Driver/Options.td
+++ include/clang/Driver/Options.td
@@ -1470,6 +1470,12 @@
 def fno_strict_return : Flag<["-"], "fno-strict-return">, Group<f_Group>,
   Flags<[CC1Option]>;
 
+def fsuppress_editor_placeholder_error : Flag<["-"], "fsuppress-editor-placeholder-error">, Group<f_Group>,
+  Flags<[CC1Option]>,
+  HelpText<"Suppress the 'editor placeholder in source file' errors">;
+def fno_suppress_editor_placeholder_error : Flag<["-"], "fno-suppress-editor-placeholder-error">, Group<f_Group>,
+  Flags<[CC1Option]>;
+
 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: include/clang/Basic/DiagnosticOptions.def
===================================================================
--- include/clang/Basic/DiagnosticOptions.def
+++ include/clang/Basic/DiagnosticOptions.def
@@ -92,6 +92,9 @@
 /// Column limit for formatting message diagnostics, or 0 if unused.
 VALUE_DIAGOPT(MessageLength, 32, 0)
 
+/// True when the 'editor placeholder in source file' has to be supressed.
+DIAGOPT(SuppressEditorPlaceholderError, 1, 0)
+
 #undef DIAGOPT
 #undef ENUM_DIAGOPT
 #undef VALUE_DIAGOPT
Index: include/clang/Basic/DiagnosticLexKinds.td
===================================================================
--- include/clang/Basic/DiagnosticLexKinds.td
+++ include/clang/Basic/DiagnosticLexKinds.td
@@ -242,6 +242,7 @@
   "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: include/clang/Basic/Diagnostic.h
===================================================================
--- include/clang/Basic/Diagnostic.h
+++ include/clang/Basic/Diagnostic.h
@@ -390,6 +390,9 @@
   /// and '='.
   std::string FlagValue;
 
+  /// The source ranges in which all diagnostics should be supressed.
+  std::vector<SourceRange> SuppressionRanges;
+
 public:
   explicit DiagnosticsEngine(IntrusiveRefCntPtr<DiagnosticIDs> Diags,
                              DiagnosticOptions *DiagOpts,
@@ -688,7 +691,15 @@
   /// \brief Reset the state of the diagnostic object to its initial 
   /// configuration.
   void Reset();
-  
+
+  /// Specify a new source range in which all of the diagnostics will be
+  /// supressed.
+  void suppressDiagnosticsInRange(SourceRange R);
+
+  ArrayRef<SourceRange> getSuppressionRanges() const {
+    return SuppressionRanges;
+  }
+
   //===--------------------------------------------------------------------===//
   // DiagnosticsEngine classification and reporting interfaces.
   //
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to