Skip to content

[AST] Always walk folded SequenceExpr if available #80242

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions include/swift/AST/ASTWalker.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,6 @@ enum class QualifiedIdentTypeReprWalkingScheme {
ASTOrderRecursive
};

/// Specifies the behavior for walking SequenceExprs.
enum class SequenceWalking {
/// Walk into every element of the sequence, regardless of what state the
/// sequence is in.
Default,

/// If the sequence has been folded by type checking, only walk into the
/// elements that represent the operator nodes. This will ensure that the walk
/// does not visit the same AST nodes twice when it encounters a sequence that
/// has already been folded but hasn't been removed from the AST.
OnlyWalkFirstOperatorWhenFolded
};

/// An abstract class used to traverse an AST.
class ASTWalker {
public:
Expand Down Expand Up @@ -629,13 +616,6 @@ class ASTWalker {
return MacroWalking::ArgumentsAndExpansion;
}

/// This method configures how the walker should walk into SequenceExprs.
/// Needing to customize this behavior should be rare, as sequence expressions
/// are only encountered in un-typechecked ASTs.
virtual SequenceWalking getSequenceWalkingBehavior() const {
return SequenceWalking::Default;
}

/// This method determines whether the given declaration should be
/// considered to be in a macro expansion context. It can be configured
/// by subclasses.
Expand Down
19 changes: 12 additions & 7 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,9 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
IsObjC : 1
);

SWIFT_INLINE_BITFIELD_FULL(SequenceExpr, Expr, 32+1,
SWIFT_INLINE_BITFIELD_FULL(SequenceExpr, Expr, 32,
: NumPadBits,
NumElements : 32,
IsFolded: 1
NumElements : 32
);

SWIFT_INLINE_BITFIELD(OpaqueValueExpr, Expr, 1,
Expand Down Expand Up @@ -3970,6 +3969,9 @@ class SequenceExpr final : public Expr,
private llvm::TrailingObjects<SequenceExpr, Expr *> {
friend TrailingObjects;

/// The cached folded expression.
Expr *FoldedExpr = nullptr;

SequenceExpr(ArrayRef<Expr*> elements)
: Expr(ExprKind::Sequence, /*Implicit=*/false) {
Bits.SequenceExpr.NumElements = elements.size();
Expand Down Expand Up @@ -4005,11 +4007,14 @@ class SequenceExpr final : public Expr,
getElements()[i] = e;
}

bool isFolded() const {
return static_cast<bool>(Bits.SequenceExpr.IsFolded);
/// Retrieve the folded expression, or \c nullptr if the SequencExpr has
/// not yet been folded.
Expr *getFoldedExpr() const {
return FoldedExpr;
}
void setFolded(bool folded) {
Bits.SequenceExpr.IsFolded = static_cast<unsigned>(folded);

void setFoldedExpr(Expr *foldedExpr) {
FoldedExpr = foldedExpr;
}

// Implement isa/cast/dyncast/etc.
Expand Down
19 changes: 12 additions & 7 deletions lib/AST/ASTWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -950,13 +950,18 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
}

Expr *visitSequenceExpr(SequenceExpr *E) {
if (Walker.getSequenceWalkingBehavior() ==
SequenceWalking::OnlyWalkFirstOperatorWhenFolded &&
E->isFolded()) {
if (E->getNumElements() > 1) {
if (Expr *Elt = doIt(E->getElement(1)))
E->setElement(1, Elt);
}
// After folding, the SequenceExpr elements can contain a broken mess of
// partially folded and/or duplicate nodes (since folding can mutate nodes
// in-place). We remove the SequenceExpr from the AST after folding, but
// there's still a period of time during pre-checking when it's still in the
// AST. To avoid issues for e.g ASTScope and availability scope
// construction, only walk the folded expression if we have it.
if (auto *foldedExpr = E->getFoldedExpr()) {
auto *newExpr = doIt(foldedExpr);
if (!newExpr)
return nullptr;

E->setFoldedExpr(newExpr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record, I suggested to Allan to do it this way in his original PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting, @tshortli any objections to changing this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to land for now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No objections, I just wasn't interested in taking on extra risk by changing the behavior fundamentally for 6.1. If this seems like the right fix in general I'm happy to have it simplified.

return E;
}

Expand Down
9 changes: 0 additions & 9 deletions lib/AST/AvailabilityScopeBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,6 @@ class AvailabilityScopeBuilder : private ASTWalker {
return MacroWalking::Arguments;
}

SequenceWalking getSequenceWalkingBehavior() const override {
// Since availability scopes may be built at arbitrary times, the builder
// may encounter ASTs where SequenceExprs still exist and have not been
// folded, or it may encounter folded SequenceExprs that have not been
// removed from the AST. When folded exprs are encountered, its important
// to avoid walking into the same AST nodes twice.
return SequenceWalking::OnlyWalkFirstOperatorWhenFolded;
}

/// Check whether this declaration is within a macro expansion buffer that
/// will have its own availability scope that will be lazily expanded.
bool isDeclInMacroExpansion(Decl *decl) const override {
Expand Down
1 change: 0 additions & 1 deletion lib/Sema/PreCheckTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,6 @@ class PreCheckTarget final : public ASTWalker {
if (!result)
return Action::Stop();
// Already walked.
seqExpr->setFolded(true);
return Action::SkipNode(result);
}

Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ Expr *TypeChecker::foldSequence(SequenceExpr *expr, DeclContext *dc) {
Expr *Result = ::foldSequence(dc, LHS, Elts, PrecedenceBound());
assert(Elts.empty());

expr->setFoldedExpr(Result);
return Result;
}

Expand Down
4 changes: 4 additions & 0 deletions test/ClangImporter/objc_failable_inits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ func testDictionary() {
func testString() throws {
// Optional
let stringOpt = NSString(path: "blah", encoding: 0)
// expected-note@-1 {{short-circuit using 'guard' to exit this function early if the optional value contains 'nil'}}
// expected-note@-2 {{coalesce using '??' to provide a default when the optional value contains 'nil'}}
// expected-note@-3 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}

_ = stringOpt as NSString // expected-error{{value of optional type 'NSString?' must be unwrapped to a value of type 'NSString'}}
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}}
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
Expand Down
19 changes: 19 additions & 0 deletions test/NameLookup/scope_map_rdar147751795.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Note: test of the scope map. All of these tests are line- and
// column-sensitive, so any additions should go at the end.

struct X {}
var y: () -> X

struct S {
let x: () = y = {() -> X in
return .init()
}
}

// RUN: %target-swift-frontend -dump-scope-maps expanded %s 2>&1 | %FileCheck %s

// rdar://147751795 - Make sure we don't end up with a duplicate closure scope.
// CHECK: PatternEntryInitializerScope {{.*}}, [8:15 - 10:3] entry 0 'x'
// CHECK-NEXT: `-ClosureParametersScope {{.*}}, [8:28 - 10:3]
// CHECK-NEXT: `-BraceStmtScope {{.*}}, [8:28 - 10:3]
// CHECK-EMPTY: