Skip to content

Commit 8294db0

Browse files
committed
[SourceKit] Add request to expand macros syntactically
Expand macros in the specified source file syntactically (without any module imports, nor typechecking). Request would look like: ``` { key.compilerargs: [...] key.sourcefile: <file name> key.sourcetext: <source text> (optional) key.expansions: [<expansion specifier>...] } ``` `key.compilerargs` are used for getting plugins search paths. If `key.sourcetext` is not specified, it's loaded from the file system. Each `<expansion sepecifier>` is ``` { key.offset: <offset> key.modulename: <plugin module name> key.typename: <macro typename> key.macro_roles: [<macro role UID>...] } ``` Clients have to provide the module and type names because that's semantic. Response is a `CategorizedEdits` just like (semantic) "ExpandMacro" refactoring. Each edit object has `key.buffer_name` that can be used for recursive expansion. If the client founds nested macro expansion in the expanded source, it can send another request with the same compiler arguments using the buffer name and the source to get the nested expansions.
1 parent a61fa5a commit 8294db0

File tree

22 files changed

+1292
-113
lines changed

22 files changed

+1292
-113
lines changed

include/swift/AST/Decl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8563,6 +8563,9 @@ class MacroDecl : public GenericContext, public ValueDecl {
85638563
/// Retrieve the definition of this macro.
85648564
MacroDefinition getDefinition() const;
85658565

8566+
/// Set the definition of this macro
8567+
void setDefinition(MacroDefinition definition);
8568+
85668569
/// Retrieve the parameter list of this macro.
85678570
ParameterList *getParameterList() const { return parameterList; }
85688571

include/swift/IDE/Utils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,9 @@ class SourceEditConsumer {
578578
void insertAfter(SourceManager &SM, SourceLoc Loc, StringRef Text, ArrayRef<NoteRegion> SubRegions = {});
579579
void accept(SourceManager &SM, Replacement Replacement) { accept(SM, RegionType::ActiveCode, {Replacement}); }
580580
void remove(SourceManager &SM, CharSourceRange Range);
581+
void acceptMacroExpansionBuffer(SourceManager &SM, unsigned bufferID,
582+
SourceFile *containingSF,
583+
bool adjustExpansion);
581584
};
582585

583586
/// This helper stream inserts text into a SourceLoc by calling functions in
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//===--- SyntacticMacroExpansion.h ----------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SWIFT_IDE_SYNTACTICMACROEXPANSION_H
14+
#define SWIFT_IDE_SYNTACTICMACROEXPANSION_H
15+
16+
#include "swift/AST/Decl.h"
17+
#include "swift/AST/MacroDefinition.h"
18+
#include "swift/AST/PluginRegistry.h"
19+
#include "swift/Basic/Fingerprint.h"
20+
#include "swift/Frontend/Frontend.h"
21+
#include "llvm/Support/MemoryBuffer.h"
22+
23+
namespace swift {
24+
25+
class ASTContext;
26+
class SourceFile;
27+
28+
namespace ide {
29+
class SourceEditConsumer;
30+
31+
/// Simple object to specify a syntactic macro expansion.
32+
struct MacroExpansionSpecifier {
33+
unsigned offset;
34+
swift::MacroRoles macroRoles;
35+
swift::MacroDefinition macroDefinition;
36+
};
37+
38+
/// Instance of a syntactic macro expansion context. This is created for each
39+
/// list of compiler arguments (i.e. 'argHash'), and reused as long as the
40+
/// compiler arguments are not changed.
41+
class SyntacticMacroExpansionInstance {
42+
const Fingerprint argHash;
43+
CompilerInvocation invocation;
44+
45+
SourceManager SourceMgr;
46+
DiagnosticEngine Diags{SourceMgr};
47+
std::unique_ptr<ASTContext> Ctx;
48+
ModuleDecl *TheModule = nullptr;
49+
llvm::DenseMap<Identifier, MacroDecl *> MacroDecls;
50+
51+
std::mutex mtx;
52+
53+
/// Create 'SourceFile' using the buffer.
54+
swift::SourceFile *getSourceFile(llvm::MemoryBuffer *inputBuf);
55+
56+
/// Synthesize 'MacroDecl' AST object to use the expansion.
57+
swift::MacroDecl *
58+
getSynthesizedMacroDecl(swift::Identifier name,
59+
const MacroExpansionSpecifier &expansion);
60+
61+
/// Expand single 'expansion' in SF.
62+
bool getExpansion(swift::SourceFile *SF,
63+
const MacroExpansionSpecifier &expansion,
64+
SourceEditConsumer &consumer);
65+
66+
public:
67+
SyntacticMacroExpansionInstance(Fingerprint argHash) : argHash(argHash) {}
68+
69+
/// Setup the instance with \p args .
70+
bool setup(StringRef SwiftExecutablePath, ArrayRef<const char *> args,
71+
std::shared_ptr<PluginRegistry> plugins, std::string &error);
72+
73+
const Fingerprint &getArgHash() const { return argHash; }
74+
ASTContext &getASTContext() { return *Ctx; }
75+
76+
/// Expand all macros in \p inputBuf and send the edit results to \p consumer.
77+
/// Expansions are specified by \p expansions .
78+
bool getExpansions(llvm::MemoryBuffer *inputBuf,
79+
ArrayRef<MacroExpansionSpecifier> expansions,
80+
SourceEditConsumer &consumer);
81+
};
82+
83+
/// Manager object to vend 'SyntacticMacroExpansionInstance'.
84+
class SyntacticMacroExpansion {
85+
StringRef SwiftExecutablePath;
86+
std::shared_ptr<PluginRegistry> Plugins;
87+
88+
/// Cached instance.
89+
std::shared_ptr<SyntacticMacroExpansionInstance> currentInstance;
90+
91+
public:
92+
SyntacticMacroExpansion(StringRef SwiftExecutablePath,
93+
std::shared_ptr<PluginRegistry> Plugins)
94+
: SwiftExecutablePath(SwiftExecutablePath), Plugins(Plugins) {}
95+
96+
/// Get instance configured with the specified compiler arguments.
97+
/// If 'currentInstance' matches with the arguments, just return it.
98+
std::shared_ptr<SyntacticMacroExpansionInstance>
99+
getInstance(ArrayRef<const char *> args, std::string &error);
100+
};
101+
102+
} // namespace ide
103+
} // namespace swift
104+
105+
#endif // SWIFT_IDE_SYNTACTICMACROEXPANSION_H

include/swift/Sema/IDETypeChecking.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ namespace swift {
3737
enum class DeclRefKind;
3838
class Expr;
3939
class ExtensionDecl;
40+
class FreestandingMacroExpansion;
4041
class FunctionType;
4142
class LabeledConditionalStmt;
4243
class LookupResult;
@@ -355,6 +356,13 @@ namespace swift {
355356
SmallVector<std::pair<ValueDecl *, ValueDecl *>, 1>
356357
getShorthandShadows(LabeledConditionalStmt *CondStmt,
357358
DeclContext *DC = nullptr);
359+
360+
SourceFile *evaluateFreestandingMacro(FreestandingMacroExpansion *expansion,
361+
StringRef discriminator);
362+
363+
SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo,
364+
CustomAttr *attr, bool passParentContext,
365+
MacroRole role, StringRef discriminator);
358366
}
359367

360368
#endif

lib/AST/Decl.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10581,6 +10581,11 @@ MacroDefinition MacroDecl::getDefinition() const {
1058110581
MacroDefinition::forUndefined());
1058210582
}
1058310583

10584+
void MacroDecl::setDefinition(MacroDefinition definition) {
10585+
getASTContext().evaluator.cacheOutput(MacroDefinitionRequest{this},
10586+
std::move(definition));
10587+
}
10588+
1058410589
Optional<BuiltinMacroKind> MacroDecl::getBuiltinKind() const {
1058510590
auto def = getDefinition();
1058610591
if (def.kind != MacroDefinition::Kind::Builtin)

lib/IDE/Utils.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,97 @@ remove(SourceManager &SM, CharSourceRange Range) {
622622
accept(SM, Range, "");
623623
}
624624

625+
/// Given the expanded code for a particular macro, perform whitespace
626+
/// adjustments to make the refactoring more suitable for inline insertion.
627+
static StringRef
628+
adjustMacroExpansionWhitespace(GeneratedSourceInfo::Kind kind,
629+
StringRef expandedCode,
630+
llvm::SmallString<64> &scratch) {
631+
scratch.clear();
632+
633+
switch (kind) {
634+
case GeneratedSourceInfo::MemberAttributeMacroExpansion:
635+
// Attributes are added to the beginning, add a space to separate from
636+
// any existing.
637+
scratch += expandedCode;
638+
scratch += " ";
639+
return scratch;
640+
641+
case GeneratedSourceInfo::MemberMacroExpansion:
642+
case GeneratedSourceInfo::PeerMacroExpansion:
643+
case GeneratedSourceInfo::ConformanceMacroExpansion:
644+
// All added to the end. Note that conformances are always expanded as
645+
// extensions, hence treating them the same as peer.
646+
scratch += "\n\n";
647+
scratch += expandedCode;
648+
scratch += "\n";
649+
return scratch;
650+
651+
case GeneratedSourceInfo::ExpressionMacroExpansion:
652+
case GeneratedSourceInfo::FreestandingDeclMacroExpansion:
653+
case GeneratedSourceInfo::AccessorMacroExpansion:
654+
case GeneratedSourceInfo::ReplacedFunctionBody:
655+
case GeneratedSourceInfo::PrettyPrinted:
656+
return expandedCode;
657+
}
658+
}
659+
660+
void swift::ide::SourceEditConsumer::acceptMacroExpansionBuffer(
661+
SourceManager &SM, unsigned bufferID, SourceFile *containingSF,
662+
bool adjustExpansion) {
663+
auto generatedInfo = SM.getGeneratedSourceInfo(bufferID);
664+
if (!generatedInfo || generatedInfo->originalSourceRange.isInvalid())
665+
return;
666+
667+
auto rewrittenBuffer = SM.extractText(generatedInfo->generatedSourceRange);
668+
669+
// If there's no change, drop the edit entirely.
670+
if (generatedInfo->originalSourceRange.getStart() ==
671+
generatedInfo->originalSourceRange.getEnd() &&
672+
rewrittenBuffer.empty())
673+
return;
674+
675+
SmallString<64> scratchBuffer;
676+
if (adjustExpansion) {
677+
rewrittenBuffer = adjustMacroExpansionWhitespace(
678+
generatedInfo->kind, rewrittenBuffer, scratchBuffer);
679+
}
680+
681+
// `containingFile` is the file of the actual expansion site, where as
682+
// `originalFile` is the possibly enclosing buffer. Concretely:
683+
// ```
684+
// // m.swift
685+
// @AddMemberAttributes
686+
// struct Foo {
687+
// // --- expanded from @AddMemberAttributes eg. @_someBufferName ---
688+
// @AddedAttribute
689+
// // ---
690+
// let someMember: Int
691+
// }
692+
// ```
693+
//
694+
// When expanding `AddedAttribute`, the expansion actually applies to the
695+
// original source (`m.swift`) rather than the buffer of the expansion
696+
// site (`@_someBufferName`). Thus, we need to include the path to the
697+
// original source as well. Note that this path could itself be another
698+
// expansion.
699+
auto originalSourceRange = generatedInfo->originalSourceRange;
700+
SourceFile *originalFile =
701+
containingSF->getParentModule()->getSourceFileContainingLocation(
702+
originalSourceRange.getStart());
703+
StringRef originalPath;
704+
if (originalFile->getBufferID().hasValue() &&
705+
containingSF->getBufferID() != originalFile->getBufferID()) {
706+
originalPath = SM.getIdentifierForBuffer(*originalFile->getBufferID());
707+
}
708+
709+
accept(SM, {originalPath,
710+
originalSourceRange,
711+
SM.getIdentifierForBuffer(bufferID),
712+
rewrittenBuffer,
713+
{}});
714+
}
715+
625716
struct swift::ide::SourceEditJsonConsumer::Implementation {
626717
llvm::raw_ostream &OS;
627718
std::vector<SingleEdit> AllEdits;

lib/IDETool/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_swift_host_library(swiftIDETool STATIC
44
CompilerInvocation.cpp
55
IDEInspectionInstance.cpp
66
DependencyChecking.cpp
7+
SyntacticMacroExpansion.cpp
78
)
89

910
target_link_libraries(swiftIDETool PRIVATE

0 commit comments

Comments
 (0)