mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
48ab4b0595
Some Darwin platforms like DriverKit use a system prefix on all of their search paths. Even though DriverKit isn't supported, add support to get the system prefix from SDKSettings when constructing the default search paths. This requires the DarwinSDKInfo to be gotten earlier in CompilerInvocation, pass that down to ASTContext through CompilerInstance. -platform-availability-inheritance-map-path is no longer needed to support visionOS in tests, remove that and its supporting code that gets an alternative DarwinSDKInfo. rdar://166277280
466 lines
16 KiB
C++
466 lines
16 KiB
C++
//===--- 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/Basic/Assertions.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<SyntacticMacroExpansionInstance>
|
|
SyntacticMacroExpansion::getInstance(ArrayRef<const char *> args,
|
|
llvm::MemoryBuffer *inputBuf,
|
|
std::string &error) {
|
|
// Create and configure a new instance.
|
|
auto instance = std::make_shared<SyntacticMacroExpansionInstance>();
|
|
|
|
bool failed =
|
|
instance->setup(SwiftExecutablePath, args, inputBuf, Plugins, error);
|
|
if (failed)
|
|
return nullptr;
|
|
|
|
return instance;
|
|
}
|
|
|
|
bool SyntacticMacroExpansionInstance::setup(
|
|
StringRef SwiftExecutablePath, ArrayRef<const char *> args,
|
|
llvm::MemoryBuffer *inputBuf, std::shared_ptr<PluginRegistry> 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<const char *> 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(),
|
|
invocation.getCASOptions(), invocation.getSerializationOptions(),
|
|
SourceMgr, Diags, invocation.getSDKInfo()));
|
|
registerParseRequestFunctions(Ctx->evaluator);
|
|
registerTypeCheckerRequestFunctions(Ctx->evaluator);
|
|
|
|
std::unique_ptr<PluginLoader> pluginLoader =
|
|
std::make_unique<PluginLoader>(*Ctx, /*DepTracker=*/nullptr);
|
|
pluginLoader->setRegistry(plugins.get());
|
|
Ctx->setPluginLoader(std::move(pluginLoader));
|
|
|
|
// Create the ModuleDecl and SourceFile.
|
|
Identifier ID = Ctx->getIdentifier(invocation.getModuleName());
|
|
TheModule = ModuleDecl::create(ID, *Ctx,
|
|
[&](ModuleDecl *TheModule, auto addFile) {
|
|
SF = new (*Ctx) SourceFile(*TheModule, SourceFileKind::Main,
|
|
SourceMgr.addMemBufferCopy(inputBuf));
|
|
SF->setImports({});
|
|
addFile(SF);
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
MacroDecl *SyntacticMacroExpansionInstance::getSynthesizedMacroDecl(
|
|
Identifier name, const MacroExpansionSpecifier &expansion) {
|
|
auto &ctx = getASTContext();
|
|
|
|
std::string macroID;
|
|
|
|
switch (expansion.macroDefinition.kind) {
|
|
case MacroDefinition::Kind::External: {
|
|
// '<module name>.<type name>'
|
|
// 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<MacroRoleAttr>()) {
|
|
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=*/{},
|
|
/*conformances=*/{},
|
|
/*rParenLoc=*/{}, /*implicit=*/true);
|
|
macro->addAttribute(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,
|
|
std::optional<SourceLoc> supplementalLoc = std::nullopt,
|
|
std::optional<MacroRole> role = std::nullopt) {
|
|
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<unsigned>
|
|
expandFreestandingMacro(MacroDecl *macro,
|
|
FreestandingMacroExpansion *expansion) {
|
|
std::vector<unsigned> 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<unsigned>
|
|
expandAttachedMacro(MacroDecl *macro, CustomAttr *attr, Decl *attachedDecl) {
|
|
|
|
std::vector<unsigned> 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<AbstractStorageDecl>(attachedDecl))
|
|
evaluate(attachedDecl, /*passParent=*/false, MacroRole::Accessor);
|
|
}
|
|
if (roles.contains(MacroRole::MemberAttribute)) {
|
|
if (auto *idc = dyn_cast<IterableDeclContext>(attachedDecl)) {
|
|
for (auto *member : idc->getParsedMembers()) {
|
|
// 'VarDecl' in 'IterableDeclContext' are part of 'PatternBindingDecl'.
|
|
if (isa<VarDecl>(member))
|
|
continue;
|
|
evaluate(member, /*passParent=*/true, MacroRole::MemberAttribute);
|
|
}
|
|
}
|
|
}
|
|
if (roles.contains(MacroRole::Member)) {
|
|
if (isa<IterableDeclContext>(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<NominalTypeDecl>(attachedDecl))
|
|
evaluate(attachedDecl, /*passParent=*/false, MacroRole::Conformance);
|
|
}
|
|
if (roles.contains(MacroRole::Extension)) {
|
|
if (isa<NominalTypeDecl>(attachedDecl))
|
|
evaluate(attachedDecl, /*passParent=*/false, MacroRole::Extension);
|
|
}
|
|
if (roles.contains(MacroRole::Preamble)) {
|
|
if (isa<AbstractFunctionDecl>(attachedDecl))
|
|
evaluate(attachedDecl, /*passParent=*/false, MacroRole::Preamble);
|
|
}
|
|
if (roles.contains(MacroRole::Body)) {
|
|
if (isa<AbstractFunctionDecl>(attachedDecl))
|
|
evaluate(attachedDecl, /*passParent=*/false, MacroRole::Body);
|
|
}
|
|
|
|
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<DeclRefTypeRepr>(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;
|
|
std::optional<ExpansionNode> result;
|
|
|
|
bool rangeContainsLocToResolve(SourceRange Range) const {
|
|
return SM.rangeContainsTokenLoc(Range, locToResolve);
|
|
}
|
|
|
|
public:
|
|
MacroExpansionFinder(SourceManager &SM, SourceLoc locToResolve)
|
|
: SM(SM), locToResolve(locToResolve) {}
|
|
|
|
std::optional<ExpansionNode> 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<VarDecl>(D) &&
|
|
!rangeContainsLocToResolve(D->getSourceRangeIncludingAttrs())) {
|
|
return Action::SkipNode();
|
|
}
|
|
|
|
// Check the attributes.
|
|
for (DeclAttribute *attr : D->getAttrs()) {
|
|
if (auto customAttr = dyn_cast<CustomAttr>(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<MacroExpansionDecl>(D)) {
|
|
SourceRange nameRange(med->getExpansionInfo()->SigilLoc,
|
|
med->getMacroNameLoc().getEndLoc());
|
|
if (rangeContainsLocToResolve(nameRange)) {
|
|
result = ExpansionNode{nullptr, ASTNode(med)};
|
|
return Action::Stop();
|
|
}
|
|
}
|
|
|
|
return Action::Continue();
|
|
}
|
|
|
|
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
|
|
if (!rangeContainsLocToResolve(E->getSourceRange())) {
|
|
return Action::SkipNode(E);
|
|
}
|
|
|
|
// Check 'MacroExpansionExpr'.
|
|
if (auto mee = dyn_cast<MacroExpansionExpr>(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<Stmt *> walkToStmtPre(Stmt *S) override {
|
|
if (!rangeContainsLocToResolve(S->getSourceRange())) {
|
|
return Action::SkipNode(S);
|
|
}
|
|
return Action::Continue(S);
|
|
}
|
|
PreWalkResult<ArgumentList *>
|
|
walkToArgumentListPre(ArgumentList *AL) override {
|
|
if (!rangeContainsLocToResolve(AL->getSourceRange())) {
|
|
return Action::SkipNode(AL);
|
|
}
|
|
return Action::Continue(AL);
|
|
}
|
|
PreWalkAction walkToParameterListPre(ParameterList *PL) override {
|
|
if (!rangeContainsLocToResolve(PL->getSourceRange())) {
|
|
return Action::SkipNode();
|
|
}
|
|
return Action::Continue();
|
|
}
|
|
PreWalkAction walkToTypeReprPre(TypeRepr *T) override {
|
|
// TypeRepr cannot have macro expansions in it.
|
|
return Action::SkipNode();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
void SyntacticMacroExpansionInstance::expand(
|
|
const MacroExpansionSpecifier &expansion, SourceEditConsumer &consumer) {
|
|
|
|
// Find the expansion at 'expansion.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<unsigned> bufferIDs;
|
|
if (auto *attr = expansionNode->attribute) {
|
|
// Attached macros.
|
|
MacroDecl *macro = getSynthesizedMacroDecl(
|
|
getCustomAttrName(getASTContext(), attr), expansion);
|
|
auto *attachedTo = cast<Decl *>(expansionNode->node);
|
|
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 (isa<Expr *>(node)) {
|
|
freestanding = cast<MacroExpansionExpr>(cast<Expr *>(node));
|
|
} else {
|
|
freestanding = cast<MacroExpansionDecl>(cast<Decl *>(node));
|
|
}
|
|
|
|
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(
|
|
ArrayRef<MacroExpansionSpecifier> expansions,
|
|
SourceEditConsumer &consumer) {
|
|
for (const auto &expansion : expansions) {
|
|
expand(expansion, consumer);
|
|
}
|
|
}
|