-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Macros] Freestanding macro expansions in code blocks #63217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Parse freestanding macro expansions as declarations rather than expressions when it's not part of a statement or expression (i.e. "top-level"). Teach name lookup to look into the result of macro expansions.
fc2f277
to
283c5cd
Compare
…or/swift into declaration-macro-name-lookup
f133e42
to
b6552ed
Compare
d4598c2
to
a11e791
Compare
a11e791
to
f030142
Compare
@@ -8452,7 +8453,8 @@ class MacroExpansionDecl : public Decl { | |||
SourceLoc LeftAngleLoc, RightAngleLoc; | |||
ArrayRef<TypeRepr *> GenericArgs; | |||
ArgumentList *ArgList; | |||
ArrayRef<Decl *> Rewritten; | |||
// An implicit brace statement serving as the container for the expansion. | |||
BraceStmt *Rewritten = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brace statements imply a nested scope, which probably isn't what we want semantically. Can this become an ArrayRef<ASTNode>
to mimic the [CodeBlockItemSyntax]
we recently agreed upon for the macro implementation interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I was relying on BraceStmt
merely as a convenient container, and the BraceStmt
was never visited as a child of MacroExpransionDecl
; ASTWalker directly walks the BraceStmt
's children instead. I think it's better than ArrayRef<ASTNode>
because we can distinguish between nullptr
(expansion failure) and an empty list from the return of swift::expandFreestandingMacro()
. Otherwise I would need to make it return a bool
and produce indirect results into a ASTNode
vector. Thoughts?
if (med->getDeclContext()->getContextKind() == | ||
DeclContextKind::FileUnit) | ||
return true; | ||
} else if (auto *md = dyn_cast<MacroDecl>(decl)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why check for a macro declaration here? It's only macro expansions that we need to worry about, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we populate SourceLookupCache
with top-level macro expansion results, type-checking a MacroDecl
's arguments will run into the same circular dependency problem as type-checking a top-level MacroExpansionDecl
's arguments. So I needed to check for both MD and MED to break the loop.
@@ -430,11 +430,13 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*, | |||
} | |||
|
|||
bool visitMacroExpansionDecl(MacroExpansionDecl *MED) { | |||
if (MED->getArgs() && doIt(MED->getArgs())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we need a customization point here, because it seems like some things really want the see the original source, other things want to see the rewritten source, and some will want to see both.
@@ -229,6 +233,7 @@ SourceLookupCache &SourceFile::getCache() const { | |||
template<typename Range> | |||
void SourceLookupCache::addToUnqualifiedLookupCache(Range decls, | |||
bool onlyOperators) { | |||
SmallVector<MacroExpansionDecl *, 4> delayedMacroExpansions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks unused?
if (rewritten) | ||
for (auto node : rewritten->getElements()) | ||
if (auto *decl = node.dyn_cast<Decl *>()) | ||
addToUnqualifiedLookupCache(TinyPtrVector<Decl *>{decl}, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The macro expansion could produce other macro expansion declarations, and we should make sure to account for those. It currently looks like addToUnqualifiedLookupCache
could add to DelayedMacroExpansions
while we're iterating over it. We'll either not see those added macro expansion declarations (because of the clear()
below) or we'll crash if the vector gets reallocated.
MacroRoles viableRoles(MacroRole::Declaration); | ||
// In a code block context, a macro expansion decl can also expand to an | ||
// arbitrary code item. | ||
if (dc->isLocalContext() || | ||
dc->getContextKind() == DeclContextKind::FileUnit) { | ||
viableRoles |= MacroRole::Expression; | ||
viableRoles |= MacroRole::CodeItem; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely an important piece of logic. What roles are acceptable, and where?
lib/Sema/TypeCheckDeclPrimary.cpp
Outdated
cs.setType(arg, TypeChecker::typeCheckExpression(arg, dc)); | ||
SmallVector<Argument, 2> newArgs; | ||
newArgs.reserve(args->size()); | ||
for (auto arg : *args) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's similar logic to this for attached macros, where we need to type-check @macroname(args...)
. We should unify the logic with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in #63247. ResolveAttachedMacroRequest
's approach of forming and type-checking a OverloadedDeclRefExpr
was very clever. I was able to remove all of the custom constraint generation code.
IncludeUsableFromInline = 1 << 2, | ||
/// Disable macro expansions. | ||
DisableMacroExpansions = 1 << 3, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These "disable macro expansions" flags end up in a lot of places. Are they all necessary, or can some be re-derived from ASTScope and location info?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The need for DisableMacroExpansions
is all related to default literal type lookup. Default literal type lookup tries to find a literal type name in the module scope, which, without the added flag, triggers top-level macro expansions. DefaultTypeRequest
also doesn't have source location to check AST scope information, so we'd have to plumb through the locations from every call site, which I did give a try. That added location, however, caused default literal type lookup, which uses unqualified lookup at in the module scope context, to look into type members, at which point it felt like opening a can of worms and it was no simpler than adding DisableMacroExpansions
.
@@ -48,3 +47,7 @@ macro am1: Void | |||
) | |||
macro am2: Void | |||
// expected-error@-1{{macro 'am2' requires a definition}} | |||
|
|||
@expression @declaration(freestanding) @declaration(attached) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, @declaration(freestanding)
has been replaced with @freestanding(declaration)
on main
.
} | ||
auto codeBlockItemList = BraceStmt::createImplicit(ctx, {expansion}); | ||
MED->setRewritten(codeBlockItemList); | ||
TypeChecker::postTypeCheckMacroExpansion(mee, innermostDC); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's interesting that this doesn't happen as part of type-checking the outer context. Is this because at some outer level (say, walking all of the code items in the enclosing BraceStmt
) we should also be walking into macro expansions? Similar to my comment earlier about having a semantic query that flattens all of the macro expansions so you walk a semantic view vs. the syntactic view?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BraceStmt
isn't the problem because it was only a means of code block storage in MacroExpansionDecl
, and was never visited by any walker (the walker skips this level). In #63553 I placed BraceStmt
with ArrayRef<ASTNode>
and the need for postTypeCheck...
still remains.
I think the reason is because those post-checking passes are all triggered from the AST verifier, but the verifier isn't triggering macro expansions.
BraceStmt
(mirroringCodeBlockItemListSyntax
as in [Macros] Freestanding macros expand to CodeBlockItemList; parse top-level macro expansion decls swift-syntax#1268) to allow freestanding macros to produce any code block item.NLOptions
throughDeclContext
lookup so that we can break the dependency cycle when we look up macros.TODOs:
@expression
and@declaration(freestanding)
to a single@freestanding
attribute.