Files
swift-mirror/include/swift/AST/ASTWalker.h
Hamish Knight 7522a3ea5b [SourceKit] Handle CustomAttrs arguments for placeholder expansion
Introduce a new ASTWalker option for walking CustomAttrs and use it
for the placeholder scanner to ensure we can expand placeholders in
attribute arguments.
2025-05-21 18:06:54 +01:00

755 lines
26 KiB
C++

//===--- ASTWalker.h - Class for walking the AST ----------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_AST_ASTWALKER_H
#define SWIFT_AST_ASTWALKER_H
#include "swift/Basic/LLVM.h"
#include "llvm/ADT/PointerUnion.h"
#include <optional>
#include <utility>
namespace swift {
class Argument;
class ArgumentList;
class Decl;
class Expr;
class ClosureExpr;
class CustomAttr;
class ModuleDecl;
class PackageUnit;
class Stmt;
class Pattern;
class TypeRepr;
class ParameterList;
enum class AccessKind: unsigned char;
enum class SemaReferenceKind : uint8_t {
ModuleRef = 0,
DeclRef,
DeclMemberRef,
DeclConstructorRef,
TypeRef,
EnumElementRef,
SubscriptRef,
DynamicMemberRef,
};
struct ReferenceMetaData {
SemaReferenceKind Kind;
std::optional<AccessKind> AccKind;
bool isImplicit = false;
bool isImplicitCtorType = false;
/// When non-none, this is a custom attribute reference.
std::optional<std::pair<const CustomAttr *, Decl *>> CustomAttrRef;
ReferenceMetaData(SemaReferenceKind Kind, std::optional<AccessKind> AccKind,
bool isImplicit = false,
std::optional<std::pair<const CustomAttr *, Decl *>>
customAttrRef = std::nullopt)
: Kind(Kind), AccKind(AccKind), isImplicit(isImplicit),
CustomAttrRef(customAttrRef) {}
};
/// Specifies how the initialization expression of a \c lazy variable should be
/// walked by the ASTWalker.
enum class LazyInitializerWalking {
/// No lazy initialization expressions will be walked.
None,
/// The lazy initialization expression will only be walked as a part of
/// the variable's pattern binding decl. This is the default behavior, and is
/// consistent with the initializer being syntactically part of the pattern
/// binding.
InPatternBinding,
/// The lazy initialization expression will only be walked as part of the
/// body of the synthesized accessor for the lazy variable. In such an
/// accessor, the expression is denoted by LazyInitializerExpr. This is mainly
/// useful for code emission.
InAccessor
};
/// Specifies the behavior for walking a macro expansion, whether we want to
/// see the macro arguments, the expansion, or both.
enum class MacroWalking {
/// Walk into the expansion of the macro, to see the semantic effect of
/// the macro expansion.
Expansion,
/// Walk into the arguments of the macro as written in the source code.
///
/// The actual arguments walked may not make it into the program itself,
/// because they can be translated by the macro in arbitrary ways.
Arguments,
/// Walk into both the arguments of the macro as written in the source code
/// and also the macro expansion.
ArgumentsAndExpansion,
/// Don't walk into macros.
None
};
/// A scheme for walking a `QualifiedIdentTypeRepr`.
enum class QualifiedIdentTypeReprWalkingScheme {
/// Walk in source order, such that each subsequent dot-separated component is
/// a child of the previous one. For example, walk `A.B<T.U>.C` like so
/// (top-down order):
///
/// ```
/// A
/// ╰─B
/// ├─T
/// │ ╰─U
/// ╰─C
/// ```
SourceOrderRecursive,
/// Walk in AST order (that is, according to how member type
/// representations are modeled in the AST, such that each previous
/// dot-separated component is a child of the subsequent one), base before
/// generic arguments. For example, walk `A.B<T.U>.C` like so
/// (top-down order):
///
/// ```
/// C
/// ╰─B
/// ├─A
/// ╰─U
/// ╰─T
/// ```
ASTOrderRecursive
};
/// An abstract class used to traverse an AST.
class ASTWalker {
public:
enum class ParentKind {
Package, Module, Decl, Stmt, Expr, Pattern, TypeRepr
};
class ParentTy {
ParentKind Kind;
void *Ptr = nullptr;
public:
ParentTy(PackageUnit *Pkg) : Kind(ParentKind::Package), Ptr(Pkg) {}
ParentTy(ModuleDecl *Mod) : Kind(ParentKind::Module), Ptr(Mod) {}
ParentTy(Decl *D) : Kind(ParentKind::Decl), Ptr(D) {}
ParentTy(Stmt *S) : Kind(ParentKind::Stmt), Ptr(S) {}
ParentTy(Expr *E) : Kind(ParentKind::Expr), Ptr(E) {}
ParentTy(Pattern *P) : Kind(ParentKind::Pattern), Ptr(P) {}
ParentTy(TypeRepr *T) : Kind(ParentKind::TypeRepr), Ptr(T) {}
ParentTy() : Kind(ParentKind::Module), Ptr(nullptr) { }
bool isNull() const { return Ptr == nullptr; }
ParentKind getKind() const {
assert(!isNull());
return Kind;
}
PackageUnit *getAsPackage() const {
return Kind == ParentKind::Package ? static_cast<PackageUnit*>(Ptr)
: nullptr;
}
ModuleDecl *getAsModule() const {
return Kind == ParentKind::Module ? static_cast<ModuleDecl*>(Ptr)
: nullptr;
}
Decl *getAsDecl() const {
return Kind == ParentKind::Decl ? static_cast<Decl*>(Ptr) : nullptr;
}
Stmt *getAsStmt() const {
return Kind == ParentKind::Stmt ? static_cast<Stmt*>(Ptr) : nullptr;
}
Expr *getAsExpr() const {
return Kind == ParentKind::Expr ? static_cast<Expr*>(Ptr) : nullptr;
}
Pattern *getAsPattern() const {
return Kind == ParentKind::Pattern ? static_cast<Pattern*>(Ptr) : nullptr;
}
TypeRepr *getAsTypeRepr() const {
return Kind==ParentKind::TypeRepr ? static_cast<TypeRepr*>(Ptr) : nullptr;
}
};
/// The parent of the node we are visiting.
ParentTy Parent;
struct _Detail {
_Detail() = delete;
// The 'Action' set of types, which do not take a payload.
struct ContinueWalkAction {};
struct SkipChildrenIfWalkAction { bool Cond; bool SkipPostWalk; };
struct StopIfWalkAction { bool Cond; };
struct StopWalkAction {};
// The 'Result' set of types, which do take a payload.
template <typename T>
struct ContinueWalkResult {
ContinueWalkAction Action;
T Value;
};
template <typename T>
struct SkipChildrenIfWalkResult {
SkipChildrenIfWalkAction Action;
T Value;
};
template <typename T>
struct StopIfWalkResult {
StopIfWalkAction Action;
T Value;
};
};
/// A namespace for ASTWalker actions that may be returned from pre-walk and
/// post-walk functions.
///
/// Only certain AST nodes support being replaced during a walk. The
/// visitation methods for such nodes use the PreWalkResult/PostWalkResult
/// types, which store both the action to take, along with the AST node to
/// splice into the tree in place of the old node. The node must be provided
/// to \c Action::<action> function, e.g \c Action::Continue(E) or
/// \c Action::SkipChildren(E). The only exception is \c Action::Stop(),
/// which never accepts a node.
///
/// AST nodes that do not support being replaced during a walk use visitation
/// methods that return PreWalkAction/PostWalkAction. These just store the
/// walking action to perform, and you return e.g \c Action::Continue() or
/// \c Action::SkipChildren().
///
/// Each function here returns a separate underscored type, which is then
/// consumed by PreWalkAction/PreWalkResult/PostWalkAction/PostWalkAction. It
/// is designed this way to achieve a pseudo form of return type overloading,
/// where e.g \c Action::Continue() can become either a pre-walk action or a
/// post-walk action, but \c Action::SkipChildren() can only become a pre-walk
/// action.
struct Action {
Action() = delete;
/// Continue the current walk, replacing the current node with \p node.
template <typename T>
static _Detail::ContinueWalkResult<T> Continue(T node) {
return {Continue(), std::move(node)};
}
/// Continue the current walk, replacing the current node with \p node.
/// However, skip visiting the children of \p node, and resume at its
/// post-walk.
template <typename T>
static _Detail::SkipChildrenIfWalkResult<T> SkipChildren(T node) {
return SkipChildrenIf(true, std::move(node));
}
/// Similar to \c Action::SkipChildren, but also skips the call to the
/// post-visitation method.
template <typename T>
static _Detail::SkipChildrenIfWalkResult<T>
SkipNode(T node) {
return SkipNodeIf(true, std::move(node));
}
/// If \p cond is true, this is equivalent to \c Action::SkipChildren(node).
/// Otherwise, it is equivalent to \c Action::Continue(node).
template <typename T>
static _Detail::SkipChildrenIfWalkResult<T>
SkipChildrenIf(bool cond, T node) {
return {SkipChildrenIf(cond), std::move(node)};
}
/// If \p cond is true, this is equivalent to \c Action::SkipNode(node).
/// Otherwise, it is equivalent to \c Action::Continue(node).
template <typename T>
static _Detail::SkipChildrenIfWalkResult<T>
SkipNodeIf(bool cond, T node) {
return {SkipNodeIf(cond), std::move(node)};
}
/// If \p cond is true, this is equivalent to \c Action::Continue(node).
/// Otherwise, it is equivalent to \c Action::SkipChildren(node).
template <typename T>
static _Detail::SkipChildrenIfWalkResult<T>
VisitChildrenIf(bool cond, T node) {
return SkipChildrenIf(!cond, std::move(node));
}
/// If \p cond is true, this is equivalent to \c Action::Continue(node).
/// Otherwise, it is equivalent to \c Action::SkipNode(node).
template <typename T>
static _Detail::SkipChildrenIfWalkResult<T>
VisitNodeIf(bool cond, T node) {
return SkipNodeIf(!cond, std::move(node));
}
/// If \p cond is true, this is equivalent to \c Action::Stop().
/// Otherwise, it is equivalent to \c Action::Continue(node).
template <typename T>
static _Detail::StopIfWalkResult<T> StopIf(bool cond, T node) {
return {StopIf(cond), std::move(node)};
}
/// Continue the current walk.
static _Detail::ContinueWalkAction Continue() { return {}; }
/// Continue the current walk, but do not visit the children of the current
/// node, resuming at its post-walk.
static _Detail::SkipChildrenIfWalkAction SkipChildren() {
return SkipChildrenIf(true);
}
/// Similar to \c Action::SkipChildren, but also skips the call to the
/// post-visitation method.
static _Detail::SkipChildrenIfWalkAction SkipNode() {
return SkipNodeIf(true);
}
/// If \p cond is true, this is equivalent to \c Action::SkipChildren().
/// Otherwise, it is equivalent to \c Action::Continue().
static _Detail::SkipChildrenIfWalkAction SkipChildrenIf(bool cond) {
return {cond, /*SkipPostWalk*/ false};
}
/// If \p cond is true, this is equivalent to \c Action::SkipNode().
/// Otherwise, it is equivalent to \c Action::Continue().
static _Detail::SkipChildrenIfWalkAction
SkipNodeIf(bool cond) {
return {cond, /*SkipPostWalk*/ true};
}
/// If \p cond is true, this is equivalent to \c Action::Continue().
/// Otherwise, it is equivalent to \c Action::SkipChildren().
static _Detail::SkipChildrenIfWalkAction VisitChildrenIf(bool cond) {
return SkipChildrenIf(!cond);
}
/// If \p cond is true, this is equivalent to \c Action::Continue().
/// Otherwise, it is equivalent to \c Action::SkipNode().
static _Detail::SkipChildrenIfWalkAction
VisitNodeIf(bool cond) {
return SkipNodeIf(!cond);
}
/// Terminate the walk, returning without visiting any other nodes.
static _Detail::StopWalkAction Stop() { return {}; }
/// If \p cond is true, this is equivalent to \c Action::Stop().
/// Otherwise, it is equivalent to \c Action::Continue().
static _Detail::StopIfWalkAction StopIf(bool cond) { return {cond}; }
};
/// Do not construct directly, use \c Action::<action> instead.
///
/// A pre-visitation action for AST nodes that do not support being replaced
/// while walking.
struct PreWalkAction {
enum Kind { Stop, SkipChildren, SkipNode, Continue };
Kind Action;
PreWalkAction(_Detail::ContinueWalkAction) : Action(Continue) {}
PreWalkAction(_Detail::StopWalkAction) : Action(Stop) {}
PreWalkAction(_Detail::SkipChildrenIfWalkAction action) {
if (action.Cond) {
Action = action.SkipPostWalk ? SkipNode : SkipChildren;
} else {
Action = Continue;
}
}
PreWalkAction(_Detail::StopIfWalkAction action)
: Action(action.Cond ? Stop : Continue) {}
};
/// Do not construct directly, use \c Action::<action> instead.
///
/// A post-visitation action for AST nodes that do not support being replaced
/// while walking.
struct PostWalkAction {
enum Kind { Stop, Continue };
Kind Action;
PostWalkAction(_Detail::ContinueWalkAction) : Action(Continue) {}
PostWalkAction(_Detail::StopWalkAction) : Action(Stop) {}
PostWalkAction(_Detail::StopIfWalkAction action)
: Action(action.Cond ? Stop : Continue) {}
};
/// Do not construct directly, use \c Action::<action> instead.
///
/// A pre-visitation result for AST nodes that support being replaced while
/// walking. Stores both the walking action to take, along with the node to
/// splice into the AST in place of the old node.
template <typename T>
struct PreWalkResult {
PreWalkAction Action;
std::optional<T> Value;
template <typename U,
typename std::enable_if<std::is_convertible<U, T>::value>::type
* = nullptr>
PreWalkResult(const PreWalkResult<U> &Other) : Action(Other.Action) {
if (Other.Value)
Value = *Other.Value;
}
template <typename U,
typename std::enable_if<std::is_convertible<U, T>::value>::type
* = nullptr>
PreWalkResult(PreWalkResult<U> &&Other) : Action(Other.Action) {
if (Other.Value)
Value = std::move(*Other.Value);
}
template <typename U>
PreWalkResult(_Detail::ContinueWalkResult<U> Result)
: Action(Result.Action), Value(std::move(Result.Value)) {}
template <typename U>
PreWalkResult(_Detail::SkipChildrenIfWalkResult<U> Result)
: Action(Result.Action), Value(std::move(Result.Value)) {}
template <typename U>
PreWalkResult(_Detail::StopIfWalkResult<U> Result)
: Action(Result.Action), Value(std::move(Result.Value)) {}
PreWalkResult(_Detail::StopWalkAction Action)
: Action(Action), Value(std::nullopt) {}
};
/// Do not construct directly, use \c Action::<action> instead.
///
/// A post-visitation result for AST nodes that support being replaced while
/// walking. Stores both the walking action to take, along with the node to
/// splice into the AST in place of the old node.
template <typename T>
struct PostWalkResult {
PostWalkAction Action;
std::optional<T> Value;
template <typename U,
typename std::enable_if<std::is_convertible<U, T>::value>::type
* = nullptr>
PostWalkResult(const PostWalkResult<U> &Other) : Action(Other.Action) {
if (Other.Value)
Value = *Other.Value;
}
template <typename U,
typename std::enable_if<std::is_convertible<U, T>::value>::type
* = nullptr>
PostWalkResult(PostWalkResult<U> &&Other) : Action(Other.Action) {
if (Other.Value)
Value = std::move(*Other.Value);
}
template <typename U>
PostWalkResult(_Detail::ContinueWalkResult<U> Result)
: Action(Result.Action), Value(std::move(Result.Value)) {}
template <typename U>
PostWalkResult(_Detail::StopIfWalkResult<U> Result)
: Action(Result.Action), Value(std::move(Result.Value)) {}
PostWalkResult(_Detail::StopWalkAction Action)
: Action(Action), Value(std::nullopt) {}
};
/// This method is called when first visiting an expression
/// before walking into its children.
///
/// \param E The expression to check.
///
/// \returns A result that contains a potentially re-written expression,
/// along with the walk action to perform. The default implementation
/// returns \c Action::Continue(E).
///
virtual PreWalkResult<Expr *> walkToExprPre(Expr *E) {
return Action::Continue(E);
}
/// This method is called after visiting an expression's children. If a new
/// expression is returned, it is spliced in where the old expression
/// previously appeared.
///
/// \param E The expression that was walked.
///
/// \returns A result that contains a potentially re-written expression,
/// along with the walk action to perform. The default implementation
/// returns \c Action::Continue(E).
///
virtual PostWalkResult<Expr *> walkToExprPost(Expr *E) {
return Action::Continue(E);
}
/// This method is called when first visiting a statement before
/// walking into its children.
///
/// \param S The statement to check.
///
/// \returns A result that contains a potentially re-written statement,
/// along with the walk action to perform. The default implementation
/// returns \c Action::Continue(S).
///
virtual PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) {
return Action::Continue(S);
}
/// This method is called after visiting an statements's children. If a new
/// statement is returned, it is spliced in where the old statement
/// previously appeared.
///
/// \param S The statement that was walked.
///
/// \returns A result that contains a potentially re-written statement,
/// along with the walk action to perform. The default implementation
/// returns \c Action::Continue(S).
///
virtual PostWalkResult<Stmt *> walkToStmtPost(Stmt *S) {
return Action::Continue(S);
}
/// This method is called when first visiting a pattern before walking into
/// its children.
///
/// \param P The statement to check.
///
/// \returns A result that contains a potentially re-written pattern,
/// along with the walk action to perform. The default implementation
/// returns \c Action::Continue(P).
///
virtual PreWalkResult<Pattern *> walkToPatternPre(Pattern *P) {
return Action::Continue(P);
}
/// This method is called after visiting an pattern's children. If a new
/// pattern is returned, it is spliced in where the old pattern
/// previously appeared.
///
/// \param P The pattern that was walked.
///
/// \returns A result that contains a potentially re-written pattern,
/// along with the walk action to perform. The default implementation
/// returns \c Action::Continue(P).
///
virtual PostWalkResult<Pattern *> walkToPatternPost(Pattern *P) {
return Action::Continue(P);
}
/// walkToDeclPre - This method is called when first visiting a decl, before
/// walking into its children.
///
/// \param D The declaration to check. The callee may update this declaration
/// in-place.
///
/// \returns The walking action to perform. By default, this
/// is \c Action::Continue().
///
virtual PreWalkAction walkToDeclPre(Decl *D) { return Action::Continue(); }
/// walkToDeclPost - This method is called after visiting the children of a
/// decl.
///
/// \param D The declaration that was walked.
///
/// \returns The walking action to perform. By default, this
/// is \c Action::Continue().
///
virtual PostWalkAction walkToDeclPost(Decl *D) { return Action::Continue(); }
/// This method is called when first visiting a TypeRepr, before
/// walking into its children.
///
/// \param T The TypeRepr to check.
///
/// \returns The walking action to perform. By default, this
/// is \c Action::Continue().
///
virtual PreWalkAction walkToTypeReprPre(TypeRepr *T) {
return Action::Continue();
}
/// This method is called after visiting the children of a TypeRepr.
///
/// \param T The type that was walked.
///
/// \returns The walking action to perform. By default, this
/// is \c Action::Continue().
///
virtual PostWalkAction walkToTypeReprPost(TypeRepr *T) {
return Action::Continue();
}
/// This method configures how to walk `QualifiedIdentTypeRepr` nodes.
virtual QualifiedIdentTypeReprWalkingScheme
getQualifiedIdentTypeReprWalkingScheme() const {
return QualifiedIdentTypeReprWalkingScheme::ASTOrderRecursive;
}
/// This method configures whether the walker should explore into the generic
/// params in AbstractFunctionDecl and NominalTypeDecl.
virtual bool shouldWalkIntoGenericParams() { return false; }
/// Whether the walker should walk into any attached CustomAttrs.
virtual bool shouldWalkIntoCustomAttrs() const {
// Default to false currently since some walkers don't handle this case
// well.
return false;
}
/// This method configures how the walker should walk the initializers of
/// lazy variables. These initializers are semantically different from other
/// initializers in their context and so sometimes should be visited as part
/// of the synthesized getter, or should not be visited at all.
virtual LazyInitializerWalking getLazyInitializerWalkingBehavior() {
return LazyInitializerWalking::InPatternBinding;
}
/// This method configures how the walker should walk into uses of macros.
virtual MacroWalking getMacroWalkingBehavior() const {
return MacroWalking::ArgumentsAndExpansion;
}
/// This method determines whether the given declaration should be
/// considered to be in a macro expansion context. It can be configured
/// by subclasses.
virtual bool isDeclInMacroExpansion(Decl *decl) const;
/// Determine whether we should walk macro arguments (as they appear in
/// source) and the expansion (which is semantically part of the program).
std::pair<bool, bool> shouldWalkMacroArgumentsAndExpansion() const {
switch (getMacroWalkingBehavior()) {
case MacroWalking::Expansion:
return std::make_pair(false, true);
case MacroWalking::Arguments:
return std::make_pair(true, false);
case MacroWalking::ArgumentsAndExpansion:
return std::make_pair(true, true);
case MacroWalking::None:
return std::make_pair(false, false);
}
}
/// This method configures whether the walker should visit the body of a
/// TapExpr.
virtual bool shouldWalkIntoTapExpression() { return true; }
/// This method configures whether the walker should visit the underlying
/// value of a property wrapper placeholder.
virtual bool shouldWalkIntoPropertyWrapperPlaceholderValue() { return true; }
/// This method configures whether the walker should exhibit the legacy
/// behavior where accessors appear as peers of their storage, rather
/// than children nested inside of it.
///
/// Please don't write new ASTWalker implementations that override this
/// method to return true; instead, refactor existing code as needed
/// until eventually we can remove this altogether.
virtual bool shouldWalkAccessorsTheOldWay() { return false; }
/// Whether to walk internal top level decls in serialized modules.
///
/// TODO: Consider changing this to false by default.
virtual bool shouldWalkSerializedTopLevelInternalDecls() { return true; }
/// Whether to walk into the definition of a \c MacroDecl if it hasn't been
/// type-checked yet.
virtual bool shouldWalkIntoUncheckedMacroDefinitions() { return false; }
/// walkToParameterListPre - This method is called when first visiting a
/// ParameterList, before walking into its parameters.
///
/// \param PL The parameter list to walk.
///
/// \returns The walking action to perform. By default, this
/// is \c Action::Continue().
///
virtual PreWalkAction walkToParameterListPre(ParameterList *PL) {
return Action::Continue();
}
/// walkToParameterListPost - This method is called after visiting the
/// children of a parameter list.
///
/// \param PL The parameter list that was walked.
///
/// \returns The walking action to perform. By default, this
/// is \c Action::Continue().
///
virtual PostWalkAction walkToParameterListPost(ParameterList *PL) {
return Action::Continue();
}
/// This method is called when first visiting an argument list before walking
/// into its arguments.
///
/// \param ArgList The argument list to walk.
///
/// \returns A result that contains a potentially re-written argument list,
/// along with the walk action to perform.
///
/// The default implementation returns \c Action::Continue(ArgList).
virtual PreWalkResult<ArgumentList *>
walkToArgumentListPre(ArgumentList *ArgList) {
return Action::Continue(ArgList);
}
/// This method is called after visiting the arguments in an argument list.
/// If a new argument list is returned, it is spliced in where the old
/// argument list previously appeared.
///
/// The default implementation returns \c Action::Continue(ArgList).
virtual PostWalkResult<ArgumentList *>
walkToArgumentListPost(ArgumentList *ArgList) {
return Action::Continue(ArgList);
}
/// This method is called when first visiting an argument in an argument list,
/// before walking into its expression.
///
/// \param Arg The argument to walk.
///
/// \returns The walking action to perform.
///
/// The default implementation returns \c Action::Continue().
virtual PreWalkAction walkToArgumentPre(const Argument &Arg) {
return Action::Continue();
}
/// This method is called after visiting an argument in an argument list.
///
/// \returns The walking action to perform.
///
/// The default implementation returns \c Action::Continue().
virtual PostWalkAction walkToArgumentPost(const Argument &Arg) {
return Action::Continue();
}
protected:
ASTWalker() = default;
ASTWalker(const ASTWalker &) = default;
virtual ~ASTWalker() = default;
virtual void anchor();
};
} // end namespace swift
#endif