Skip to content

Type substitution eliminates dependencies with Escapable targets. #80438

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

Conversation

jckarter
Copy link
Contributor

@jckarter jckarter commented Apr 1, 2025

When a generic function has potentially Escapable outputs, those outputs declare lifetime dependencies, which have no effect when substitution leads to those types becoming Escapable in a concrete context. This means that type substitution should canonically eliminate lifetime dependencies targeting Escapable parameters or returns, and that type checking should allow a function value with potentially-Escapable lifetime dependencies to bind to a function type without those dependencies when the target of the dependencies is Escapable.

Fixes rdar://147533059.

@jckarter
Copy link
Contributor Author

jckarter commented Apr 1, 2025

@swift-ci Please test

@jckarter
Copy link
Contributor Author

jckarter commented Apr 1, 2025

@swift-ci Please test Windows

Copy link
Contributor

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

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

Just some nitpicks

/// Returns true if the set of output lifetime dependencies is different from
/// the set of input lifetime dependencies, false if there was no change.
inline bool
filterEscapableLifetimeDependencies(GenericSignature sig,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this belongs in LifetimeDependenceInfo.h?

} else {
// The last lifetime dependency targets the result at the end
// of the curried chain.
assert(targetIndex == params.size()
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use ASSERT in new code

auto extInfo = fnTy->getExtInfo();
SmallVector<LifetimeDependenceInfo, 2> substDependenceInfos;
bool didRemoveLifetimeDependencies
= filterEscapableLifetimeDependencies(GenericSignature(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Type::transform() is a very hot code path. Can you think of a way to skip all of this new work if the function type does not have lifetime dependencies?

Copy link
Contributor

Choose a reason for hiding this comment

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

I misunderstood your code; if there are no lifetime dependencies in the function type it does appear everything is skipped.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

filterEscapableLifetimeDependencies doesn't do anything if the input list is empty, since it just runs in a for loop. I can hoist that check out at each use point if you think it'll make a difference.

@@ -916,3 +916,11 @@ bool TypeBase::isEscapable() {
auto canType = preprocessTypeForInvertibleQuery(this);
return conformsToInvertible(canType, InvertibleProtocolKind::Escapable);
}

bool TypeBase::isEscapable(GenericSignature sig) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this new utility method really necessary? If you know type is an interface type, this will work whether sig is null or not:

GenericEnvironment::mapTypeIntoContext(sig.getGenericEnvironment(), type)->isEscapable()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To me, it seems like having only the version that takes a GenericSignature would be more robust, since that would lead to call sites passing a GenericSignature proactively. As is it's easy to reach for isEscapable and write code that crashes as soon as it's used in a generic context.

@jckarter
Copy link
Contributor Author

jckarter commented Apr 1, 2025

@swift-ci Please test macOS

When a generic function has potentially Escapable outputs, those outputs
declare lifetime dependencies, which have no effect when substitution
leads to those types becoming `Escapable` in a concrete context.
This means that type substitution should canonically eliminate lifetime
dependencies targeting Escapable parameters or returns, and that
type checking should allow a function value with potentially-Escapable
lifetime dependencies to bind to a function type without those dependencies
when the target of the dependencies is Escapable.

Fixes rdar://147533059.
@jckarter jckarter force-pushed the substitute-away-escapable-lifetime-deps branch from 54f195f to 6b605f4 Compare April 2, 2025 15:54
@jckarter
Copy link
Contributor Author

jckarter commented Apr 2, 2025

@swift-ci Please test

@jckarter jckarter merged commit 562d7dc into swiftlang:main Apr 2, 2025
5 checks passed
j-hui added a commit to j-hui/swift that referenced this pull request Jun 6, 2025
Comments in PR swiftlang#80438, where this code comes from, already suggest using
ASSERT() instead of assert().
j-hui added a commit to j-hui/swift that referenced this pull request Jun 12, 2025
Comments in PR swiftlang#80438, where this code comes from, already suggest using
ASSERT() instead of assert().
al45tair pushed a commit to al45tair/swift that referenced this pull request Jun 24, 2025
Comments in PR swiftlang#80438, where this code comes from, already suggest using
ASSERT() instead of assert().
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