diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 38a91b67e4356..792568c22d518 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -8601,6 +8601,9 @@ class MacroDecl : public GenericContext, public ValueDecl { /// Retrieve the definition of this macro. MacroDefinition getDefinition() const; + /// Set the definition of this macro + void setDefinition(MacroDefinition definition); + /// Retrieve the parameter list of this macro. ParameterList *getParameterList() const { return parameterList; } diff --git a/include/swift/IDE/Utils.h b/include/swift/IDE/Utils.h index 77b361d2d7be4..a53e9e46c151a 100644 --- a/include/swift/IDE/Utils.h +++ b/include/swift/IDE/Utils.h @@ -578,6 +578,9 @@ class SourceEditConsumer { void insertAfter(SourceManager &SM, SourceLoc Loc, StringRef Text, ArrayRef SubRegions = {}); void accept(SourceManager &SM, Replacement Replacement) { accept(SM, RegionType::ActiveCode, {Replacement}); } void remove(SourceManager &SM, CharSourceRange Range); + void acceptMacroExpansionBuffer(SourceManager &SM, unsigned bufferID, + SourceFile *containingSF, + bool adjustExpansion, bool includeBufferName); }; /// This helper stream inserts text into a SourceLoc by calling functions in diff --git a/include/swift/IDETool/SyntacticMacroExpansion.h b/include/swift/IDETool/SyntacticMacroExpansion.h new file mode 100644 index 0000000000000..2f1c079ae9c28 --- /dev/null +++ b/include/swift/IDETool/SyntacticMacroExpansion.h @@ -0,0 +1,97 @@ +//===--- SyntacticMacroExpansion.h ----------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_IDE_SYNTACTICMACROEXPANSION_H +#define SWIFT_IDE_SYNTACTICMACROEXPANSION_H + +#include "swift/AST/Decl.h" +#include "swift/AST/MacroDefinition.h" +#include "swift/AST/PluginRegistry.h" +#include "swift/Basic/Fingerprint.h" +#include "swift/Frontend/Frontend.h" +#include "llvm/Support/MemoryBuffer.h" + +namespace swift { + +class ASTContext; +class SourceFile; + +namespace ide { +class SourceEditConsumer; + +/// Simple object to specify a syntactic macro expansion. +struct MacroExpansionSpecifier { + unsigned offset; + swift::MacroRoles macroRoles; + swift::MacroDefinition macroDefinition; +}; + +/// Instance of a syntactic macro expansion context. This is created for each +/// list of compiler arguments (i.e. 'argHash'), and reused as long as the +/// compiler arguments are not changed. +class SyntacticMacroExpansionInstance { + CompilerInvocation invocation; + + SourceManager SourceMgr; + DiagnosticEngine Diags{SourceMgr}; + std::unique_ptr Ctx; + ModuleDecl *TheModule = nullptr; + llvm::StringMap MacroDecls; + + /// Create 'SourceFile' using the buffer. + swift::SourceFile *getSourceFile(llvm::MemoryBuffer *inputBuf); + + /// Synthesize 'MacroDecl' AST object to use the expansion. + swift::MacroDecl * + getSynthesizedMacroDecl(swift::Identifier name, + const MacroExpansionSpecifier &expansion); + + /// Expand single 'expansion' in SF. + void expand(swift::SourceFile *SF, + const MacroExpansionSpecifier &expansion, + SourceEditConsumer &consumer); + +public: + SyntacticMacroExpansionInstance() {} + + /// Setup the instance with \p args . + bool setup(StringRef SwiftExecutablePath, ArrayRef args, + std::shared_ptr plugins, std::string &error); + + ASTContext &getASTContext() { return *Ctx; } + + /// Expand all macros in \p inputBuf and send the edit results to \p consumer. + /// Expansions are specified by \p expansions . + void expandAll(llvm::MemoryBuffer *inputBuf, + ArrayRef expansions, + SourceEditConsumer &consumer); +}; + +/// Manager object to vend 'SyntacticMacroExpansionInstance'. +class SyntacticMacroExpansion { + StringRef SwiftExecutablePath; + std::shared_ptr Plugins; + +public: + SyntacticMacroExpansion(StringRef SwiftExecutablePath, + std::shared_ptr Plugins) + : SwiftExecutablePath(SwiftExecutablePath), Plugins(Plugins) {} + + /// Get instance configured with the specified compiler arguments. + std::shared_ptr + getInstance(ArrayRef args, std::string &error); +}; + +} // namespace ide +} // namespace swift + +#endif // SWIFT_IDE_SYNTACTICMACROEXPANSION_H diff --git a/include/swift/Sema/IDETypeChecking.h b/include/swift/Sema/IDETypeChecking.h index 2d4da1a016ae5..33825e593cf15 100644 --- a/include/swift/Sema/IDETypeChecking.h +++ b/include/swift/Sema/IDETypeChecking.h @@ -37,6 +37,7 @@ namespace swift { enum class DeclRefKind; class Expr; class ExtensionDecl; + class FreestandingMacroExpansion; class FunctionType; class LabeledConditionalStmt; class LookupResult; @@ -355,6 +356,13 @@ namespace swift { SmallVector, 1> getShorthandShadows(LabeledConditionalStmt *CondStmt, DeclContext *DC = nullptr); + + SourceFile *evaluateFreestandingMacro(FreestandingMacroExpansion *expansion, + StringRef discriminator); + + SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, + CustomAttr *attr, bool passParentContext, + MacroRole role, StringRef discriminator); } #endif diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 9f8965cf7bbd3..f13fd1591b0c3 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -10632,6 +10632,11 @@ MacroDefinition MacroDecl::getDefinition() const { MacroDefinition::forUndefined()); } +void MacroDecl::setDefinition(MacroDefinition definition) { + getASTContext().evaluator.cacheOutput(MacroDefinitionRequest{this}, + std::move(definition)); +} + Optional MacroDecl::getBuiltinKind() const { auto def = getDefinition(); if (def.kind != MacroDefinition::Kind::Builtin) diff --git a/lib/IDE/Utils.cpp b/lib/IDE/Utils.cpp index 776f6a9e54450..6db691f0879ee 100644 --- a/lib/IDE/Utils.cpp +++ b/lib/IDE/Utils.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "swift/IDE/Utils.h" +#include "swift/AST/SourceFile.h" #include "swift/Basic/Edit.h" #include "swift/Basic/Platform.h" #include "swift/Basic/SourceManager.h" @@ -622,6 +623,102 @@ remove(SourceManager &SM, CharSourceRange Range) { accept(SM, Range, ""); } +/// Given the expanded code for a particular macro, perform whitespace +/// adjustments to make the refactoring more suitable for inline insertion. +static StringRef +adjustMacroExpansionWhitespace(GeneratedSourceInfo::Kind kind, + StringRef expandedCode, + llvm::SmallString<64> &scratch) { + scratch.clear(); + + switch (kind) { + case GeneratedSourceInfo::MemberAttributeMacroExpansion: + // Attributes are added to the beginning, add a space to separate from + // any existing. + scratch += expandedCode; + scratch += " "; + return scratch; + + case GeneratedSourceInfo::MemberMacroExpansion: + case GeneratedSourceInfo::PeerMacroExpansion: + case GeneratedSourceInfo::ConformanceMacroExpansion: + // All added to the end. Note that conformances are always expanded as + // extensions, hence treating them the same as peer. + scratch += "\n\n"; + scratch += expandedCode; + scratch += "\n"; + return scratch; + + case GeneratedSourceInfo::ExpressionMacroExpansion: + case GeneratedSourceInfo::FreestandingDeclMacroExpansion: + case GeneratedSourceInfo::AccessorMacroExpansion: + case GeneratedSourceInfo::ReplacedFunctionBody: + case GeneratedSourceInfo::PrettyPrinted: + return expandedCode; + } +} + +void swift::ide::SourceEditConsumer::acceptMacroExpansionBuffer( + SourceManager &SM, unsigned bufferID, SourceFile *containingSF, + bool adjustExpansion, bool includeBufferName) { + auto generatedInfo = SM.getGeneratedSourceInfo(bufferID); + if (!generatedInfo || generatedInfo->originalSourceRange.isInvalid()) + return; + + auto rewrittenBuffer = SM.extractText(generatedInfo->generatedSourceRange); + + // If there's no change, drop the edit entirely. + if (generatedInfo->originalSourceRange.getStart() == + generatedInfo->originalSourceRange.getEnd() && + rewrittenBuffer.empty()) + return; + + SmallString<64> scratchBuffer; + if (adjustExpansion) { + rewrittenBuffer = adjustMacroExpansionWhitespace( + generatedInfo->kind, rewrittenBuffer, scratchBuffer); + } + + // `containingFile` is the file of the actual expansion site, where as + // `originalFile` is the possibly enclosing buffer. Concretely: + // ``` + // // m.swift + // @AddMemberAttributes + // struct Foo { + // // --- expanded from @AddMemberAttributes eg. @_someBufferName --- + // @AddedAttribute + // // --- + // let someMember: Int + // } + // ``` + // + // When expanding `AddedAttribute`, the expansion actually applies to the + // original source (`m.swift`) rather than the buffer of the expansion + // site (`@_someBufferName`). Thus, we need to include the path to the + // original source as well. Note that this path could itself be another + // expansion. + auto originalSourceRange = generatedInfo->originalSourceRange; + SourceFile *originalFile = + containingSF->getParentModule()->getSourceFileContainingLocation( + originalSourceRange.getStart()); + StringRef originalPath; + if (originalFile->getBufferID().hasValue() && + containingSF->getBufferID() != originalFile->getBufferID()) { + originalPath = SM.getIdentifierForBuffer(*originalFile->getBufferID()); + } + + StringRef bufferName; + if (includeBufferName) { + bufferName = SM.getIdentifierForBuffer(bufferID); + } + + accept(SM, {originalPath, + originalSourceRange, + bufferName, + rewrittenBuffer, + {}}); +} + struct swift::ide::SourceEditJsonConsumer::Implementation { llvm::raw_ostream &OS; std::vector AllEdits; diff --git a/lib/IDETool/CMakeLists.txt b/lib/IDETool/CMakeLists.txt index c7555e0cb3d51..34994928c1459 100644 --- a/lib/IDETool/CMakeLists.txt +++ b/lib/IDETool/CMakeLists.txt @@ -4,6 +4,7 @@ add_swift_host_library(swiftIDETool STATIC CompilerInvocation.cpp IDEInspectionInstance.cpp DependencyChecking.cpp + SyntacticMacroExpansion.cpp ) target_link_libraries(swiftIDETool PRIVATE diff --git a/lib/IDETool/SyntacticMacroExpansion.cpp b/lib/IDETool/SyntacticMacroExpansion.cpp new file mode 100644 index 0000000000000..afcffa672721b --- /dev/null +++ b/lib/IDETool/SyntacticMacroExpansion.cpp @@ -0,0 +1,471 @@ +//===--- SyntacticMacroExpansion.cpp --------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "swift/IDETool/SyntacticMacroExpansion.h" +#include "swift/AST/ASTWalker.h" +#include "swift/AST/MacroDefinition.h" +#include "swift/AST/PluginLoader.h" +#include "swift/AST/TypeRepr.h" +#include "swift/Driver/FrontendUtil.h" +#include "swift/Frontend/Frontend.h" +#include "swift/IDE/Utils.h" +#include "swift/Sema/IDETypeChecking.h" + +using namespace swift; +using namespace ide; + +std::shared_ptr +SyntacticMacroExpansion::getInstance(ArrayRef args, + std::string &error) { + // Create and configure a new instance. + auto instance = std::make_shared(); + + bool failed = instance->setup(SwiftExecutablePath, args, Plugins, error); + if (failed) + return nullptr; + + return instance; +} + +bool SyntacticMacroExpansionInstance::setup( + StringRef SwiftExecutablePath, ArrayRef args, + std::shared_ptr plugins, std::string &error) { + SmallString<256> driverPath(SwiftExecutablePath); + llvm::sys::path::remove_filename(driverPath); + llvm::sys::path::append(driverPath, "swiftc"); + + // Setup CompilerInstance to configure the plugin search path correctly. + bool hadError = driver::getSingleFrontendInvocationFromDriverArguments( + driverPath, args, Diags, + [&](ArrayRef frontendArgs) { + return invocation.parseArgs( + frontendArgs, Diags, /*ConfigurationFileBuffers=*/nullptr, + /*workingDirectory=*/{}, SwiftExecutablePath); + }, + /*ForceNoOutput=*/true); + if (hadError) { + error = "failed to setup compiler invocation"; + return true; + } + + // Setup ASTContext. + Ctx.reset(ASTContext::get( + invocation.getLangOptions(), invocation.getTypeCheckerOptions(), + invocation.getSILOptions(), invocation.getSearchPathOptions(), + invocation.getClangImporterOptions(), invocation.getSymbolGraphOptions(), + SourceMgr, Diags)); + registerParseRequestFunctions(Ctx->evaluator); + registerTypeCheckerRequestFunctions(Ctx->evaluator); + + std::unique_ptr pluginLoader = + std::make_unique(*Ctx, /*DepTracker=*/nullptr); + pluginLoader->setRegistry(plugins.get()); + Ctx->setPluginLoader(std::move(pluginLoader)); + + // Create a module where SourceFiles reside. + Identifier ID = Ctx->getIdentifier(invocation.getModuleName()); + TheModule = ModuleDecl::create(ID, *Ctx); + + return false; +} + +SourceFile * +SyntacticMacroExpansionInstance::getSourceFile(llvm::MemoryBuffer *inputBuf) { + + // If there is a SourceFile with the same name and the content, use it. + // Note that this finds the generated source file that was created in the + // previous expansion requests. + if (auto bufID = + SourceMgr.getIDForBufferIdentifier(inputBuf->getBufferIdentifier())) { + if (inputBuf->getBuffer() == SourceMgr.getEntireTextForBuffer(*bufID)) { + SourceLoc bufLoc = SourceMgr.getLocForBufferStart(*bufID); + if (SourceFile *existing = + TheModule->getSourceFileContainingLocation(bufLoc)) { + return existing; + } + } + } + + // Otherwise, create a new SourceFile. + SourceFile *SF = new (getASTContext()) SourceFile( + *TheModule, SourceFileKind::Main, SourceMgr.addMemBufferCopy(inputBuf)); + SF->setImports({}); + TheModule->addFile(*SF); + + return SF; +} + +MacroDecl *SyntacticMacroExpansionInstance::getSynthesizedMacroDecl( + Identifier name, const MacroExpansionSpecifier &expansion) { + auto &ctx = getASTContext(); + + std::string macroID; + + switch (expansion.macroDefinition.kind) { + case MacroDefinition::Kind::External: { + // '.' + // It's safe to use without 'kind' because 'Expanded' always starts with a + // sigil '#' which can't be valid in a module name. + auto external = expansion.macroDefinition.getExternalMacro(); + macroID += external.moduleName.str(); + macroID += "."; + macroID += external.macroTypeName.str(); + break; + } + case MacroDefinition::Kind::Expanded: { + auto expanded = expansion.macroDefinition.getExpanded(); + macroID += expanded.getExpansionText(); + break; + } + case MacroDefinition::Kind::Builtin: + case MacroDefinition::Kind::Invalid: + case MacroDefinition::Kind::Undefined: + assert(false && "invalid macro definition for syntactic expansion"); + macroID += name.str(); + } + + // Reuse cached MacroDecl of the same name if it's already created. + MacroDecl *macro; + auto found = MacroDecls.find(macroID); + if (found != MacroDecls.end()) { + macro = found->second; + } else { + macro = new (ctx) MacroDecl( + /*macroLoc=*/{}, DeclName(name), /*nameLoc=*/{}, + /*genericParams=*/nullptr, /*parameterList=*/nullptr, + /*arrowLoc=*/{}, /*resultType=*/nullptr, + /*definition=*/nullptr, /*parent=*/TheModule); + macro->setImplicit(); + MacroDecls.insert({macroID, macro}); + } + + // Add missing role attributes to MacroDecl. + MacroRoles roles = expansion.macroRoles; + for (auto attr : macro->getAttrs().getAttributes()) { + roles -= attr->getMacroRole(); + } + for (MacroRole role : getAllMacroRoles()) { + if (!roles.contains(role)) + continue; + + MacroSyntax syntax = getFreestandingMacroRoles().contains(role) + ? MacroSyntax::Freestanding + : MacroSyntax::Attached; + + auto *attr = MacroRoleAttr::create(ctx, /*atLoc=*/{}, /*range=*/{}, syntax, + /*lParenLoc=*/{}, role, /*names=*/{}, + /*rParenLoc=*/{}, /*implicit=*/true); + macro->getAttrs().add(attr); + } + + // Set the macro definition. + macro->setDefinition(expansion.macroDefinition); + + return macro; +} + +/// Create a unique name of the expansion. The result is *appended* to \p out. +static void addExpansionDiscriminator(SmallString<32> &out, + const SourceFile *SF, SourceLoc loc, + Optional supplementalLoc = None, + Optional role = None) { + SourceManager &SM = SF->getASTContext().SourceMgr; + + StableHasher hasher = StableHasher::defaultHasher(); + + // Module name. + hasher.combine(SF->getParentModule()->getName().str()); + hasher.combine(uint8_t{0}); + + // File base name. + // Do not use the full path because we want this hash stable. + hasher.combine(llvm::sys::path::filename(SF->getFilename())); + hasher.combine(uint8_t{0}); + + // Line/column. + auto lineColumn = SM.getLineAndColumnInBuffer(loc); + hasher.combine(lineColumn.first); + hasher.combine(lineColumn.second); + + // Supplemental line/column. + if (supplementalLoc.has_value()) { + auto supLineColumn = SM.getLineAndColumnInBuffer(*supplementalLoc); + hasher.combine(supLineColumn.first); + hasher.combine(supLineColumn.second); + } + + // Macro role. + if (role.has_value()) { + hasher.combine(*role); + } + + Fingerprint hash(std::move(hasher)); + out.append(hash.getRawValue()); +} + +/// Perform expansion of the specified freestanding macro using the 'MacroDecl'. +static std::vector +expandFreestandingMacro(MacroDecl *macro, + FreestandingMacroExpansion *expansion) { + std::vector bufferIDs; + + SmallString<32> discriminator; + discriminator.append("__syntactic_macro_"); + addExpansionDiscriminator(discriminator, + expansion->getDeclContext()->getParentSourceFile(), + expansion->getPoundLoc()); + + expansion->setMacroRef(macro); + + SourceFile *expandedSource = + swift::evaluateFreestandingMacro(expansion, discriminator); + if (expandedSource) + bufferIDs.push_back(*expandedSource->getBufferID()); + + return bufferIDs; +} + +/// Perform expansion of the specified decl and the attribute using the +/// 'MacroDecl'. If the macro has multiple roles, evaluate it for all macro +/// roles. +static std::vector +expandAttachedMacro(MacroDecl *macro, CustomAttr *attr, Decl *attachedDecl) { + + std::vector bufferIDs; + auto evaluate = [&](Decl *target, bool passParent, MacroRole role) { + + SmallString<32> discriminator; + discriminator.append("macro_"); + addExpansionDiscriminator(discriminator, + target->getDeclContext()->getParentSourceFile(), + target->getLoc(), attr->getLocation(), role); + + SourceFile *expandedSource = swift::evaluateAttachedMacro( + macro, target, attr, passParent, role, discriminator); + if (expandedSource) + bufferIDs.push_back(*expandedSource->getBufferID()); + }; + + MacroRoles roles = macro->getMacroRoles(); + if (roles.contains(MacroRole::Accessor)) { + if (isa(attachedDecl)) + evaluate(attachedDecl, /*passParent=*/false, MacroRole::Accessor); + } + if (roles.contains(MacroRole::MemberAttribute)) { + if (auto *idc = dyn_cast(attachedDecl)) { + for (auto *member : idc->getParsedMembers()) { + // 'VarDecl' in 'IterableDeclContext' are part of 'PatternBindingDecl'. + if (isa(member)) + continue; + evaluate(member, /*passParent=*/true, MacroRole::MemberAttribute); + } + } + } + if (roles.contains(MacroRole::Member)) { + if (isa(attachedDecl)) + evaluate(attachedDecl, /*passParent=*/false, MacroRole::Member); + } + if (roles.contains(MacroRole::Peer)) { + evaluate(attachedDecl, /*passParent=*/false, MacroRole::Peer); + } + if (roles.contains(MacroRole::Conformance)) { + if (isa(attachedDecl)) + evaluate(attachedDecl, /*passParent=*/false, MacroRole::Conformance); + } + return bufferIDs; +} + +/// Get the name of the custom attribute. This is used to create a dummy +/// MacroDecl. +static Identifier getCustomAttrName(ASTContext &ctx, const CustomAttr *attr) { + TypeRepr *tyR = attr->getTypeRepr(); + if (auto ref = dyn_cast(tyR)) { + return ref->getNameRef().getBaseIdentifier(); + } + + // If the attribute is not an identifier type, create an identifier with its + // textual representation. This is *not* expected to be reachable. + // The only case is like `@Foo?` where the client should not send the + // expansion request on this in the first place. + SmallString<32> name; + llvm::raw_svector_ostream OS(name); + tyR->print(OS); + return ctx.getIdentifier(name); +} + +namespace { + +/// Find macro expansion i.e. '#foo' or '@foo' at the specified source location. +/// If a freestanding expansion (i.e. #foo) is found, the result 'ExpansionNode' +/// only has the node. If an attribute is found, the attribute and the attached +/// decl object is returned. +struct ExpansionNode { + CustomAttr *attribute; + ASTNode node; +}; +class MacroExpansionFinder : public ASTWalker { + SourceManager &SM; + SourceLoc locToResolve; + llvm::Optional result; + + bool rangeContainsLocToResolve(SourceRange Range) const { + return SM.rangeContainsTokenLoc(Range, locToResolve); + } + +public: + MacroExpansionFinder(SourceManager &SM, SourceLoc locToResolve) + : SM(SM), locToResolve(locToResolve) {} + + llvm::Optional getResult() const { return result; } + + MacroWalking getMacroWalkingBehavior() const override { + return MacroWalking::None; + } + + PreWalkAction walkToDeclPre(Decl *D) override { + // Visit all 'VarDecl' because 'getSourceRangeIncludingAttrs()' doesn't + // include its attribute ranges (because attributes are part of PBD.) + if (!isa(D) && + !rangeContainsLocToResolve(D->getSourceRangeIncludingAttrs())) { + return Action::SkipChildren(); + } + + // Check the attributes. + for (DeclAttribute *attr : D->getAttrs()) { + if (auto customAttr = dyn_cast(attr)) { + SourceRange nameRange(customAttr->getRangeWithAt().Start, + customAttr->getTypeExpr()->getEndLoc()); + if (rangeContainsLocToResolve(nameRange)) { + result = ExpansionNode{customAttr, ASTNode(D)}; + return Action::Stop(); + } + } + } + + // Check 'MacroExpansionDecl'. + if (auto med = dyn_cast(D)) { + SourceRange nameRange(med->getExpansionInfo()->SigilLoc, + med->getMacroNameLoc().getEndLoc()); + if (rangeContainsLocToResolve(nameRange)) { + result = ExpansionNode{nullptr, ASTNode(med)}; + return Action::Stop(); + } + } + + return Action::Continue(); + } + + PreWalkResult walkToExprPre(Expr *E) override { + if (!rangeContainsLocToResolve(E->getSourceRange())) { + return Action::SkipChildren(E); + } + + // Check 'MacroExpansionExpr'. + if (auto mee = dyn_cast(E)) { + SourceRange nameRange(mee->getExpansionInfo()->SigilLoc, + mee->getMacroNameLoc().getEndLoc()); + if (rangeContainsLocToResolve(nameRange)) { + result = ExpansionNode{nullptr, ASTNode(mee)}; + return Action::Stop(); + } + } + + return Action::Continue(E); + } + + PreWalkResult walkToStmtPre(Stmt *S) override { + if (!rangeContainsLocToResolve(S->getSourceRange())) { + return Action::SkipChildren(S); + } + return Action::Continue(S); + } + PreWalkResult + walkToArgumentListPre(ArgumentList *AL) override { + if (!rangeContainsLocToResolve(AL->getSourceRange())) { + return Action::SkipChildren(AL); + } + return Action::Continue(AL); + } + PreWalkAction walkToParameterListPre(ParameterList *PL) override { + if (!rangeContainsLocToResolve(PL->getSourceRange())) { + return Action::SkipChildren(); + } + return Action::Continue(); + } + PreWalkAction walkToTypeReprPre(TypeRepr *T) override { + // TypeRepr cannot have macro expansions in it. + return Action::SkipChildren(); + } +}; +} // namespace + +void SyntacticMacroExpansionInstance::expand( + SourceFile *SF, const MacroExpansionSpecifier &expansion, + SourceEditConsumer &consumer) { + + // Find the expansion at 'expantion.offset'. + MacroExpansionFinder expansionFinder( + SourceMgr, + SourceMgr.getLocForOffset(*SF->getBufferID(), expansion.offset)); + SF->walk(expansionFinder); + auto expansionNode = expansionFinder.getResult(); + if (!expansionNode) + return; + + // Expand the macro. + std::vector bufferIDs; + if (auto *attr = expansionNode->attribute) { + // Attached macros. + MacroDecl *macro = getSynthesizedMacroDecl( + getCustomAttrName(getASTContext(), attr), expansion); + auto *attachedTo = expansionNode->node.get(); + bufferIDs = expandAttachedMacro(macro, attr, attachedTo); + + // For an attached macro, remove the custom attribute; it's been fully + // subsumed by its expansions. + SourceRange range = attr->getRangeWithAt(); + auto charRange = Lexer::getCharSourceRangeFromSourceRange(SourceMgr, range); + consumer.remove(SourceMgr, charRange); + } else { + // Freestanding macros. + FreestandingMacroExpansion *freestanding; + auto node = expansionNode->node; + if (node.is()) { + freestanding = cast(node.get()); + } else { + freestanding = cast(node.get()); + } + + MacroDecl *macro = getSynthesizedMacroDecl( + freestanding->getMacroName().getBaseIdentifier(), expansion); + bufferIDs = expandFreestandingMacro(macro, freestanding); + } + + // Send all edits to the consumer. + for (unsigned bufferID : bufferIDs) { + consumer.acceptMacroExpansionBuffer(SourceMgr, bufferID, SF, + /*adjust=*/false, + /*includeBufferName=*/false); + } +} + +void SyntacticMacroExpansionInstance::expandAll( + llvm::MemoryBuffer *inputBuf, ArrayRef expansions, + SourceEditConsumer &consumer) { + + // Create a source file. + SourceFile *SF = getSourceFile(inputBuf); + + for (const auto &expansion : expansions) { + expand(SF, expansion, consumer); + } +} diff --git a/lib/Refactoring/Refactoring.cpp b/lib/Refactoring/Refactoring.cpp index 515b96a0e50e7..5cda1ef0ccf58 100644 --- a/lib/Refactoring/Refactoring.cpp +++ b/lib/Refactoring/Refactoring.cpp @@ -8754,40 +8754,6 @@ getMacroExpansionBuffers(SourceManager &sourceMgr, ResolvedCursorInfoPtr Info) { return {}; } -/// Given the expanded code for a particular macro, perform whitespace -/// adjustments to make the refactoring more suitable for inline insertion. -static StringRef adjustMacroExpansionWhitespace( - GeneratedSourceInfo::Kind kind, StringRef expandedCode, - llvm::SmallString<64> &scratch) { - scratch.clear(); - - switch (kind) { - case GeneratedSourceInfo::MemberAttributeMacroExpansion: - // Attributes are added to the beginning, add a space to separate from - // any existing. - scratch += expandedCode; - scratch += " "; - return scratch; - - case GeneratedSourceInfo::MemberMacroExpansion: - case GeneratedSourceInfo::PeerMacroExpansion: - case GeneratedSourceInfo::ConformanceMacroExpansion: - // All added to the end. Note that conformances are always expanded as - // extensions, hence treating them the same as peer. - scratch += "\n\n"; - scratch += expandedCode; - scratch += "\n"; - return scratch; - - case GeneratedSourceInfo::ExpressionMacroExpansion: - case GeneratedSourceInfo::FreestandingDeclMacroExpansion: - case GeneratedSourceInfo::AccessorMacroExpansion: - case GeneratedSourceInfo::ReplacedFunctionBody: - case GeneratedSourceInfo::PrettyPrinted: - return expandedCode; - } -} - static bool expandMacro(SourceManager &SM, ResolvedCursorInfoPtr cursorInfo, SourceEditConsumer &editConsumer, bool adjustExpansion) { auto bufferIDs = getMacroExpansionBuffers(SM, cursorInfo); @@ -8799,68 +8765,19 @@ static bool expandMacro(SourceManager &SM, ResolvedCursorInfoPtr cursorInfo, return true; // Send all of the rewritten buffer snippets. - CustomAttr *attachedMacroAttr = nullptr; - SmallString<64> scratchBuffer; for (auto bufferID: bufferIDs) { - auto generatedInfo = SM.getGeneratedSourceInfo(bufferID); - if (!generatedInfo || generatedInfo->originalSourceRange.isInvalid()) - continue; - - auto rewrittenBuffer = SM.extractText(generatedInfo->generatedSourceRange); - - // If there's no change, drop the edit entirely. - if (generatedInfo->originalSourceRange.getStart() == - generatedInfo->originalSourceRange.getEnd() && - rewrittenBuffer.empty()) - continue; - - if (adjustExpansion) { - rewrittenBuffer = adjustMacroExpansionWhitespace(generatedInfo->kind, rewrittenBuffer, scratchBuffer); - } - - // `containingFile` is the file of the actual expansion site, where as - // `originalFile` is the possibly enclosing buffer. Concretely: - // ``` - // // m.swift - // @AddMemberAttributes - // struct Foo { - // // --- expanded from @AddMemberAttributes eg. @_someBufferName --- - // @AddedAttribute - // // --- - // let someMember: Int - // } - // ``` - // - // When expanding `AddedAttribute`, the expansion actually applies to the - // original source (`m.swift`) rather than the buffer of the expansion - // site (`@_someBufferName`). Thus, we need to include the path to the - // original source as well. Note that this path could itself be another - // expansion. - auto originalSourceRange = generatedInfo->originalSourceRange; - SourceFile *originalFile = - containingSF->getParentModule()->getSourceFileContainingLocation(originalSourceRange.getStart()); - StringRef originalPath; - if (originalFile->getBufferID().hasValue() && - containingSF->getBufferID() != originalFile->getBufferID()) { - originalPath = SM.getIdentifierForBuffer(*originalFile->getBufferID()); - } - - editConsumer.accept(SM, {originalPath, - originalSourceRange, - SM.getIdentifierForBuffer(bufferID), - rewrittenBuffer, - {}}); - - if (generatedInfo->attachedMacroCustomAttr && !attachedMacroAttr) - attachedMacroAttr = generatedInfo->attachedMacroCustomAttr; + editConsumer.acceptMacroExpansionBuffer(SM, bufferID, containingSF, + adjustExpansion, /*includeBufferName=*/true); } // For an attached macro, remove the custom attribute; it's been fully // subsumed by its expansions. - if (attachedMacroAttr) { + if (auto attrRef = + cast(cursorInfo)->getCustomAttrRef()) { + const CustomAttr *attachedMacroAttr = attrRef->first; SourceRange range = attachedMacroAttr->getRangeWithAt(); auto charRange = Lexer::getCharSourceRangeFromSourceRange(SM, range); - editConsumer.accept(SM, charRange, StringRef()); + editConsumer.remove(SM, charRange); } return false; diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 54e3a62a3a3f8..6fb677cbefcc7 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -39,6 +39,7 @@ #include "swift/Demangling/Demangler.h" #include "swift/Demangling/ManglingMacros.h" #include "swift/Parse/Lexer.h" +#include "swift/Sema/IDETypeChecking.h" #include "swift/Subsystems.h" #include "llvm/Config/config.h" @@ -861,7 +862,8 @@ createMacroSourceFile(std::unique_ptr buffer, /// Evaluate the given freestanding macro expansion. static SourceFile * -evaluateFreestandingMacro(FreestandingMacroExpansion *expansion) { +evaluateFreestandingMacro(FreestandingMacroExpansion *expansion, + StringRef discriminatorStr = "") { auto *dc = expansion->getDeclContext(); ASTContext &ctx = dc->getASTContext(); SourceLoc loc = expansion->getPoundLoc(); @@ -889,6 +891,8 @@ evaluateFreestandingMacro(FreestandingMacroExpansion *expansion) { /// The discriminator used for the macro. LazyValue discriminator([&]() -> std::string { + if (!discriminatorStr.empty()) + return discriminatorStr.str(); #if SWIFT_SWIFT_PARSER Mangle::ASTMangler mangler; return mangler.mangleMacroExpansion(expansion); @@ -983,7 +987,7 @@ evaluateFreestandingMacro(FreestandingMacroExpansion *expansion) { } Optional swift::expandMacroExpr(MacroExpansionExpr *mee) { - SourceFile *macroSourceFile = evaluateFreestandingMacro(mee); + SourceFile *macroSourceFile = ::evaluateFreestandingMacro(mee); if (!macroSourceFile) return None; @@ -1043,7 +1047,7 @@ Optional swift::expandMacroExpr(MacroExpansionExpr *mee) { /// Expands the given macro expansion declaration. Optional swift::expandFreestandingMacro(MacroExpansionDecl *med) { - SourceFile *macroSourceFile = evaluateFreestandingMacro(med); + SourceFile *macroSourceFile = ::evaluateFreestandingMacro(med); if (!macroSourceFile) return None; @@ -1065,9 +1069,10 @@ swift::expandFreestandingMacro(MacroExpansionDecl *med) { return *macroSourceFile->getBufferID(); } -static SourceFile * -evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, CustomAttr *attr, - bool passParentContext, MacroRole role) { +static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, + CustomAttr *attr, + bool passParentContext, MacroRole role, + StringRef discriminatorStr = "") { DeclContext *dc; if (role == MacroRole::Peer) { dc = attachedTo->getDeclContext(); @@ -1117,6 +1122,8 @@ evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, CustomAttr *attr, /// The discriminator used for the macro. LazyValue discriminator([&]() -> std::string { + if (!discriminatorStr.empty()) + return discriminatorStr.str(); #if SWIFT_SWIFT_PARSER Mangle::ASTMangler mangler; return mangler.mangleAttachedMacroExpansion(attachedTo, attr, role); @@ -1228,9 +1235,9 @@ Optional swift::expandAccessors( AbstractStorageDecl *storage, CustomAttr *attr, MacroDecl *macro ) { // Evaluate the macro. - auto macroSourceFile = evaluateAttachedMacro(macro, storage, attr, - /*passParentContext*/false, - MacroRole::Accessor); + auto macroSourceFile = + ::evaluateAttachedMacro(macro, storage, attr, + /*passParentContext=*/false, MacroRole::Accessor); if (!macroSourceFile) return None; @@ -1279,9 +1286,9 @@ ArrayRef ExpandAccessorMacros::evaluate( Optional swift::expandAttributes(CustomAttr *attr, MacroDecl *macro, Decl *member) { // Evaluate the macro. - auto macroSourceFile = evaluateAttachedMacro(macro, member, attr, - /*passParentContext*/true, - MacroRole::MemberAttribute); + auto macroSourceFile = ::evaluateAttachedMacro(macro, member, attr, + /*passParentContext=*/true, + MacroRole::MemberAttribute); if (!macroSourceFile) return None; @@ -1304,9 +1311,9 @@ swift::expandAttributes(CustomAttr *attr, MacroDecl *macro, Decl *member) { Optional swift::expandMembers(CustomAttr *attr, MacroDecl *macro, Decl *decl) { // Evaluate the macro. - auto macroSourceFile = evaluateAttachedMacro(macro, decl, attr, - /*passParentContext*/false, - MacroRole::Member); + auto macroSourceFile = + ::evaluateAttachedMacro(macro, decl, attr, + /*passParentContext=*/false, MacroRole::Member); if (!macroSourceFile) return None; @@ -1331,9 +1338,9 @@ swift::expandMembers(CustomAttr *attr, MacroDecl *macro, Decl *decl) { Optional swift::expandPeers(CustomAttr *attr, MacroDecl *macro, Decl *decl) { - auto macroSourceFile = evaluateAttachedMacro(macro, decl, attr, - /*passParentContext*/false, - MacroRole::Peer); + auto macroSourceFile = + ::evaluateAttachedMacro(macro, decl, attr, + /*passParentContext=*/false, MacroRole::Peer); if (!macroSourceFile) return None; @@ -1357,10 +1364,9 @@ ExpandConformanceMacros::evaluate(Evaluator &evaluator, Optional swift::expandConformances(CustomAttr *attr, MacroDecl *macro, NominalTypeDecl *nominal) { - auto macroSourceFile = - evaluateAttachedMacro(macro, nominal, attr, - /*passParentContext*/false, - MacroRole::Conformance); + auto macroSourceFile = ::evaluateAttachedMacro(macro, nominal, attr, + /*passParentContext=*/false, + MacroRole::Conformance); if (!macroSourceFile) return None; @@ -1447,3 +1453,19 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator, return macroExpansion->getMacroRef(); } + +// MARK: for IDE. + +SourceFile *swift::evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, + CustomAttr *attr, + bool passParentContext, MacroRole role, + StringRef discriminator) { + return ::evaluateAttachedMacro(macro, attachedTo, attr, passParentContext, + role, discriminator); +} + +SourceFile * +swift::evaluateFreestandingMacro(FreestandingMacroExpansion *expansion, + StringRef discriminator) { + return ::evaluateFreestandingMacro(expansion, discriminator); +} diff --git a/test/SourceKit/Macros/syntactic_expansion.swift b/test/SourceKit/Macros/syntactic_expansion.swift new file mode 100644 index 0000000000000..17d3f7ae8b032 --- /dev/null +++ b/test/SourceKit/Macros/syntactic_expansion.swift @@ -0,0 +1,82 @@ +//--- test.swift +@DelegatedConformance +@wrapAllProperties +struct Generic { + + @myPropertyWrapper + @otherAttr + var value: Int + + func member() {} + var otherVal: Int = 1 + + #bitwidthNumberedStructs("blah") +} + +//--- DelegatedConformance.json +{ + key.macro_roles: [source.lang.swift.macro_role.conformance, source.lang.swift.macro_role.member], + key.modulename: "MacroDefinition", + key.typename: "DelegatedConformanceMacro", +} + +//--- myPropertyWrapper.json +{ + key.macro_roles: [source.lang.swift.macro_role.accessor, source.lang.swift.macro_role.peer], + key.modulename: "MacroDefinition", + key.typename: "PropertyWrapperMacro", +} + +//--- wrapAllProperties.json +{ + key.macro_roles: [source.lang.swift.macro_role.member_attribute], + key.modulename: "MacroDefinition", + key.typename: "WrapAllProperties", +} + +//--- bitwidthNumberedStructs.json +{ + key.macro_roles: [source.lang.swift.macro_role.declaration], + key.modulename: "MacroDefinition", + key.typename: "DefineBitwidthNumberedStructsMacro", +} + +//--- dummy.script +// REQUIRES: swift_swift_parser + +// RUN: %empty-directory(%t) +// RUN: mkdir -p %t/plugins +// RUN: split-file %s %t + +//##-- Prepare the macro plugin. +// RUN: %host-build-swift -swift-version 5 -emit-library -o %t/plugins/%target-library-name(MacroDefinition) -module-name=MacroDefinition %S/../../Macros/Inputs/syntax_macro_definitions.swift -g -no-toolchain-stdlib-rpath + +// RUN: %sourcekitd-test \ +// RUN: -shell -- echo '### 1' \ +// RUN: == \ +// RUN: -req=syntactic-expandmacro \ +// RUN: -req-opts=1:1:%t/DelegatedConformance.json \ +// RUN: -req-opts=5:3:%t/myPropertyWrapper.json \ +// RUN: -req-opts=2:1:%t/wrapAllProperties.json \ +// RUN: -req-opts=12:3:%t/bitwidthNumberedStructs.json \ +// RUN: %t/test.swift \ +// RUN: -- \ +// RUN: %t/test.swift \ +// RUN: -plugin-path %t/plugins -Xfrontend -dump-macro-expansions \ +// RUN: -module-name TestModule \ +// RUN: == \ +// RUN: -shell -- echo '### 2' \ +// RUN: == \ +// RUN: -req=syntactic-expandmacro \ +// RUN: -req-opts=12:3:%t/bitwidthNumberedStructs.json \ +// RUN: -req-opts=2:1:%t/wrapAllProperties.json \ +// RUN: -req-opts=5:3:%t/myPropertyWrapper.json \ +// RUN: -req-opts=1:1:%t/DelegatedConformance.json \ +// RUN: %t/test.swift \ +// RUN: -- \ +// RUN: %t/test.swift \ +// RUN: -plugin-path %t/plugins -Xfrontend -dump-macro-expansions \ +// RUN: -module-name TestModule \ +// RUN: | tee %t.response + +// RUN: diff -u %s.expected %t.response diff --git a/test/SourceKit/Macros/syntactic_expansion.swift.expected b/test/SourceKit/Macros/syntactic_expansion.swift.expected new file mode 100644 index 0000000000000..90a0ccf5b0f65 --- /dev/null +++ b/test/SourceKit/Macros/syntactic_expansion.swift.expected @@ -0,0 +1,108 @@ +### 1 +source.edit.kind.active: + 1:1-1:22 "" +source.edit.kind.active: + 13:1-13:1 "static func requirement() where Element : P { + Element.requirement() +}" +source.edit.kind.active: + 13:2-13:2 "extension Generic : P where Element: P {}" +source.edit.kind.active: + 5:3-5:21 "" +source.edit.kind.active: + 7:17-7:17 "{ + get { + _value.wrappedValue + } + + set { + _value.wrappedValue = newValue + } +}" +source.edit.kind.active: + 7:12-7:12 "var _value: MyWrapperThingy" +source.edit.kind.active: + 2:1-2:19 "" +source.edit.kind.active: + 7:3-7:3 "@Wrapper" +source.edit.kind.active: + 10:3-10:3 "@Wrapper" +source.edit.kind.active: + 12:3-12:35 "struct blah8 { + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu_() { + } + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu0_() { + } +} +struct blah16 { + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu1_() { + } + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu2_() { + } +} +struct blah32 { + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu3_() { + } + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu4_() { + } +} +struct blah64 { + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu5_() { + } + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu6_() { + } +}" +### 2 +source.edit.kind.active: + 12:3-12:35 "struct blah8 { + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu_() { + } + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu0_() { + } +} +struct blah16 { + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu1_() { + } + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu2_() { + } +} +struct blah32 { + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu3_() { + } + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu4_() { + } +} +struct blah64 { + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu5_() { + } + func __syntactic_macro_a01f07de3e9ee8585adf794ccd6dec8c6methodfMu6_() { + } +}" +source.edit.kind.active: + 2:1-2:19 "" +source.edit.kind.active: + 7:3-7:3 "@Wrapper" +source.edit.kind.active: + 10:3-10:3 "@Wrapper" +source.edit.kind.active: + 5:3-5:21 "" +source.edit.kind.active: + 7:17-7:17 "{ + get { + _value.wrappedValue + } + + set { + _value.wrappedValue = newValue + } +}" +source.edit.kind.active: + 7:12-7:12 "var _value: MyWrapperThingy" +source.edit.kind.active: + 1:1-1:22 "" +source.edit.kind.active: + 13:1-13:1 "static func requirement() where Element : P { + Element.requirement() +}" +source.edit.kind.active: + 13:2-13:2 "extension Generic : P where Element: P {}" diff --git a/tools/SourceKit/include/SourceKit/Core/LangSupport.h b/tools/SourceKit/include/SourceKit/Core/LangSupport.h index 5dccd87dd59a0..4203393ca53b2 100644 --- a/tools/SourceKit/include/SourceKit/Core/LangSupport.h +++ b/tools/SourceKit/include/SourceKit/Core/LangSupport.h @@ -220,6 +220,64 @@ struct RawCharSourceRange { unsigned Length; }; +enum class MacroRole : uint8_t { + // This should align with 'swift::MacroRole'. + Expression = 0x01, + Declaration = 0x02, + Accessor = 0x04, + MemberAttribute = 0x08, + Member = 0x10, + Peer = 0x20, + Conformance = 0x40, + CodeItem = 0x80, +}; +using MacroRoles = swift::OptionSet; + +struct MacroExpansionInfo { + // See swift::ExternalMacroReference. + struct ExternalMacroReference { + std::string moduleName; + std::string typeName; + + ExternalMacroReference(StringRef moduleName, StringRef typeName) + : moduleName(moduleName), typeName(typeName){}; + }; + // See swift::ExpandedMacroDefinition. + struct ExpandedMacroDefinition { + // 'Replacement.range' references some part of code in 'expansionText'. + // 'expansionText' will be replaced by the 'parameterIndex'-th argument of + // the macro. + struct Replacement { + RawCharSourceRange range; + unsigned parameterIndex; + Replacement(RawCharSourceRange range, unsigned parameterIndex) + : range(range), parameterIndex(parameterIndex) {} + }; + std::string expansionText; + std::vector replacements; + + ExpandedMacroDefinition(StringRef expansionText) + : expansionText(expansionText), replacements(){}; + }; + + // Offset of the macro expansion syntax (i.e. attribute or #) from + // the start of the source file. + unsigned offset; + + // Macro roles. + MacroRoles roles; + + // Tagged union of macro definition. + std::variant macroDefinition; + + MacroExpansionInfo(unsigned offset, MacroRoles roles, + ExternalMacroReference macroRef) + : offset(offset), roles(roles), macroDefinition(macroRef) {} + MacroExpansionInfo(unsigned offset, MacroRoles roles, + ExpandedMacroDefinition definition) + : offset(offset), roles(roles), macroDefinition(definition) {} +}; + /// Stores information about a given buffer, including its name and, if /// generated, its source text and original location. struct BufferInfo { @@ -1128,6 +1186,11 @@ class LangSupport { ConformingMethodListConsumer &Consumer, Optional vfsOptions) = 0; + virtual void expandMacroSyntactically(llvm::MemoryBuffer *inputBuf, + ArrayRef args, + ArrayRef expansions, + CategorizedEditsReceiver receiver) = 0; + virtual void performCompile(StringRef Name, ArrayRef Args, Optional vfsOptions, diff --git a/tools/SourceKit/lib/SwiftLang/CMakeLists.txt b/tools/SourceKit/lib/SwiftLang/CMakeLists.txt index 38725d140d215..31f596e5a0019 100644 --- a/tools/SourceKit/lib/SwiftLang/CMakeLists.txt +++ b/tools/SourceKit/lib/SwiftLang/CMakeLists.txt @@ -11,6 +11,7 @@ add_sourcekit_library(SourceKitSwiftLang SwiftLangSupport.cpp SwiftMangling.cpp SwiftSourceDocInfo.cpp + SwiftSyntacticMacroExpansion.cpp SwiftTypeContextInfo.cpp LLVM_LINK_COMPONENTS ${LLVM_TARGETS_TO_BUILD} bitreader diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp index a77254e38d6ce..6f8b019d2db92 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp @@ -29,6 +29,7 @@ #include "swift/IDE/SyntaxModel.h" #include "swift/IDE/Utils.h" #include "swift/IDETool/IDEInspectionInstance.h" +#include "swift/IDETool/SyntacticMacroExpansion.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/Preprocessor.h" @@ -303,6 +304,9 @@ SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx) // By default, just use the in-memory cache. CCCache->inMemory = std::make_unique(); + SyntacticMacroExpansions = + std::make_shared(SwiftExecutablePath, Plugins); + // Provide a default file system provider. setFileSystemProvider("in-memory-vfs", std::make_unique()); } diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h index 5fd15b589bd20..ad4a8a7fe5769 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h @@ -55,6 +55,7 @@ namespace ide { class IDEInspectionInstance; class OnDiskCodeCompletionCache; class SourceEditConsumer; + class SyntacticMacroExpansion; enum class CodeCompletionDeclKind : uint8_t; enum class SyntaxNodeKind : uint8_t; enum class SyntaxStructureKind : uint8_t; @@ -366,6 +367,7 @@ class SwiftLangSupport : public LangSupport { llvm::StringMap> FileSystemProviders; std::shared_ptr IDEInspectionInst; std::shared_ptr CompileManager; + std::shared_ptr SyntacticMacroExpansions; public: explicit SwiftLangSupport(SourceKit::Context &SKCtx); @@ -756,6 +758,11 @@ class SwiftLangSupport : public LangSupport { ConformingMethodListConsumer &Consumer, Optional vfsOptions) override; + void expandMacroSyntactically(llvm::MemoryBuffer *inputBuf, + ArrayRef args, + ArrayRef expansions, + CategorizedEditsReceiver receiver) override; + void performCompile(StringRef Name, ArrayRef Args, Optional vfsOptions, diff --git a/tools/SourceKit/lib/SwiftLang/SwiftSyntacticMacroExpansion.cpp b/tools/SourceKit/lib/SwiftLang/SwiftSyntacticMacroExpansion.cpp new file mode 100644 index 0000000000000..170ca176bc7f0 --- /dev/null +++ b/tools/SourceKit/lib/SwiftLang/SwiftSyntacticMacroExpansion.cpp @@ -0,0 +1,94 @@ +//===--- SwiftSyntaxMacro.cpp ---------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "SwiftLangSupport.h" +#include "swift/AST/MacroDefinition.h" +#include "swift/Frontend/Frontend.h" +#include "swift/Frontend/PrintingDiagnosticConsumer.h" +#include "swift/IDE/TypeContextInfo.h" +#include "swift/IDETool/SyntacticMacroExpansion.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Comment.h" +#include "clang/AST/Decl.h" + +using namespace SourceKit; +using namespace swift; +using namespace ide; + +void SwiftLangSupport::expandMacroSyntactically( + llvm::MemoryBuffer *inputBuf, ArrayRef args, + ArrayRef reqExpansions, + CategorizedEditsReceiver receiver) { + + std::string error; + auto instance = SyntacticMacroExpansions->getInstance(args, error); + if (!instance) { + return receiver( + RequestResult>::fromError(error)); + } + auto &ctx = instance->getASTContext(); + + // Convert 'SourceKit::MacroExpansionInfo' to 'ide::MacroExpansionSpecifier'. + SmallVector expansions; + for (auto &req : reqExpansions) { + unsigned offset = req.offset; + + swift::MacroRoles macroRoles; + if (req.roles.contains(SourceKit::MacroRole::Expression)) + macroRoles |= swift::MacroRole::Expression; + if (req.roles.contains(SourceKit::MacroRole::Declaration)) + macroRoles |= swift::MacroRole::Declaration; + if (req.roles.contains(SourceKit::MacroRole::CodeItem)) + macroRoles |= swift::MacroRole::CodeItem; + if (req.roles.contains(SourceKit::MacroRole::Accessor)) + macroRoles |= swift::MacroRole::Accessor; + if (req.roles.contains(SourceKit::MacroRole::MemberAttribute)) + macroRoles |= swift::MacroRole::MemberAttribute; + if (req.roles.contains(SourceKit::MacroRole::Member)) + macroRoles |= swift::MacroRole::Member; + if (req.roles.contains(SourceKit::MacroRole::Peer)) + macroRoles |= swift::MacroRole::Peer; + if (req.roles.contains(SourceKit::MacroRole::Conformance)) + macroRoles |= swift::MacroRole::Conformance; + + MacroDefinition definition = [&] { + if (auto *expanded = + std::get_if( + &req.macroDefinition)) { + SmallVector replacements; + for (auto &reqReplacement : expanded->replacements) { + replacements.push_back( + {/*startOffset=*/reqReplacement.range.Offset, + /*endOffset=*/reqReplacement.range.Offset + + reqReplacement.range.Length, + /*parameterIndex=*/reqReplacement.parameterIndex}); + } + return MacroDefinition::forExpanded(ctx, expanded->expansionText, + replacements); + } else if (auto *externalRef = + std::get_if( + &req.macroDefinition)) { + return MacroDefinition::forExternal( + ctx.getIdentifier(externalRef->moduleName), + ctx.getIdentifier(externalRef->typeName)); + } else { + return MacroDefinition::forUndefined(); + } + }(); + + expansions.push_back({offset, macroRoles, definition}); + } + + RequestRefactoringEditConsumer consumer(receiver); + instance->expandAll(inputBuf, expansions, consumer); + // consumer automatically send the results on destruction. +} diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp index e92982d18ea0a..d6f97c5d446ef 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp @@ -153,6 +153,7 @@ bool TestOptions::parseArgs(llvm::ArrayRef Args) { .Case("diags", SourceKitRequest::Diagnostics) .Case("compile", SourceKitRequest::Compile) .Case("compile.close", SourceKitRequest::CompileClose) + .Case("syntactic-expandmacro", SourceKitRequest::SyntacticMacroExpansion) #define SEMANTIC_REFACTORING(KIND, NAME, ID) .Case("refactoring." #ID, SourceKitRequest::KIND) #include "swift/Refactoring/RefactoringKinds.def" .Default(SourceKitRequest::None); @@ -203,6 +204,7 @@ bool TestOptions::parseArgs(llvm::ArrayRef Args) { << "- collect-type\n" << "- global-config\n" << "- dependency-updated\n" + << "- syntactic-expandmacro\n" #define SEMANTIC_REFACTORING(KIND, NAME, ID) << "- refactoring." #ID "\n" #include "swift/Refactoring/RefactoringKinds.def" "\n"; diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h index d8b9868dc28c9..ace443a58fa86 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h @@ -70,6 +70,7 @@ enum class SourceKitRequest { Diagnostics, Compile, CompileClose, + SyntacticMacroExpansion, #define SEMANTIC_REFACTORING(KIND, NAME, ID) KIND, #include "swift/Refactoring/RefactoringKinds.def" }; diff --git a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp index 2db2dc7206c92..84f4ae8482940 100644 --- a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp @@ -416,6 +416,46 @@ static bool readPopularAPIList(StringRef filename, return false; } +/// Read '-req-opts' for syntactic macro expansion request and apply it to 'req' +/// object. +/// The format of the argument is '-req-opts={line}:{column}:{path}' +/// where {path} is a path to a JSON file that has macro roles and definition. +/// {line} and {column} is resolved to 'offset' using \p inputBuf . +static bool setSyntacticMacroExpansions(sourcekitd_object_t req, + TestOptions &opts, + llvm::MemoryBuffer *inputBuf) { + SmallVector expansions; + for (std::string &opt : opts.RequestOptions) { + SmallVector args; + StringRef(opt).split(args, ":"); + unsigned line, column; + + if (args.size() != 3 || args[0].getAsInteger(10, line) || + args[1].getAsInteger(10, column)) { + llvm::errs() << "-req-opts should be {line}:{column}:{json-path}"; + return true; + } + unsigned offset = resolveFromLineCol(line, column, inputBuf); + + auto Buffer = getBufferForFilename(args[2], opts.VFSFiles)->getBuffer(); + char *Err = nullptr; + auto expansion = sourcekitd_request_create_from_yaml(Buffer.data(), &Err); + if (!expansion) { + assert(Err); + llvm::errs() << Err; + free(Err); + return true; + } + sourcekitd_request_dictionary_set_int64(expansion, KeyOffset, + int64_t(offset)); + expansions.push_back(expansion); + } + sourcekitd_request_dictionary_set_value( + req, KeyExpansions, + sourcekitd_request_array_create(expansions.data(), expansions.size())); + return false; +} + namespace { class PrintingTimer { std::string desc; @@ -1107,6 +1147,12 @@ static int handleTestInvocation(TestOptions Opts, TestOptions &InitOpts) { sourcekitd_request_dictionary_set_string(Req, KeyName, SemaName.c_str()); sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestCompileClose); break; + + case SourceKitRequest::SyntacticMacroExpansion: + sourcekitd_request_dictionary_set_uid(Req, KeyRequest, + RequestSyntacticMacroExpansion); + setSyntacticMacroExpansions(Req, Opts, SourceBuf.get()); + break; } if (!Opts.SourceFile.empty()) { @@ -1521,6 +1567,7 @@ static bool handleResponse(sourcekitd_response_t Resp, const TestOptions &Opts, #define SEMANTIC_REFACTORING(KIND, NAME, ID) case SourceKitRequest::KIND: #include "swift/Refactoring/RefactoringKinds.def" case SourceKitRequest::SyntacticRename: + case SourceKitRequest::SyntacticMacroExpansion: printSyntacticRenameEdits(Info, llvm::outs()); break; case SourceKitRequest::FindRenameRanges: diff --git a/tools/SourceKit/tools/sourcekitd/lib/Service/Requests.cpp b/tools/SourceKit/tools/sourcekitd/lib/Service/Requests.cpp index 94686abdf8567..d0ad86c6ed704 100644 --- a/tools/SourceKit/tools/sourcekitd/lib/Service/Requests.cpp +++ b/tools/SourceKit/tools/sourcekitd/lib/Service/Requests.cpp @@ -1824,6 +1824,134 @@ handleRequestDiagnostics(const RequestDict &Req, }); } +/// Expand macros in the specified source file syntactically. +/// +/// Request would look like: +/// { +/// key.compilerargs: [] +/// key.sourcefile: +/// key.sourcetext: (optional) +/// key.expansions: [...] +/// } +/// 'compilerargs' is used for plugin search paths. +/// 'expansion specifier' is +/// { +/// key.offset: +/// key.modulename: +/// key.typename: +/// key.macro_roles: [...] +/// } +/// +/// Sends the results as a 'CategorizedEdits'. +/// Note that, unlike refactoring, each edit doesn't have 'key.buffer_name'. +/// FIXME: Support nested expansion. +static void handleRequestSyntacticMacroExpansion( + const RequestDict &req, SourceKitCancellationToken cancellationToken, + ResponseReceiver rec) { + + Optional vfsOptions = getVFSOptions(req); + std::unique_ptr inputBuf = + getInputBufForRequestOrEmitError(req, vfsOptions, rec); + if (!inputBuf) + return; + + SmallVector args; + if (getCompilerArgumentsForRequestOrEmitError(req, args, rec)) + return; + + // key.expansions: [ + // { key.offset: 42, + // key.macro_roles: [source.lang.swift.macrorole.conformance, + // source.lang.swift.macrorole.member], + // key.modulename: "MyMacroImpl", + // key.typename: "StringifyMacro"}, + // { key.offset: 132, + // key.sourceText: "foo(bar, baz)", + // key.macro_roles: [source.lang.swift.macrorole.conformance, + // source.lang.swift.macrorole.member], + // key.expandedmacro_replacements: [ + // {key.offset: 4, key.length: 3, key.argindex: 0}, + // {key.offset: 9, key.length: 3, key.argindex: 1}]} + // ] + std::vector expansions; + bool failed = req.dictionaryArrayApply(KeyExpansions, [&](RequestDict dict) { + // offset. + int64_t offset; + dict.getInt64(KeyOffset, offset, false); + + // macro roles. + SmallVector macroRoleUIDs; + if (dict.getUIDArray(KeyMacroRoles, macroRoleUIDs, false)) { + rec(createErrorRequestInvalid( + "missing 'key.macro_roles' for expansion specifier")); + return true; + } + MacroRoles macroRoles; + for (auto uid : macroRoleUIDs) { + if (uid == KindMacroRoleExpression) + macroRoles |= MacroRole::Expression; + if (uid == KindMacroRoleDeclaration) + macroRoles |= MacroRole::Declaration; + if (uid == KindMacroRoleCodeItem) + macroRoles |= MacroRole::CodeItem; + if (uid == KindMacroRoleAccessor) + macroRoles |= MacroRole::Accessor; + if (uid == KindMacroRoleMemberAttribute) + macroRoles |= MacroRole::MemberAttribute; + if (uid == KindMacroRoleMember) + macroRoles |= MacroRole::Member; + if (uid == KindMacroRolePeer) + macroRoles |= MacroRole::Peer; + if (uid == KindMacroRoleConformance) + macroRoles |= MacroRole::Conformance; + } + + // definition. + if (auto moduleName = dict.getString(KeyModuleName)) { + auto typeName = dict.getString(KeyTypeName); + if (!typeName) { + rec(createErrorRequestInvalid( + "missing 'key.typename' for external macro definition")); + return true; + } + MacroExpansionInfo::ExternalMacroReference definition(moduleName->str(), + typeName->str()); + expansions.emplace_back(offset, macroRoles, definition); + } else if (auto expandedText = dict.getString(KeySourceText)) { + MacroExpansionInfo::ExpandedMacroDefinition definition(*expandedText); + bool failed = dict.dictionaryArrayApply( + KeyExpandedMacroReplacements, [&](RequestDict dict) { + int64_t offset, length, paramIndex; + bool failed = false; + failed |= dict.getInt64(KeyOffset, offset, false); + failed |= dict.getInt64(KeyLength, length, false); + failed |= dict.getInt64(KeyArgIndex, paramIndex, false); + if (failed) { + rec(createErrorRequestInvalid( + "macro replacement should have key.offset, key.length, and " + "key.argindex")); + return true; + } + definition.replacements.emplace_back( + RawCharSourceRange{unsigned(offset), unsigned(length)}, + paramIndex); + return false; + }); + if (failed) + return true; + expansions.emplace_back(offset, macroRoles, definition); + } + return false; + }); + if (failed) + return; + + LangSupport &Lang = getGlobalContext().getSwiftLangSupport(); + Lang.expandMacroSyntactically( + inputBuf.get(), args, expansions, + [&](const auto &Result) { rec(createCategorizedEditsResponse(Result)); }); +} + void handleRequestImpl(sourcekitd_object_t ReqObj, SourceKitCancellationToken CancellationToken, ResponseReceiver Rec) { @@ -1927,6 +2055,8 @@ void handleRequestImpl(sourcekitd_object_t ReqObj, HANDLE_REQUEST(RequestRelatedIdents, handleRequestRelatedIdents) HANDLE_REQUEST(RequestActiveRegions, handleRequestActiveRegions) HANDLE_REQUEST(RequestDiagnostics, handleRequestDiagnostics) + HANDLE_REQUEST(RequestSyntacticMacroExpansion, + handleRequestSyntacticMacroExpansion) { SmallString<64> ErrBuf; diff --git a/utils/gyb_sourcekit_support/UIDs.py b/utils/gyb_sourcekit_support/UIDs.py index 4bd5a9f40a534..a660222a15665 100644 --- a/utils/gyb_sourcekit_support/UIDs.py +++ b/utils/gyb_sourcekit_support/UIDs.py @@ -209,6 +209,9 @@ def __init__(self, internal_name, external_name): KEY('IsSynthesized', 'key.is_synthesized'), KEY('BufferName', 'key.buffer_name'), KEY('BarriersEnabled', 'key.barriers_enabled'), + KEY('Expansions', 'key.expansions'), + KEY('MacroRoles', 'key.macro_roles'), + KEY('ExpandedMacroReplacements', 'key.expanded_macro_replacements'), ] @@ -274,6 +277,8 @@ def __init__(self, internal_name, external_name): REQUEST('Compile', 'source.request.compile'), REQUEST('CompileClose', 'source.request.compile.close'), REQUEST('EnableRequestBarriers', 'source.request.enable_request_barriers'), + REQUEST('SyntacticMacroExpansion', + 'source.request.syntactic_macro_expansion'), ] @@ -491,4 +496,12 @@ def __init__(self, internal_name, external_name): KIND('StatInstructionCount', 'source.statistic.instruction-count'), KIND('Swift', 'source.lang.swift'), KIND('ObjC', 'source.lang.objc'), + KIND('MacroRoleExpression', 'source.lang.swift.macro_role.expression'), + KIND('MacroRoleDeclaration', 'source.lang.swift.macro_role.declaration'), + KIND('MacroRoleCodeItem', 'source.lang.swift.macro_role.codeitem'), + KIND('MacroRoleAccessor', 'source.lang.swift.macro_role.accessor'), + KIND('MacroRoleMemberAttribute', 'source.lang.swift.macro_role.member_attribute'), + KIND('MacroRoleMember', 'source.lang.swift.macro_role.member'), + KIND('MacroRolePeer', 'source.lang.swift.macro_role.peer'), + KIND('MacroRoleConformance', 'source.lang.swift.macro_role.conformance'), ]