Skip to content

[Diagnostics][NFCI] Introduce Structured fix-its #26612

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions include/swift/AST/DiagnosticConsumer.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ struct DiagnosticInfo {
std::string Text;

public:
FixIt(CharSourceRange R, StringRef Str)
: Range(R), Text(Str) {}
FixIt(CharSourceRange R, StringRef Str, ArrayRef<DiagnosticArgument> Args);

CharSourceRange getRange() const { return Range; }
StringRef getText() const { return Text; }
Expand Down
89 changes: 82 additions & 7 deletions include/swift/AST/DiagnosticEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,22 @@ namespace swift {
: OpeningQuotationMark("'"), ClosingQuotationMark("'"),
AKAFormatString("'%s' (aka '%s')"),
OpaqueResultFormatString("'%s' (%s of '%s')") {}

/// When formatting fix-it arguments, don't include quotes or other
/// additions which would result in invalid code.
static DiagnosticFormatOptions formatForFixIts() {
return DiagnosticFormatOptions("", "", "%s", "%s");
}
};


enum class FixItID : uint32_t;

/// Represents a fix-it defined with a format string and optional
/// DiagnosticArguments. The template parameters allow the
/// fixIt... methods on InFlightDiagnostic to infer their own
/// template params.
template <typename... ArgTypes> struct StructuredFixIt { FixItID ID; };

/// Diagnostic - This is a specific instance of a diagnostic along with all of
/// the DiagnosticArguments that it requires.
class Diagnostic {
Expand All @@ -333,7 +347,7 @@ namespace swift {
Diagnostic(Diag<ArgTypes...> ID,
typename detail::PassArgument<ArgTypes>::type... VArgs)
: ID(ID.ID) {
DiagnosticArgument DiagArgs[] = {
DiagnosticArgument DiagArgs[] = {
DiagnosticArgument(0), std::move(VArgs)...
};
Args.append(DiagArgs + 1, DiagArgs + 1 + sizeof...(VArgs));
Expand Down Expand Up @@ -425,25 +439,70 @@ namespace swift {
/// Add a character-based range to the currently-active diagnostic.
InFlightDiagnostic &highlightChars(SourceLoc Start, SourceLoc End);

static const char *fixItStringFor(const FixItID id);

/// Add a token-based replacement fix-it to the currently-active
/// diagnostic.
template <typename... ArgTypes>
InFlightDiagnostic &
fixItReplace(SourceRange R, StructuredFixIt<ArgTypes...> fixIt,
typename detail::PassArgument<ArgTypes>::type... VArgs) {
DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
return fixItReplace(R, fixItStringFor(fixIt.ID), DiagArgs);
}

/// Add a character-based replacement fix-it to the currently-active
/// diagnostic.
template <typename... ArgTypes>
InFlightDiagnostic &
fixItReplaceChars(SourceLoc Start, SourceLoc End,
StructuredFixIt<ArgTypes...> fixIt,
typename detail::PassArgument<ArgTypes>::type... VArgs) {
DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
return fixItReplaceChars(Start, End, fixItStringFor(fixIt.ID), DiagArgs);
}

/// Add an insertion fix-it to the currently-active diagnostic.
template <typename... ArgTypes>
InFlightDiagnostic &
fixItInsert(SourceLoc L, StructuredFixIt<ArgTypes...> fixIt,
typename detail::PassArgument<ArgTypes>::type... VArgs) {
DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
return fixItReplaceChars(L, L, fixItStringFor(fixIt.ID), DiagArgs);
}

/// Add an insertion fix-it to the currently-active diagnostic. The
/// text is inserted immediately *after* the token specified.
template <typename... ArgTypes>
InFlightDiagnostic &
fixItInsertAfter(SourceLoc L, StructuredFixIt<ArgTypes...> fixIt,
typename detail::PassArgument<ArgTypes>::type... VArgs) {
DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
return fixItInsertAfter(L, fixItStringFor(fixIt.ID), DiagArgs);
}

/// Add a token-based replacement fix-it to the currently-active
/// diagnostic.
InFlightDiagnostic &fixItReplace(SourceRange R, StringRef Str);

/// Add a character-based replacement fix-it to the currently-active
/// diagnostic.
InFlightDiagnostic &fixItReplaceChars(SourceLoc Start, SourceLoc End,
StringRef Str);
StringRef Str) {
return fixItReplaceChars(Start, End, "%0", {Str});
}

/// Add an insertion fix-it to the currently-active diagnostic.
InFlightDiagnostic &fixItInsert(SourceLoc L, StringRef Str) {
return fixItReplaceChars(L, L, Str);
return fixItReplaceChars(L, L, "%0", {Str});
}

/// Add an insertion fix-it to the currently-active diagnostic. The
/// text is inserted immediately *after* the token specified.
///
InFlightDiagnostic &fixItInsertAfter(SourceLoc L, StringRef);

InFlightDiagnostic &fixItInsertAfter(SourceLoc L, StringRef Str) {
return fixItInsertAfter(L, "%0", {Str});
}

/// Add a token-based removal fix-it to the currently-active
/// diagnostic.
InFlightDiagnostic &fixItRemove(SourceRange R);
Expand All @@ -457,6 +516,22 @@ namespace swift {
/// Add two replacement fix-it exchanging source ranges to the
/// currently-active diagnostic.
InFlightDiagnostic &fixItExchange(SourceRange R1, SourceRange R2);

private:
InFlightDiagnostic &fixItReplace(SourceRange R, StringRef FormatString,
ArrayRef<DiagnosticArgument> Args);

InFlightDiagnostic &fixItReplaceChars(SourceLoc Start, SourceLoc End,
StringRef FormatString,
ArrayRef<DiagnosticArgument> Args);

InFlightDiagnostic &fixItInsert(SourceLoc L, StringRef FormatString,
ArrayRef<DiagnosticArgument> Args) {
return fixItReplaceChars(L, L, FormatString, Args);
}

InFlightDiagnostic &fixItInsertAfter(SourceLoc L, StringRef FormatString,
ArrayRef<DiagnosticArgument> Args);
};

/// Class to track, map, and remap diagnostic severity and fatality
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/DiagnosticsAll.def
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
DIAG(REMARK,ID,Options,Text,Signature)
#endif

#ifndef FIXIT
# define FIXIT(ID, Text, Signature)
#endif

#define DIAG_NO_UNDEF

#include "DiagnosticsCommon.def"
Expand All @@ -60,3 +64,4 @@
#undef WARNING
#undef ERROR
#undef REMARK
#undef FIXIT
5 changes: 5 additions & 0 deletions include/swift/AST/DiagnosticsCommon.def
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
DIAG(REMARK,ID,Options,Text,Signature)
#endif

#ifndef FIXIT
# define FIXIT(ID, Text, Signature)
#endif

ERROR(invalid_diagnostic,none,
"INTERNAL ERROR: this diagnostic should not be produced", ())

Expand Down Expand Up @@ -166,4 +170,5 @@ NOTE(kind_declname_declared_here,none,
# undef NOTE
# undef WARNING
# undef ERROR
# undef FIXIT
#endif
18 changes: 15 additions & 3 deletions include/swift/AST/DiagnosticsCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,24 @@ namespace swift {
struct Diag;

namespace detail {
// These templates are used to help extract the type arguments of the
// DIAG/ERROR/WARNING/NOTE/REMARK/FIXIT macros.
template<typename T>
struct DiagWithArguments;

template<typename ...ArgTypes>
struct DiagWithArguments<void(ArgTypes...)> {
typedef Diag<ArgTypes...> type;
};
}

template <typename T>
struct StructuredFixItWithArguments;

template <typename... ArgTypes>
struct StructuredFixItWithArguments<void(ArgTypes...)> {
typedef StructuredFixIt<ArgTypes...> type;
};
} // end namespace detail

enum class StaticSpellingKind : uint8_t;

Expand All @@ -48,8 +58,10 @@ namespace swift {
// Declare common diagnostics objects with their appropriate types.
#define DIAG(KIND,ID,Options,Text,Signature) \
extern detail::DiagWithArguments<void Signature>::type ID;
#define FIXIT(ID, Text, Signature) \
extern detail::StructuredFixItWithArguments<void Signature>::type ID;
#include "DiagnosticsCommon.def"
}
}
} // end namespace diag
} // end namespace swift

#endif
5 changes: 5 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
DIAG(NOTE,ID,Options,Text,Signature)
#endif

#ifndef FIXIT
# define FIXIT(ID, Text, Signature)
#endif

//==============================================================================
// MARK: Lexing and Parsing diagnostics
//==============================================================================
Expand Down Expand Up @@ -1638,4 +1642,5 @@ ERROR(unknown_syntax_entity, PointsToFirstBadToken,
# undef NOTE
# undef WARNING
# undef ERROR
# undef FIXIT
#endif
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsParse.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace swift {
// Declare common diagnostics objects with their appropriate types.
#define DIAG(KIND,ID,Options,Text,Signature) \
extern detail::DiagWithArguments<void Signature>::type ID;
#define FIXIT(ID,Text,Signature) \
extern detail::StructuredFixItWithArguments<void Signature>::type ID;
#include "DiagnosticsParse.def"
}
}
Expand Down
16 changes: 16 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
DIAG(NOTE,ID,Options,Text,Signature)
#endif

#ifndef FIXIT
# define FIXIT(ID, Text, Signature)
#endif

NOTE(decl_declared_here,none,
"%0 declared here", (DeclName))
NOTE(kind_declared_here,none,
Expand Down Expand Up @@ -145,6 +149,8 @@ ERROR(could_not_use_member_on_existential,none,
"member %1 cannot be used on value of protocol type %0; use a generic"
" constraint instead",
(Type, DeclName))
FIXIT(replace_with_type,"%0",(Type))
FIXIT(insert_type_qualification,"%0.",(Type))

ERROR(candidate_inaccessible,none,
"%0 is inaccessible due to "
Expand Down Expand Up @@ -297,6 +303,9 @@ ERROR(cannot_infer_closure_type,none,
ERROR(cannot_infer_closure_result_type,none,
"unable to infer complex closure return type; "
"add explicit type to disambiguate", ())
FIXIT(insert_closure_return_type,
"%select{| () }1-> %0 %select{|in }1",
(Type, bool))

ERROR(incorrect_explicit_closure_result,none,
"declared closure result %0 is incompatible with contextual type %1",
Expand Down Expand Up @@ -344,6 +353,9 @@ ERROR(cannot_throw_error_code,none,
"thrown error code type %0 does not conform to 'Error'; construct an %1 "
"instance", (Type, Type))

FIXIT(insert_type_coercion,
" %select{as!|as}0 %1",(bool, Type))

ERROR(bad_yield_count,none,
"expected %0 yield value(s)", (unsigned))

Expand Down Expand Up @@ -4178,6 +4190,9 @@ NOTE(availability_guard_with_version_check, none,

NOTE(availability_add_attribute, none,
"add @available attribute to enclosing %0", (DescriptiveDeclKind))
FIXIT(insert_available_attr,
"@available(%0 %1, *)\n%2",
(StringRef, StringRef, StringRef))

ERROR(availability_accessor_only_version_newer, none,
"%select{getter|setter}0 for %1 is only available in %2 %3"
Expand Down Expand Up @@ -4595,4 +4610,5 @@ ERROR(function_builder_arguments, none,
# undef NOTE
# undef WARNING
# undef ERROR
# undef FIXIT
#endif
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ namespace swift {
// Declare common diagnostics objects with their appropriate types.
#define DIAG(KIND,ID,Options,Text,Signature) \
extern detail::DiagWithArguments<void Signature>::type ID;
#define FIXIT(ID,Text,Signature) \
extern detail::StructuredFixItWithArguments<void Signature>::type ID;
#include "DiagnosticsSema.def"
}
}
Expand Down
9 changes: 9 additions & 0 deletions lib/AST/DiagnosticConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ using namespace swift;

DiagnosticConsumer::~DiagnosticConsumer() = default;

DiagnosticInfo::FixIt::FixIt(CharSourceRange R, StringRef Str,
ArrayRef<DiagnosticArgument> Args) : Range(R) {
// FIXME: Defer text formatting to later in the pipeline.
llvm::raw_string_ostream OS(Text);
DiagnosticEngine::formatDiagnosticText(OS, Str, Args,
DiagnosticFormatOptions::
formatForFixIts());
}

llvm::SMLoc DiagnosticConsumer::getRawLoc(SourceLoc loc) {
return loc.Value;
}
Expand Down
Loading