Skip to content

Commit 921e61b

Browse files
committed
Emit "in expansion of macro" notes for diagnostics within macro expansion buffers
1 parent f467ef4 commit 921e61b

File tree

5 files changed

+109
-12
lines changed

5 files changed

+109
-12
lines changed

include/swift/AST/DiagnosticEngine.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ namespace swift {
502502
}
503503

504504
void addChildNote(Diagnostic &&D);
505+
void insertChildNote(unsigned beforeIndex, Diagnostic &&D);
505506
};
506507

507508
/// Describes an in-flight diagnostic, which is currently active
@@ -1164,6 +1165,11 @@ namespace swift {
11641165
/// Send \c diag to all diagnostic consumers.
11651166
void emitDiagnostic(const Diagnostic &diag);
11661167

1168+
/// Retrieve the set of child notes that describe how the generated
1169+
/// source buffer was derived, e.g., a macro expansion backtrace.
1170+
std::vector<Diagnostic> getGeneratedSourceBufferNotes(
1171+
SourceLoc loc, Optional<unsigned> &lastBufferID);
1172+
11671173
/// Handle a new diagnostic, which will either be emitted, or added to an
11681174
/// active transaction.
11691175
void handleDiagnostic(Diagnostic &&diag);

include/swift/AST/DiagnosticsCommon.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ REMARK(remark_save_cache, none,
214214
ERROR(unknown_attribute,none,
215215
"unknown attribute '%0'", (StringRef))
216216

217+
//------------------------------------------------------------------------------
218+
// MARK: macro diagnostics
219+
//------------------------------------------------------------------------------
220+
NOTE(in_macro_expansion,none,
221+
"in expansion of macro %0 here", (DeclName))
222+
217223
//------------------------------------------------------------------------------
218224
// MARK: bridged diagnostics
219225
//------------------------------------------------------------------------------

lib/AST/DiagnosticEngine.cpp

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/AST/Decl.h"
2222
#include "swift/AST/DiagnosticSuppression.h"
2323
#include "swift/AST/DiagnosticsCommon.h"
24+
#include "swift/AST/Expr.h"
2425
#include "swift/AST/Module.h"
2526
#include "swift/AST/Pattern.h"
2627
#include "swift/AST/PrintOptions.h"
@@ -1267,10 +1268,82 @@ DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
12671268
diagnostic.isChildNote());
12681269
}
12691270

1271+
std::vector<Diagnostic> DiagnosticEngine::getGeneratedSourceBufferNotes(
1272+
SourceLoc loc, Optional<unsigned> &lastBufferID
1273+
) {
1274+
// The set of child notes we're building up.
1275+
std::vector<Diagnostic> childNotes;
1276+
1277+
// If the location is invalid, there's nothing to do.
1278+
if (loc.isInvalid())
1279+
return childNotes;
1280+
1281+
// If we already emitted these notes for a prior part of the diagnostic,
1282+
// don't do so again.
1283+
auto currentBufferID = SourceMgr.findBufferContainingLoc(loc);
1284+
if (currentBufferID == lastBufferID)
1285+
return childNotes;
1286+
1287+
// Keep track of the last buffer ID we considered.
1288+
lastBufferID = currentBufferID;
1289+
1290+
SourceLoc currentLoc = loc;
1291+
do {
1292+
auto generatedInfo = SourceMgr.getGeneratedSourceInfo(currentBufferID);
1293+
if (!generatedInfo)
1294+
return childNotes;
1295+
1296+
ASTNode expansionNode =
1297+
ASTNode::getFromOpaqueValue(generatedInfo->astNode);
1298+
1299+
switch (generatedInfo->kind) {
1300+
case GeneratedSourceInfo::MacroExpansion: {
1301+
SourceRange origRange = expansionNode.getSourceRange();
1302+
DeclName macroName;
1303+
if (auto expansionExpr = dyn_cast_or_null<MacroExpansionExpr>(
1304+
expansionNode.dyn_cast<Expr *>())) {
1305+
macroName = expansionExpr->getMacroName().getFullName();
1306+
} else {
1307+
auto expansionDecl =
1308+
cast<MacroExpansionDecl>(expansionNode.get<Decl *>());
1309+
macroName = expansionDecl->getMacro().getFullName();
1310+
}
1311+
1312+
Diagnostic expansionNote(diag::in_macro_expansion, macroName);
1313+
expansionNote.setLoc(origRange.Start);
1314+
expansionNote.addRange(
1315+
Lexer::getCharSourceRangeFromSourceRange(SourceMgr, origRange));
1316+
childNotes.push_back(std::move(expansionNote));
1317+
break;
1318+
}
1319+
1320+
case GeneratedSourceInfo::ReplacedFunctionBody:
1321+
return childNotes;
1322+
}
1323+
1324+
// Walk up the stack.
1325+
currentLoc = expansionNode.getStartLoc();
1326+
currentBufferID = SourceMgr.findBufferContainingLoc(currentLoc);
1327+
} while (true);
1328+
}
1329+
12701330
void DiagnosticEngine::emitDiagnostic(const Diagnostic &diagnostic) {
1331+
Optional<unsigned> lastBufferID;
1332+
1333+
ArrayRef<Diagnostic> childNotes = diagnostic.getChildNotes();
1334+
std::vector<Diagnostic> extendedChildNotes;
1335+
12711336
if (auto info = diagnosticInfoForDiagnostic(diagnostic)) {
1337+
// If the diagnostic location is within a buffer containing generated
1338+
// source code, add child notes showing where the generation occurred.
1339+
extendedChildNotes = getGeneratedSourceBufferNotes(info->Loc, lastBufferID);
1340+
if (!extendedChildNotes.empty()) {
1341+
extendedChildNotes.insert(extendedChildNotes.end(),
1342+
childNotes.begin(), childNotes.end());
1343+
childNotes = extendedChildNotes;
1344+
}
1345+
12721346
SmallVector<DiagnosticInfo, 1> childInfo;
1273-
auto childNotes = diagnostic.getChildNotes();
12741347
for (unsigned i : indices(childNotes)) {
12751348
auto child = diagnosticInfoForDiagnostic(childNotes[i]);
12761349
assert(child);
@@ -1302,7 +1375,7 @@ void DiagnosticEngine::emitDiagnostic(const Diagnostic &diagnostic) {
13021375
// For compatibility with DiagnosticConsumers which don't know about child
13031376
// notes. These can be ignored by consumers which do take advantage of the
13041377
// grouping.
1305-
for (auto &childNote : diagnostic.getChildNotes())
1378+
for (auto &childNote : childNotes)
13061379
emitDiagnostic(childNote);
13071380
}
13081381

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,12 @@ public struct AddBlocker: ExpressionMacro {
8484
// Link the folded argument back into the tree.
8585
var node = node.withArgumentList(node.argumentList.replacing(childAt: 0, with: node.argumentList.first!.withExpression(foldedArgument.as(ExprSyntax.self)!)))
8686

87-
class AddVisitor: SyntaxVisitor {
87+
class AddVisitor: SyntaxRewriter {
8888
var diagnostics: [Diagnostic] = []
8989

90-
init() {
91-
super.init(viewMode: .sourceAccurate)
92-
}
93-
9490
override func visit(
9591
_ node: InfixOperatorExprSyntax
96-
) -> SyntaxVisitorContinueKind {
92+
) -> ExprSyntax {
9793
if let binOp = node.operatorOperand.as(BinaryOperatorExprSyntax.self) {
9894
if binOp.operatorToken.text == "+" {
9995
let messageID = MessageID(domain: "silly", id: "addblock")
@@ -131,20 +127,30 @@ public struct AddBlocker: ExpressionMacro {
131127
]
132128
)
133129
)
130+
131+
return ExprSyntax(
132+
node.withOperatorOperand(
133+
ExprSyntax(
134+
binOp.withOperatorToken(
135+
binOp.operatorToken.withKind(.spacedBinaryOperator("-"))
136+
)
137+
)
138+
)
139+
)
134140
}
135141
}
136142

137-
return .visitChildren
143+
return ExprSyntax(node)
138144
}
139145
}
140146

141147
let visitor = AddVisitor()
142-
visitor.walk(Syntax(node))
148+
let result = visitor.visit(Syntax(node))
143149

144150
for diag in visitor.diagnostics {
145151
context.diagnose(diag)
146152
}
147153

148-
return argument
154+
return result.as(MacroExpansionExprSyntax.self)!.argumentList.first!.expression
149155
}
150156
}

test/Macros/macro_expand.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,15 @@ testStringify(a: 1, b: 1)
5050

5151
macro addBlocker<T>(_ value: T) -> T = MacroDefinition.AddBlocker
5252

53-
func testAddBlocker(a: Int, b: Int, c: Int) {
53+
struct OnlyAdds {
54+
static func +(lhs: OnlyAdds, rhs: OnlyAdds) -> OnlyAdds { lhs }
55+
}
56+
57+
func testAddBlocker(a: Int, b: Int, c: Int, oa: OnlyAdds) {
5458
_ = #addBlocker(a * b * c)
5559
#if TEST_DIAGNOSTICS
5660
_ = #addBlocker(a + b * c) // expected-error{{blocked an add; did you mean to subtract? (from macro 'addBlocker')}}{{21-22=-}}
61+
_ = #addBlocker(oa + oa) // expected-error{{blocked an add; did you mean to subtract? (from macro 'addBlocker')}}{{22-23=-}}
62+
// expected-note@-1{{in expansion of macro 'addBlocker' here}}
5763
#endif
5864
}

0 commit comments

Comments
 (0)