Skip to content

Commit c3d1304

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. But without `key.buffer_name`. Nested expnasions are not supported at this point.
1 parent 65e37f7 commit c3d1304

File tree

22 files changed

+1287
-111
lines changed

22 files changed

+1287
-111
lines changed

include/swift/AST/Decl.h

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

8604+
/// Set the definition of this macro
8605+
void setDefinition(MacroDefinition definition);
8606+
86048607
/// Retrieve the parameter list of this macro.
86058608
ParameterList *getParameterList() const { return parameterList; }
86068609

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, bool includeBufferName);
581584
};
582585

583586
/// This helper stream inserts text into a SourceLoc by calling functions in
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
CompilerInvocation invocation;
43+
44+
SourceManager SourceMgr;
45+
DiagnosticEngine Diags{SourceMgr};
46+
std::unique_ptr<ASTContext> Ctx;
47+
ModuleDecl *TheModule = nullptr;
48+
llvm::StringMap<MacroDecl *> MacroDecls;
49+
50+
/// Create 'SourceFile' using the buffer.
51+
swift::SourceFile *getSourceFile(llvm::MemoryBuffer *inputBuf);
52+
53+
/// Synthesize 'MacroDecl' AST object to use the expansion.
54+
swift::MacroDecl *
55+
getSynthesizedMacroDecl(swift::Identifier name,
56+
const MacroExpansionSpecifier &expansion);
57+
58+
/// Expand single 'expansion' in SF.
59+
void expand(swift::SourceFile *SF,
60+
const MacroExpansionSpecifier &expansion,
61+
SourceEditConsumer &consumer);
62+
63+
public:
64+
SyntacticMacroExpansionInstance() {}
65+
66+
/// Setup the instance with \p args .
67+
bool setup(StringRef SwiftExecutablePath, ArrayRef<const char *> args,
68+
std::shared_ptr<PluginRegistry> plugins, std::string &error);
69+
70+
ASTContext &getASTContext() { return *Ctx; }
71+
72+
/// Expand all macros in \p inputBuf and send the edit results to \p consumer.
73+
/// Expansions are specified by \p expansions .
74+
void expandAll(llvm::MemoryBuffer *inputBuf,
75+
ArrayRef<MacroExpansionSpecifier> expansions,
76+
SourceEditConsumer &consumer);
77+
};
78+
79+
/// Manager object to vend 'SyntacticMacroExpansionInstance'.
80+
class SyntacticMacroExpansion {
81+
StringRef SwiftExecutablePath;
82+
std::shared_ptr<PluginRegistry> Plugins;
83+
84+
public:
85+
SyntacticMacroExpansion(StringRef SwiftExecutablePath,
86+
std::shared_ptr<PluginRegistry> Plugins)
87+
: SwiftExecutablePath(SwiftExecutablePath), Plugins(Plugins) {}
88+
89+
/// Get instance configured with the specified compiler arguments.
90+
std::shared_ptr<SyntacticMacroExpansionInstance>
91+
getInstance(ArrayRef<const char *> args, std::string &error);
92+
};
93+
94+
} // namespace ide
95+
} // namespace swift
96+
97+
#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
@@ -10632,6 +10632,11 @@ MacroDefinition MacroDecl::getDefinition() const {
1063210632
MacroDefinition::forUndefined());
1063310633
}
1063410634

10635+
void MacroDecl::setDefinition(MacroDefinition definition) {
10636+
getASTContext().evaluator.cacheOutput(MacroDefinitionRequest{this},
10637+
std::move(definition));
10638+
}
10639+
1063510640
Optional<BuiltinMacroKind> MacroDecl::getBuiltinKind() const {
1063610641
auto def = getDefinition();
1063710642
if (def.kind != MacroDefinition::Kind::Builtin)

lib/IDE/Utils.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/IDE/Utils.h"
14+
#include "swift/AST/SourceFile.h"
1415
#include "swift/Basic/Edit.h"
1516
#include "swift/Basic/Platform.h"
1617
#include "swift/Basic/SourceManager.h"
@@ -622,6 +623,102 @@ remove(SourceManager &SM, CharSourceRange Range) {
622623
accept(SM, Range, "");
623624
}
624625

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