Skip to content

Commit 07f7857

Browse files
committed
[Macros] Automatically format expanded macros
Rather than requiring macro implementations to add required whitespace and indentation, basic format all macro expansions. Right now this uses the default four space indentation, we can consider having that inferred later. Macros can opt-out of automatic formatting by implementing `formatMode` and setting it to `.disabled`. Also moves the extra newlines before/after expansions to a new "Inline Macro" refactoring. Resolves rdar://107731047.
1 parent db14ae8 commit 07f7857

File tree

7 files changed

+297
-96
lines changed

7 files changed

+297
-96
lines changed

include/swift/Refactoring/RefactoringKinds.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ CURSOR_REFACTORING(AddAsyncWrapper, "Add Async Wrapper", add.async-wrapper)
6666

6767
CURSOR_REFACTORING(ExpandMacro, "Expand Macro", expand.macro)
6868

69+
CURSOR_REFACTORING(InlineMacro, "Inline Macro", inline.macro)
70+
6971
RANGE_REFACTORING(ExtractExpr, "Extract Expression", extract.expr)
7072

7173
RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)

lib/ASTGen/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ add_pure_swift_host_library(swiftASTGen STATIC
2525
DEPENDENCIES
2626
swiftAST
2727
SWIFT_DEPENDENCIES
28+
SwiftSyntax::SwiftBasicFormat
2829
SwiftSyntax::SwiftDiagnostics
2930
SwiftSyntax::SwiftOperators
3031
SwiftSyntax::SwiftParser

lib/ASTGen/Sources/ASTGen/Macros.swift

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -566,10 +566,11 @@ func expandFreestandingMacroInProcess(
566566
}
567567

568568
let macroPtr = macroPtr.bindMemory(to: ExportedMacro.self, capacity: 1)
569+
let macro = macroPtr.pointee.macro
569570

570571
let evaluatedSyntax: Syntax
571572
do {
572-
switch macroPtr.pointee.macro {
573+
switch macro {
573574
// Handle expression macro.
574575
case let exprMacro as ExpressionMacro.Type:
575576
func expandExpressionMacro<Node: FreestandingMacroExpansionSyntax>(
@@ -630,7 +631,7 @@ func expandFreestandingMacroInProcess(
630631
return nil
631632
}
632633

633-
return evaluatedSyntax.trimmedDescription
634+
return evaluatedSyntax.formattedExpansion(macro.formatMode)
634635
}
635636

636637
/// Retrieve a syntax node in the given source file, with the given type.
@@ -936,7 +937,7 @@ func expandAttachedMacroInProcess(
936937

937938
// Form a buffer of accessor declarations to return to the caller.
938939
expandedSources = accessors.map {
939-
$0.trimmedDescription
940+
$0.formattedExpansion(macro.formatMode)
940941
}
941942

942943
case (let attachedMacro as MemberAttributeMacro.Type, .MemberAttribute):
@@ -970,7 +971,7 @@ func expandAttachedMacroInProcess(
970971

971972
// Form a buffer containing an attribute list to return to the caller.
972973
expandedSources = attributes.map {
973-
$0.trimmedDescription
974+
$0.formattedExpansion(macro.formatMode)
974975
}
975976

976977
case (let attachedMacro as MemberMacro.Type, .Member):
@@ -998,7 +999,7 @@ func expandAttachedMacroInProcess(
998999

9991000
// Form a buffer of member declarations to return to the caller.
10001001
expandedSources = members.map {
1001-
$0.trimmedDescription
1002+
$0.formattedExpansion(macro.formatMode)
10021003
}
10031004

10041005
case (let attachedMacro as PeerMacro.Type, .Peer):
@@ -1016,7 +1017,7 @@ func expandAttachedMacroInProcess(
10161017

10171018
// Form a buffer of peer declarations to return to the caller.
10181019
expandedSources = peers.map {
1019-
$0.trimmedDescription
1020+
$0.formattedExpansion(macro.formatMode)
10201021
}
10211022

10221023
case (let attachedMacro as ConformanceMacro.Type, .Conformance):
@@ -1064,43 +1065,67 @@ func expandAttachedMacroInProcess(
10641065
return expandedSources
10651066
}
10661067

1068+
fileprivate extension SyntaxProtocol {
1069+
/// Perform a format if required and then trim any leading/trailing
1070+
/// whitespace.
1071+
func formattedExpansion(_ mode: FormatMode) -> String {
1072+
let formatted: Syntax
1073+
if mode == .auto {
1074+
formatted = self.formatted()
1075+
} else {
1076+
formatted = Syntax(self)
1077+
}
1078+
return formatted.trimmedDescription(matching: { $0.isWhitespace })
1079+
}
1080+
}
1081+
10671082
fileprivate func collapse<Node: SyntaxProtocol>(
10681083
expansions: [String],
10691084
for role: MacroRole,
10701085
attachedTo declarationNode: Node
10711086
) -> String {
1087+
if expansions.isEmpty {
1088+
return ""
1089+
}
1090+
1091+
var expansions = expansions
10721092
var separator: String = "\n\n"
1073-
var prefix: String = ""
1074-
var suffix: String = ""
1075-
1076-
switch role {
1077-
case .Accessor:
1078-
if let varDecl = declarationNode.as(VariableDeclSyntax.self),
1079-
let binding = varDecl.bindings.first,
1080-
binding.accessor == nil {
1081-
prefix = "{\n"
1082-
suffix = "\n}"
1083-
}
1084-
case .Member:
1085-
prefix = "\n\n"
1086-
suffix = "\n"
1087-
case .MemberAttribute:
1093+
1094+
if role == .Accessor,
1095+
let varDecl = declarationNode.as(VariableDeclSyntax.self),
1096+
let binding = varDecl.bindings.first,
1097+
binding.accessor == nil {
1098+
let indentation = String(repeating: " ", count: 4)
1099+
1100+
expansions = expansions.map({ indent($0, with: indentation) })
1101+
expansions[0] = "{\n" + expansions[0]
1102+
expansions[expansions.count - 1] += "\n}"
1103+
} else if role == .MemberAttribute {
10881104
separator = " "
1089-
suffix = " "
1090-
case .Peer:
1091-
prefix = "\n\n"
1092-
suffix = "\n"
1093-
case .Conformance:
1094-
prefix = "\n\n"
1095-
suffix = "\n"
1096-
case .Expression,
1097-
.FreestandingDeclaration:
1098-
fatalError("unreachable")
10991105
}
11001106

1101-
let separated = expansions.joined(separator: separator)
1102-
if separated.isEmpty {
1103-
return separated
1107+
return expansions.joined(separator: separator)
1108+
}
1109+
1110+
fileprivate func indent(_ source: String, with indentation: String) -> String {
1111+
if source.isEmpty || indentation.isEmpty {
1112+
return source
11041113
}
1105-
return prefix + separated + suffix
1114+
1115+
var indented = ""
1116+
var remaining = source[...]
1117+
while let nextNewline = remaining.firstIndex(of: "\n") {
1118+
if nextNewline != remaining.startIndex {
1119+
indented += indentation
1120+
}
1121+
indented += remaining[...nextNewline]
1122+
remaining = remaining[remaining.index(after: nextNewline)...]
1123+
}
1124+
1125+
if !remaining.isEmpty {
1126+
indented += indentation
1127+
indented += remaining
1128+
}
1129+
1130+
return indented
11061131
}

lib/Refactoring/Refactoring.cpp

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8756,18 +8756,53 @@ getMacroExpansionBuffers(SourceManager &sourceMgr, ResolvedCursorInfoPtr Info) {
87568756
return {};
87578757
}
87588758

8759-
bool RefactoringActionExpandMacro::isApplicable(ResolvedCursorInfoPtr Info,
8760-
DiagnosticEngine &Diag) {
8761-
return !getMacroExpansionBuffers(Diag.SourceMgr, Info).empty();
8762-
}
8763-
8764-
bool RefactoringActionExpandMacro::performChange() {
8765-
auto bufferIDs = getMacroExpansionBuffers(SM, CursorInfo);
8759+
/// Given the expanded code for a particular macro, perform whitespace
8760+
/// adjustments to make the refactoring more suitable for inline insertion.
8761+
static StringRef adjustMacroExpansionWhitespace(
8762+
GeneratedSourceInfo::Kind kind, StringRef expandedCode,
8763+
llvm::SmallString<64> &scratch) {
8764+
scratch.clear();
8765+
8766+
switch (kind) {
8767+
case GeneratedSourceInfo::MemberAttributeMacroExpansion:
8768+
// Attributes are added to the beginning, add a space to separate from
8769+
// any existing.
8770+
scratch += expandedCode;
8771+
scratch += " ";
8772+
return scratch;
8773+
8774+
case GeneratedSourceInfo::MemberMacroExpansion:
8775+
case GeneratedSourceInfo::PeerMacroExpansion:
8776+
case GeneratedSourceInfo::ConformanceMacroExpansion:
8777+
// All added to the end. Note that conformances are always expanded as
8778+
// extensions, hence treating them the same as peer.
8779+
scratch += "\n\n";
8780+
scratch += expandedCode;
8781+
scratch += "\n";
8782+
return scratch;
8783+
8784+
case GeneratedSourceInfo::ExpressionMacroExpansion:
8785+
case GeneratedSourceInfo::FreestandingDeclMacroExpansion:
8786+
case GeneratedSourceInfo::AccessorMacroExpansion:
8787+
case GeneratedSourceInfo::ReplacedFunctionBody:
8788+
case GeneratedSourceInfo::PrettyPrinted:
8789+
return expandedCode;
8790+
}
8791+
}
8792+
8793+
static bool expandMacro(SourceManager &SM, ResolvedCursorInfoPtr cursorInfo,
8794+
SourceEditConsumer &editConsumer, bool adjustExpansion) {
8795+
auto bufferIDs = getMacroExpansionBuffers(SM, cursorInfo);
87668796
if (bufferIDs.empty())
87678797
return true;
87688798

8799+
SourceFile *containingSF = cursorInfo->getSourceFile();
8800+
if (!containingSF)
8801+
return true;
8802+
87698803
// Send all of the rewritten buffer snippets.
87708804
CustomAttr *attachedMacroAttr = nullptr;
8805+
SmallString<64> scratchBuffer;
87718806
for (auto bufferID: bufferIDs) {
87728807
auto generatedInfo = SM.getGeneratedSourceInfo(bufferID);
87738808
if (!generatedInfo || generatedInfo->originalSourceRange.isInvalid())
@@ -8781,8 +8816,12 @@ bool RefactoringActionExpandMacro::performChange() {
87818816
rewrittenBuffer.empty())
87828817
continue;
87838818

8784-
// `TheFile` is the file of the actual expansion site, where as
8785-
// `OriginalFile` is the possibly enclosing buffer. Concretely:
8819+
if (adjustExpansion) {
8820+
rewrittenBuffer = adjustMacroExpansionWhitespace(generatedInfo->kind, rewrittenBuffer, scratchBuffer);
8821+
}
8822+
8823+
// `containingFile` is the file of the actual expansion site, where as
8824+
// `originalFile` is the possibly enclosing buffer. Concretely:
87868825
// ```
87878826
// // m.swift
87888827
// @AddMemberAttributes
@@ -8801,14 +8840,14 @@ bool RefactoringActionExpandMacro::performChange() {
88018840
// expansion.
88028841
auto originalSourceRange = generatedInfo->originalSourceRange;
88038842
SourceFile *originalFile =
8804-
MD->getSourceFileContainingLocation(originalSourceRange.getStart());
8843+
containingSF->getParentModule()->getSourceFileContainingLocation(originalSourceRange.getStart());
88058844
StringRef originalPath;
88068845
if (originalFile->getBufferID().hasValue() &&
8807-
TheFile->getBufferID() != originalFile->getBufferID()) {
8846+
containingSF->getBufferID() != originalFile->getBufferID()) {
88088847
originalPath = SM.getIdentifierForBuffer(*originalFile->getBufferID());
88098848
}
88108849

8811-
EditConsumer.accept(SM, {originalPath,
8850+
editConsumer.accept(SM, {originalPath,
88128851
originalSourceRange,
88138852
SM.getIdentifierForBuffer(bufferID),
88148853
rewrittenBuffer,
@@ -8823,12 +8862,31 @@ bool RefactoringActionExpandMacro::performChange() {
88238862
if (attachedMacroAttr) {
88248863
SourceRange range = attachedMacroAttr->getRangeWithAt();
88258864
auto charRange = Lexer::getCharSourceRangeFromSourceRange(SM, range);
8826-
EditConsumer.accept(SM, charRange, StringRef());
8865+
editConsumer.accept(SM, charRange, StringRef());
88278866
}
88288867

88298868
return false;
88308869
}
88318870

8871+
bool RefactoringActionExpandMacro::isApplicable(ResolvedCursorInfoPtr Info,
8872+
DiagnosticEngine &Diag) {
8873+
// Never list in available refactorings. Only allow requesting directly.
8874+
return false;
8875+
}
8876+
8877+
bool RefactoringActionExpandMacro::performChange() {
8878+
return expandMacro(SM, CursorInfo, EditConsumer, /*adjustExpansion=*/false);
8879+
}
8880+
8881+
bool RefactoringActionInlineMacro::isApplicable(ResolvedCursorInfoPtr Info,
8882+
DiagnosticEngine &Diag) {
8883+
return !getMacroExpansionBuffers(Diag.SourceMgr, Info).empty();
8884+
}
8885+
8886+
bool RefactoringActionInlineMacro::performChange() {
8887+
return expandMacro(SM, CursorInfo, EditConsumer, /*adjustExpansion=*/true);
8888+
}
8889+
88328890
} // end of anonymous namespace
88338891

88348892
StringRef swift::ide::
@@ -8940,8 +8998,8 @@ swift::ide::collectRefactorings(ResolvedCursorInfoPtr CursorInfo,
89408998

89418999
// Only macro expansion is available within generated buffers
89429000
if (CursorInfo->getSourceFile()->Kind == SourceFileKind::MacroExpansion) {
8943-
if (RefactoringActionExpandMacro::isApplicable(CursorInfo, DiagEngine)) {
8944-
Infos.emplace_back(RefactoringKind::ExpandMacro,
9001+
if (RefactoringActionInlineMacro::isApplicable(CursorInfo, DiagEngine)) {
9002+
Infos.emplace_back(RefactoringKind::InlineMacro,
89459003
RefactorAvailableKind::Available);
89469004
}
89479005
return Infos;
@@ -9019,6 +9077,7 @@ refactorSwiftModule(ModuleDecl *M, RefactoringOptions Opts,
90199077
case RefactoringKind::KIND: { \
90209078
RefactoringAction##KIND Action(M, Opts, EditConsumer, DiagConsumer); \
90219079
if (RefactoringKind::KIND == RefactoringKind::LocalRename || \
9080+
RefactoringKind::KIND == RefactoringKind::ExpandMacro || \
90229081
Action.isApplicable()) \
90239082
return Action.performChange(); \
90249083
return true; \

0 commit comments

Comments
 (0)