Skip to content

[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

Closed
wants to merge 8 commits into from

Conversation

rxwei
Copy link
Contributor

@rxwei rxwei commented Jan 25, 2023

  • Parse freestanding macro expansions as declarations rather than expressions when it's not part of a statement or expression (i.e. "top-level"). When it's a freestanding expression macro,
  • Store the macro expansion result as a BraceStmt (mirroring CodeBlockItemListSyntax 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.
  • Teach name lookup to look into the result of macro expansions. Plumb NLOptions through DeclContext lookup so that we can break the dependency cycle when we look up macros.

TODOs:

  • Combine @expression and @declaration(freestanding) to a single @freestanding attribute.
  • Fix local expansions.

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.
@rxwei rxwei force-pushed the declaration-macro-name-lookup branch from fc2f277 to 283c5cd Compare January 26, 2023 04:55
@rxwei rxwei force-pushed the declaration-macro-name-lookup branch from f133e42 to b6552ed Compare January 26, 2023 15:56
@rxwei rxwei force-pushed the declaration-macro-name-lookup branch 2 times, most recently from d4598c2 to a11e791 Compare January 26, 2023 16:58
@rxwei rxwei force-pushed the declaration-macro-name-lookup branch from a11e791 to f030142 Compare January 26, 2023 17:50
@@ -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;
Copy link
Member

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?

Copy link
Contributor Author

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)) {
Copy link
Member

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?

Copy link
Contributor Author

@rxwei rxwei Jan 27, 2023

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()))
Copy link
Member

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;
Copy link
Member

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);
Copy link
Member

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.

Comment on lines +3707 to +3714
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;
}
Copy link
Member

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?

cs.setType(arg, TypeChecker::typeCheckExpression(arg, dc));
SmallVector<Argument, 2> newArgs;
newArgs.reserve(args->size());
for (auto arg : *args) {
Copy link
Member

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.

Copy link
Contributor Author

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,
Copy link
Member

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?

Copy link
Contributor Author

@rxwei rxwei Feb 9, 2023

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)
Copy link
Member

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);
Copy link
Member

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?

Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants