From 976f1494f51226ca65a3fd91f6979e7bf6acd72f Mon Sep 17 00:00:00 2001 From: John McCall Date: Fri, 1 Mar 2024 01:30:33 -0500 Subject: [PATCH 1/3] [NFC] Introduce DiagRef and use it throughout the parser The goal is to have a lightweight way to pass an unapplied diagnostic to general routines. Constructing a Diagnostic is quite expensive as something we're potentially doing in hot paths, as opposed to just when we're actually emitting the diagnostic. This design allows the expense to be delayed until we need it. I've also optimized the Diagnostic constructor to avoid copying arguments unnecessarily; this is a relatively small expense, since arguments are POD, but there's really no good reason not to do it. --- include/swift/AST/DiagnosticEngine.h | 126 +++++++++++++++++++++++++-- include/swift/Parse/Parser.h | 38 ++++---- lib/Parse/ParseDecl.cpp | 14 +-- lib/Parse/ParseExpr.cpp | 4 +- lib/Parse/Parser.cpp | 15 ++-- lib/SIL/Parser/ParseSIL.cpp | 8 +- lib/SIL/Parser/SILParser.h | 13 ++- 7 files changed, 166 insertions(+), 52 deletions(-) diff --git a/include/swift/AST/DiagnosticEngine.h b/include/swift/AST/DiagnosticEngine.h index 6151f0dc8f47a..5dedbbf5bba63 100644 --- a/include/swift/AST/DiagnosticEngine.h +++ b/include/swift/AST/DiagnosticEngine.h @@ -88,6 +88,10 @@ namespace swift { }; } + template + using DiagArgTuple = + std::tuple::type...>; + /// A family of wrapper types for compiler data types that forces its /// underlying data to be formatted with full qualification. /// @@ -476,20 +480,28 @@ namespace swift { friend DiagnosticEngine; friend class InFlightDiagnostic; + Diagnostic(DiagID ID) : ID(ID) {} + public: // All constructors are intentionally implicit. template Diagnostic(Diag ID, typename detail::PassArgument::type... VArgs) - : ID(ID.ID) { - DiagnosticArgument DiagArgs[] = { - DiagnosticArgument(0), std::move(VArgs)... - }; - Args.append(DiagArgs + 1, DiagArgs + 1 + sizeof...(VArgs)); + : Diagnostic(ID.ID) { + Args.reserve(sizeof...(ArgTypes)); + gatherArgs(VArgs...); } /*implicit*/Diagnostic(DiagID ID, ArrayRef Args) : ID(ID), Args(Args.begin(), Args.end()) {} + + template + static Diagnostic fromTuple(Diag id, + const DiagArgTuple &tuple) { + Diagnostic result(id.ID); + result.gatherArgsFromTuple, 0, ArgTypes...>(tuple); + return result; + } // Accessors. DiagID getID() const { return ID; } @@ -528,6 +540,37 @@ namespace swift { void addChildNote(Diagnostic &&D); void insertChildNote(unsigned beforeIndex, Diagnostic &&D); + + private: + // gatherArgs could just be `Args.emplace_back(args)...;` if C++ + // allowed pack expansions in statement context. + + // Base case. + void gatherArgs() {} + + // Pull one off the pack. + template + void gatherArgs(ArgType arg, RemainingArgTypes... remainingArgs) { + Args.emplace_back(arg); + gatherArgs(remainingArgs...); + } + + // gatherArgsFromTuple could just be + // `Args.emplace_back(std::get>(tuple))...;` + // in a better world. + + // Base case. + template + void gatherArgsFromTuple(const Tuple &tuple) {} + + // Pull one off the pack. + template + void gatherArgsFromTuple(const Tuple &tuple) { + Args.emplace_back(std::move(std::get(tuple))); + gatherArgsFromTuple( + std::move(tuple)); + } }; /// A diagnostic that has no input arguments, so it is trivially-destructable. @@ -866,6 +909,73 @@ namespace swift { DiagnosticState &operator=(DiagnosticState &&) = default; }; + /// A lightweight reference to a diagnostic that's been fully applied to + /// its arguments. This allows a general routine (in the parser, say) to + /// be customized to emit an arbitrary diagnostic without needing to + /// eagerly construct a full Diagnostic. Like ArrayRef and function_ref, + /// this stores a reference to what's likely to be a temporary, so it + /// should only be used as a function parameter. If you need to persist + /// the diagnostic, you'll have to call createDiagnostic(). + /// + /// You can initialize a DiagRef parameter in one of two ways: + /// - passing a Diag<> as the argument, e.g. + /// diag::circular_reference + /// or + /// - constructing it with a Diag and its arguments, e.g. + /// {diag::circular_protocol_def, {proto->getName()}} + /// + /// It'd be nice to let people write `{diag::my_error, arg0, arg1}` + /// instead of `{diag::my_error, {arg0, arg1}}`, but we can't: the + /// temporary needs to be created in the calling context. + class DiagRef { + DiagID id; + + /// If this is null, then id is a Diag<> and there are no arguments. + Diagnostic (*createFn)(DiagID id, const void *opaqueStorage); + const void *opaqueStorage; + + public: + /// Construct a diagnostic from a diagnostic ID that's known to not take + /// arguments. + DiagRef(Diag<> id) + : id(id.ID), createFn(nullptr), opaqueStorage(nullptr) {} + + /// Construct a diagnostic from a diagnostic ID and its arguments. + template + DiagRef(Diag id, const DiagArgTuple &tuple) + : id(id.ID), + createFn(&createFromTuple), + opaqueStorage(&tuple) {} + + // A specialization of the general constructor above for diagnostics + // with no arguments; this is a useful optimization when a DiagRef + // is constructed generically. + DiagRef(Diag<> id, const DiagArgTuple<> &tuple) + : DiagRef(id) {} + + /// Return the diagnostic ID that this will emit. + DiagID getID() const { + return id; + } + + /// Create a full Diagnostic. It's safe to do this multiple times on + /// a single DiagRef. + Diagnostic createDiagnostic() { + if (!createFn) { + return Diagnostic(Diag<> {id}); + } else { + return createFn(id, opaqueStorage); + } + } + + private: + template + static Diagnostic createFromTuple(DiagID id, const void *opaqueStorage) { + auto tuple = static_cast *>(opaqueStorage); + return Diagnostic::fromTuple(Diag {id}, *tuple); + } + }; + /// Class responsible for formatting diagnostics and presenting them /// to the user. class DiagnosticEngine { @@ -1113,6 +1223,12 @@ namespace swift { return diagnose(Loc, Diagnostic(ID, std::move(Args)...)); } + /// Emit the given lazily-applied diagnostic at the specified + /// source location. + InFlightDiagnostic diagnose(SourceLoc loc, DiagRef diag) { + return diagnose(loc, diag.createDiagnostic()); + } + /// Delete an API that may lead clients to avoid specifying source location. template InFlightDiagnostic diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index c588e11018fc4..39a2671335f63 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -729,28 +729,28 @@ class Parser { } public: - InFlightDiagnostic diagnose(SourceLoc Loc, Diagnostic Diag) { + InFlightDiagnostic diagnose(SourceLoc Loc, DiagRef Diag) { if (Diags.isDiagnosticPointsToFirstBadToken(Diag.getID()) && Loc == Tok.getLoc() && Tok.isAtStartOfLine()) Loc = getEndOfPreviousLoc(); return Diags.diagnose(Loc, Diag); } - InFlightDiagnostic diagnose(Token Tok, Diagnostic Diag) { + InFlightDiagnostic diagnose(Token Tok, DiagRef Diag) { return diagnose(Tok.getLoc(), Diag); } template InFlightDiagnostic diagnose(SourceLoc Loc, Diag DiagID, ArgTypes &&...Args) { - return diagnose(Loc, Diagnostic(DiagID, std::forward(Args)...)); + return diagnose(Loc, {DiagID, {std::forward(Args)...}}); } template InFlightDiagnostic diagnose(Token Tok, Diag DiagID, ArgTypes &&...Args) { return diagnose(Tok.getLoc(), - Diagnostic(DiagID, std::forward(Args)...)); + {DiagID, {std::forward(Args)...}}); } /// Add a fix-it to remove the space in consecutive identifiers. @@ -809,19 +809,19 @@ class Parser { /// its name in \p Result. Otherwise, emit an error. /// /// \returns false on success, true on error. - bool parseIdentifier(Identifier &Result, SourceLoc &Loc, const Diagnostic &D, + bool parseIdentifier(Identifier &Result, SourceLoc &Loc, DiagRef D, bool diagnoseDollarPrefix); /// Consume an identifier with a specific expected name. This is useful for /// contextually sensitive keywords that must always be present. bool parseSpecificIdentifier(StringRef expected, SourceLoc &Loc, - const Diagnostic &D); + DiagRef D); template bool parseIdentifier(Identifier &Result, SourceLoc &L, bool diagnoseDollarPrefix, Diag ID, ArgTypes... Args) { - return parseIdentifier(Result, L, Diagnostic(ID, Args...), + return parseIdentifier(Result, L, {ID, {Args...}}, diagnoseDollarPrefix); } @@ -829,19 +829,19 @@ class Parser { bool parseSpecificIdentifier(StringRef expected, Diag ID, ArgTypes... Args) { SourceLoc L; - return parseSpecificIdentifier(expected, L, Diagnostic(ID, Args...)); + return parseSpecificIdentifier(expected, L, {ID, {Args...}}); } /// Consume an identifier or operator if present and return its name /// in \p Result. Otherwise, emit an error and return true. bool parseAnyIdentifier(Identifier &Result, SourceLoc &Loc, - const Diagnostic &D, bool diagnoseDollarPrefix); + DiagRef D, bool diagnoseDollarPrefix); template bool parseAnyIdentifier(Identifier &Result, bool diagnoseDollarPrefix, Diag ID, ArgTypes... Args) { SourceLoc L; - return parseAnyIdentifier(Result, L, Diagnostic(ID, Args...), + return parseAnyIdentifier(Result, L, {ID, {Args...}}, diagnoseDollarPrefix); } @@ -849,28 +849,28 @@ class Parser { /// emit the specified error diagnostic, and a note at the specified note /// location. bool parseUnsignedInteger(unsigned &Result, SourceLoc &Loc, - const Diagnostic &D); + DiagRef D); /// The parser expects that \p K is next token in the input. If so, /// it is consumed and false is returned. /// /// If the input is malformed, this emits the specified error diagnostic. - bool parseToken(tok K, SourceLoc &TokLoc, const Diagnostic &D); + bool parseToken(tok K, SourceLoc &TokLoc, DiagRef D); template bool parseToken(tok K, Diag ID, ArgTypes... Args) { SourceLoc L; - return parseToken(K, L, Diagnostic(ID, Args...)); + return parseToken(K, L, {ID, {Args...}}); } template bool parseToken(tok K, SourceLoc &L, Diag ID, ArgTypes... Args) { - return parseToken(K, L, Diagnostic(ID, Args...)); + return parseToken(K, L, {ID, {Args...}}); } /// Parse the specified expected token and return its location on success. On failure, emit the specified /// error diagnostic, a note at the specified note location, and return the location of the previous token. - bool parseMatchingToken(tok K, SourceLoc &TokLoc, Diagnostic ErrorDiag, + bool parseMatchingToken(tok K, SourceLoc &TokLoc, DiagRef ErrorDiag, SourceLoc OtherLoc); /// Returns the proper location for a missing right brace, parenthesis, etc. @@ -903,7 +903,7 @@ class Parser { /// Parse a comma separated list of some elements. ParserStatus parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc, - bool AllowSepAfterLast, Diag<> ErrorDiag, + bool AllowSepAfterLast, DiagRef RightErrorDiag, llvm::function_ref callback); void consumeTopLevelDecl(ParserPosition BeginParserPosition, @@ -1184,7 +1184,7 @@ class Parser { /// Parse a version tuple of the form x[.y[.z]]. Returns true if there was /// an error parsing. bool parseVersionTuple(llvm::VersionTuple &Version, SourceRange &Range, - const Diagnostic &D); + DiagRef D); bool isParameterSpecifier() { if (Tok.is(tok::kw_inout)) return true; @@ -1832,7 +1832,7 @@ class Parser { /// unqualified-decl-name: /// unqualified-decl-base-name /// unqualified-decl-base-name '(' ((identifier | '_') ':') + ')' - DeclNameRef parseDeclNameRef(DeclNameLoc &loc, const Diagnostic &diag, + DeclNameRef parseDeclNameRef(DeclNameLoc &loc, DiagRef diag, DeclNameOptions flags); /// Parse macro expansion. @@ -1843,7 +1843,7 @@ class Parser { SourceLoc £Loc, DeclNameLoc ¯oNameLoc, DeclNameRef ¯oNameRef, SourceLoc &leftAngleLoc, SmallVectorImpl &genericArgs, SourceLoc &rightAngleLoc, ArgumentList *&argList, bool isExprBasic, - const Diagnostic &diag); + DiagRef diag); ParserResult parseExprIdentifier(bool allowKeyword); Expr *parseExprEditorPlaceholder(Token PlaceholderTok, diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index f026b10c0375f..f6884aa9ad175 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -629,7 +629,7 @@ ParserResult Parser::parseExtendedAvailabilitySpecList( bool VerArgWasEmpty = VerArg.empty(); if (parseVersionTuple( VerArg.Version, VerArg.Range, - Diagnostic(diag::attr_availability_expected_version, AttrName))) { + {diag::attr_availability_expected_version, {AttrName}})) { AnyArgumentInvalid = true; if (peekToken().isAny(tok::r_paren, tok::comma)) consumeToken(); @@ -2251,7 +2251,7 @@ ParserStatus Parser::parsePlatformVersionInList(StringRef AttrName, llvm::VersionTuple VerTuple; SourceRange VersionRange; if (parseVersionTuple(VerTuple, VersionRange, - Diagnostic(diag::attr_availability_expected_version, AttrName))) { + {diag::attr_availability_expected_version, {AttrName}})) { return makeParserError(); } @@ -2745,7 +2745,7 @@ Parser::parseMacroRoleAttribute( /// omitted; the identifier written by the user otherwise. static std::optional parseSingleAttrOptionImpl( Parser &P, SourceLoc Loc, SourceRange &AttrRange, StringRef AttrName, - DeclAttrKind DK, bool allowOmitted, Diagnostic nonIdentifierDiagnostic) { + DeclAttrKind DK, bool allowOmitted, DiagRef nonIdentifierDiagnostic) { SWIFT_DEFER { AttrRange = SourceRange(Loc, P.PreviousLoc); }; @@ -2793,7 +2793,7 @@ parseSingleAttrOptionIdentifier(Parser &P, SourceLoc Loc, DeclAttrKind DK, bool allowOmitted = false) { return parseSingleAttrOptionImpl( P, Loc, AttrRange, AttrName, DK, allowOmitted, - { diag::attr_expected_option_identifier, AttrName }); + {diag::attr_expected_option_identifier, {AttrName}}); } /// Parses a (possibly optional) argument for an attribute containing a single identifier from a known set of @@ -2819,8 +2819,8 @@ parseSingleAttrOption(Parser &P, SourceLoc Loc, SourceRange &AttrRange, auto parsedIdentifier = parseSingleAttrOptionImpl( P, Loc, AttrRange,AttrName, DK, /*allowOmitted=*/valueIfOmitted.has_value(), - Diagnostic(diag::attr_expected_option_such_as, AttrName, - options.front().first.str())); + {diag::attr_expected_option_such_as, + {AttrName, options.front().first.str()}}); if (!parsedIdentifier) return std::nullopt; @@ -4043,7 +4043,7 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, bool Parser::parseVersionTuple(llvm::VersionTuple &Version, SourceRange &Range, - const Diagnostic &D) { + DiagRef D) { // A version number is either an integer (8), a float (8.1), or a // float followed by a dot and an integer (8.1.0). if (!Tok.isAny(tok::integer_literal, tok::floating_literal)) { diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 7efe1e452acc0..091071a3ba38e 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -2275,7 +2275,7 @@ static bool tryParseArgLabelList(Parser &P, Parser::DeclNameOptions flags, } DeclNameRef Parser::parseDeclNameRef(DeclNameLoc &loc, - const Diagnostic &diag, + DiagRef diag, DeclNameOptions flags) { // Consume the base name. DeclBaseName baseName; @@ -2338,7 +2338,7 @@ ParserStatus Parser::parseFreestandingMacroExpansion( SourceLoc £Loc, DeclNameLoc ¯oNameLoc, DeclNameRef ¯oNameRef, SourceLoc &leftAngleLoc, SmallVectorImpl &genericArgs, SourceLoc &rightAngleLoc, ArgumentList *&argList, bool isExprBasic, - const Diagnostic &diag) { + DiagRef diag) { SourceLoc poundEndLoc = Tok.getRange().getEnd(); poundLoc = consumeToken(tok::pound); diff --git a/lib/Parse/Parser.cpp b/lib/Parse/Parser.cpp index ad25e38005d27..6590dfe17f458 100644 --- a/lib/Parse/Parser.cpp +++ b/lib/Parse/Parser.cpp @@ -868,7 +868,7 @@ Parser::StructureMarkerRAII::StructureMarkerRAII(Parser &parser, //===----------------------------------------------------------------------===// bool Parser::parseIdentifier(Identifier &Result, SourceLoc &Loc, - const Diagnostic &D, bool diagnoseDollarPrefix) { + DiagRef D, bool diagnoseDollarPrefix) { switch (Tok.getKind()) { case tok::kw_self: case tok::kw_Self: @@ -883,7 +883,7 @@ bool Parser::parseIdentifier(Identifier &Result, SourceLoc &Loc, } bool Parser::parseSpecificIdentifier(StringRef expected, SourceLoc &loc, - const Diagnostic &D) { + DiagRef D) { if (Tok.getText() != expected) { diagnose(Tok, D); return true; @@ -895,8 +895,7 @@ bool Parser::parseSpecificIdentifier(StringRef expected, SourceLoc &loc, /// parseAnyIdentifier - Consume an identifier or operator if present and return /// its name in Result. Otherwise, emit an error and return true. bool Parser::parseAnyIdentifier(Identifier &Result, SourceLoc &Loc, - const Diagnostic &D, - bool diagnoseDollarPrefix) { + DiagRef D, bool diagnoseDollarPrefix) { if (Tok.is(tok::identifier)) { Loc = consumeIdentifier(Result, diagnoseDollarPrefix); return false; @@ -935,7 +934,7 @@ bool Parser::parseAnyIdentifier(Identifier &Result, SourceLoc &Loc, /// consumed and false is returned. /// /// If the input is malformed, this emits the specified error diagnostic. -bool Parser::parseToken(tok K, SourceLoc &TokLoc, const Diagnostic &D) { +bool Parser::parseToken(tok K, SourceLoc &TokLoc, DiagRef D) { if (Tok.is(K)) { TokLoc = consumeToken(K); return false; @@ -946,7 +945,7 @@ bool Parser::parseToken(tok K, SourceLoc &TokLoc, const Diagnostic &D) { return true; } -bool Parser::parseMatchingToken(tok K, SourceLoc &TokLoc, Diagnostic ErrorDiag, +bool Parser::parseMatchingToken(tok K, SourceLoc &TokLoc, DiagRef ErrorDiag, SourceLoc OtherLoc) { Diag<> OtherNote; switch (K) { @@ -966,7 +965,7 @@ bool Parser::parseMatchingToken(tok K, SourceLoc &TokLoc, Diagnostic ErrorDiag, } bool Parser::parseUnsignedInteger(unsigned &Result, SourceLoc &Loc, - const Diagnostic &D) { + DiagRef D) { auto IntTok = Tok; if (parseToken(tok::integer_literal, Loc, D)) return true; @@ -1058,7 +1057,7 @@ Parser::parseListItem(ParserStatus &Status, tok RightK, SourceLoc LeftLoc, ParserStatus Parser::parseList(tok RightK, SourceLoc LeftLoc, SourceLoc &RightLoc, - bool AllowSepAfterLast, Diag<> ErrorDiag, + bool AllowSepAfterLast, DiagRef ErrorDiag, llvm::function_ref callback) { if (Tok.is(RightK)) { RightLoc = consumeToken(RightK); diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index a760239b3a0ce..372966dc54c23 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -138,7 +138,7 @@ ParseSILModuleRequest::evaluate(Evaluator &evaluator, //===----------------------------------------------------------------------===// bool SILParser::parseSILIdentifier(Identifier &Result, SourceLoc &Loc, - const Diagnostic &D) { + DiagRef D) { switch (P.Tok.getKind()) { case tok::identifier: case tok::dollarident: @@ -602,7 +602,7 @@ bool SILParser::parseSILQualifier( } result = parseName(Str); if (!result) { - P.diagnose(loc, Diagnostic(diag::unrecognized_sil_qualifier)); + P.diagnose(loc, diag::unrecognized_sil_qualifier); return true; } return false; @@ -2051,7 +2051,7 @@ parseAssignOrInitAssignments(llvm::SmallVectorImpl &assignments, // Returns true on error. static bool parseIndexList(Parser &P, StringRef label, SmallVectorImpl &indices, - const Diagnostic &parseIndexDiag) { + DiagRef parseIndexDiag) { SourceLoc loc; // Parse `[