Skip to content

[SE-0413] Adopt typed throws in withoutActuallyEscaping(_:do:) #70913

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
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
18 changes: 17 additions & 1 deletion lib/AST/ASTScopeLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,21 @@ getCatchNode(const ASTScopeImpl *scope) {
return { CatchNode(), nullptr };
}

/// Check whether the given location precedes the start of the catch location
/// despite being technically within the catch node's source range.
static bool locationIsPriorToStartOfCatchScope(SourceLoc loc, CatchNode node) {
auto closure = node.dyn_cast<ClosureExpr *>();
if (!closure)
return false;

SourceManager &sourceMgr = closure->getASTContext().SourceMgr;
SourceLoc inLoc = closure->getInLoc();
if (inLoc.isValid())
return sourceMgr.isBefore(loc, inLoc);

return sourceMgr.isAtOrBefore(loc, closure->getStartLoc());
}

CatchNode ASTScopeImpl::lookupCatchNode(ModuleDecl *module, SourceLoc loc) {
auto sourceFile = module->getSourceFileContainingLocation(loc);
if (!sourceFile)
Expand All @@ -721,7 +736,8 @@ CatchNode ASTScopeImpl::lookupCatchNode(ModuleDecl *module, SourceLoc loc) {
// If we are at a catch node and in the body of the region from which that
// node catches thrown errors, we have our result.
auto caught = getCatchNode(scope);
if (caught.first && caught.second == innerBodyScope) {
if (caught.first && caught.second == innerBodyScope &&
!locationIsPriorToStartOfCatchScope(loc, caught.first)) {
return caught.first;
}

Expand Down
7 changes: 5 additions & 2 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3108,12 +3108,15 @@ static DeclReferenceType getTypeOfReferenceWithSpecialTypeCheckingSemantics(
auto result = CS.createTypeVariable(
CS.getConstraintLocator(locator, ConstraintLocator::FunctionResult),
TVO_CanBindToNoEscape);
auto thrownError = CS.createTypeVariable(
CS.getConstraintLocator(locator, ConstraintLocator::ThrownErrorType),
0);
FunctionType::Param arg(escapeClosure);
auto bodyClosure = FunctionType::get(arg, result,
FunctionType::ExtInfoBuilder()
.withNoEscape(true)
.withAsync(true)
.withThrows(true, /*FIXME:*/Type())
.withThrows(true, thrownError)
.build());
FunctionType::Param args[] = {
FunctionType::Param(noescapeClosure),
Expand All @@ -3124,7 +3127,7 @@ static DeclReferenceType getTypeOfReferenceWithSpecialTypeCheckingSemantics(
FunctionType::ExtInfoBuilder()
.withNoEscape(false)
.withAsync(true)
.withThrows(true, /*FIXME:*/Type())
.withThrows(true, thrownError)
.build());
return {refType, refType, refType, refType, Type()};
}
Expand Down
20 changes: 18 additions & 2 deletions stdlib/public/core/Builtin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1022,12 +1022,28 @@ public func type<T, Metatype>(of value: T) -> Metatype {
/// - body: A closure that is executed immediately with an escapable copy of
/// `closure` as its argument.
/// - Returns: The return value, if any, of the `body` closure.
@_alwaysEmitIntoClient
@_transparent
@_semantics("typechecker.withoutActuallyEscaping(_:do:)")
public func withoutActuallyEscaping<ClosureType, ResultType>(
public func withoutActuallyEscaping<ClosureType, ResultType, Failure>(
_ closure: ClosureType,
do body: (_ escapingClosure: ClosureType) throws(Failure) -> ResultType
) throws(Failure) -> ResultType {
// This implementation is never used, since calls to
// `Swift.withoutActuallyEscaping(_:do:)` are resolved as a special case by
// the type checker.
Builtin.staticReport(_trueAfterDiagnostics(), true._value,
("internal consistency error: 'withoutActuallyEscaping(_:do:)' operation failed to resolve"
as StaticString).utf8Start._rawValue)
Builtin.unreachable()
}

@_silgen_name("$ss23withoutActuallyEscaping_2doq_x_q_xKXEtKr0_lF")
@usableFromInline
func __abi_withoutActuallyEscaping<ClosureType, ResultType>(
_ closure: ClosureType,
do body: (_ escapingClosure: ClosureType) throws -> ResultType
) rethrows -> ResultType {
) throws -> ResultType {
// This implementation is never used, since calls to
// `Swift.withoutActuallyEscaping(_:do:)` are resolved as a special case by
// the type checker.
Expand Down
25 changes: 22 additions & 3 deletions test/Constraints/without_actually_escaping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,8 @@ func rethrowThroughWAE(_ zz: (Int, Int, Int) throws -> Int, _ value: Int) throws
}
}

// There should be two errors - here one for async -> sync, and another throws -> non-throws
let _: ((Int) -> Int, (@escaping (Int) -> Int) -> ()) -> () = withoutActuallyEscaping(_:do:)
// expected-error@-1 {{invalid conversion from throwing function of type '((Int) -> Int, (@escaping (Int) -> Int) async throws -> ()) async throws -> ()' to non-throwing function type '((Int) -> Int, (@escaping (Int) -> Int) -> ()) -> ()'}}
// expected-error@-2 {{invalid conversion from 'async' function of type '((Int) -> Int, (@escaping (Int) -> Int) async throws -> ()) async throws -> ()' to synchronous function type '((Int) -> Int, (@escaping (Int) -> Int) -> ()) -> ()'}}
// expected-error@-1 {{invalid conversion from 'async' function of type '((Int) -> Int, (@escaping (Int) -> Int) async -> ()) async -> ()' to synchronous function type '((Int) -> Int, (@escaping (Int) -> Int) -> ()) -> ()'}}


// Failing to propagate @noescape into non-single-expression
Expand Down Expand Up @@ -91,3 +89,24 @@ class Box<T> {
}
}
}

enum HomeworkError: Error {
case forgot
case dogAteIt
}

enum MyError: Error {
case fail
}

func letEscapeThrowTyped(f: () throws(HomeworkError) -> () -> ()) throws(HomeworkError) -> () -> () {
// Note: thrown error type inference for closures will fix this error below.
return try withoutActuallyEscaping(f) { return try $0() }
// expected-error@-1{{thrown expression type 'any Error' cannot be converted to error type 'HomeworkError'}}
}

func letEscapeThrowTypedBad(f: () throws(HomeworkError) -> () -> ()) throws(MyError) -> () -> () {
// Note: thrown error type inference for closures will change this error below.
return try withoutActuallyEscaping(f) { return try $0() }
// expected-error@-1{{thrown expression type 'any Error' cannot be converted to error type 'MyError'}}
}
26 changes: 24 additions & 2 deletions test/SILGen/without_actually_escaping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,31 @@ func letEscapeThrow(f: () throws -> () -> ()) throws -> () -> () {
return try withoutActuallyEscaping(f) { return try $0() }
}

// thunk for @callee_guaranteed () -> (@owned @escaping @callee_guaranteed () -> (), @error @owned Error)
enum HomeworkError: Error {
case forgot
case dogAteIt
}

// CHECK-LABEL: sil hidden [ossa] @$s25without_actually_escaping19letEscapeThrowTyped1fyycyycyAA13HomeworkErrorOYKXE_tKF : $@convention(thin) (@guaranteed @noescape @callee_guaranteed () -> (@owned @callee_guaranteed () -> (), @error HomeworkError)) -> (@owned @callee_guaranteed () -> (), @error any Error)
// CHECK: bb0([[ARG:%.*]] : @guaranteed $@noescape @callee_guaranteed () -> (@owned @callee_guaranteed () -> (), @error HomeworkError)):
// CHECK: function_ref @$sIeg_25without_actually_escaping13HomeworkErrorOIgozo_Ieg_ACIegozo_TR : $@convention(thin) (@guaranteed @noescape @callee_guaranteed () -> (@owned @callee_guaranteed () -> (), @error HomeworkError)) -> (@owned @callee_guaranteed () -> (), @error HomeworkError)
// CHECK: bb2([[ERR:%.*]] : @owned $any Error):
// CHECK: end_borrow [[BORROW]]
// CHECK: destroy_value [[MD]]
// CHECK: throw [[ERR]] : $any Error
// CHECK: }

func letEscapeThrowTyped(f: () throws(HomeworkError) -> () -> ()) throws -> () -> () {
return try withoutActuallyEscaping(f) { return try $0() }
}

// The thunk must be [without_actually_escaping].
// CHECK-LABEL: sil shared [transparent] [serialized] [reabstraction_thunk] [without_actually_escaping] [ossa] @$sIeg_s5Error_pIgozo_Ieg_sAA_pIegozo_TR : $@convention(thin) (@guaranteed @noescape @callee_guaranteed () -> (@owned @callee_guaranteed () -> (), @error any Error)) -> (@owned @callee_guaranteed () -> (), @error any Error) {
// CHECK-LABEL: sil shared [transparent] [serialized] [reabstraction_thunk] [without_actually_escaping] [ossa] @$sIeg_25without_actually_escaping13HomeworkErrorOIgozo_Ieg_ACIegozo_TR : $@convention(thin) (@guaranteed @noescape @callee_guaranteed () -> (@owned @callee_guaranteed () -> (), @error HomeworkError)) -> (@owned @callee_guaranteed () -> (), @error HomeworkError)

func letEscapeThrowTyped2(f: () throws(HomeworkError) -> () -> ()) throws(HomeworkError) -> () -> () {
return try withoutActuallyEscaping(f) { (g) throws(HomeworkError) in return try g() }
}


// We used to crash on this example because we would use the wrong substitution
// map.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ Func Collection.map(_:) is now without @rethrows
Func Sequence.map(_:) has generic signature change from <Self, T where Self : Swift.Sequence> to <Self, T, E where Self : Swift.Sequence, E : Swift.Error>
Func Sequence.map(_:) is now without @rethrows
Constructor Result.init(catching:) has generic signature change from <Success, Failure where Failure == any Swift.Error> to <Success, Failure where Failure : Swift.Error>
Func withoutActuallyEscaping(_:do:) has generic signature change from <ClosureType, ResultType> to <ClosureType, ResultType, Failure where Failure : Swift.Error>
Func withoutActuallyEscaping(_:do:) is now without @rethrows

Protocol SIMDScalar has generic signature change from <Self == Self.SIMD16Storage.Scalar, Self.SIMD16Storage : Swift.SIMDStorage, Self.SIMD2Storage : Swift.SIMDStorage, Self.SIMD32Storage : Swift.SIMDStorage, Self.SIMD4Storage : Swift.SIMDStorage, Self.SIMD64Storage : Swift.SIMDStorage, Self.SIMD8Storage : Swift.SIMDStorage, Self.SIMDMaskScalar : Swift.FixedWidthInteger, Self.SIMDMaskScalar : Swift.SIMDScalar, Self.SIMDMaskScalar : Swift.SignedInteger, Self.SIMD16Storage.Scalar == Self.SIMD2Storage.Scalar, Self.SIMD2Storage.Scalar == Self.SIMD32Storage.Scalar, Self.SIMD32Storage.Scalar == Self.SIMD4Storage.Scalar, Self.SIMD4Storage.Scalar == Self.SIMD64Storage.Scalar, Self.SIMD64Storage.Scalar == Self.SIMD8Storage.Scalar> to <Self == Self.SIMD16Storage.Scalar, Self.SIMD16Storage : Swift.SIMDStorage, Self.SIMD2Storage : Swift.SIMDStorage, Self.SIMD32Storage : Swift.SIMDStorage, Self.SIMD4Storage : Swift.SIMDStorage, Self.SIMD64Storage : Swift.SIMDStorage, Self.SIMD8Storage : Swift.SIMDStorage, Self.SIMDMaskScalar : Swift.FixedWidthInteger, Self.SIMDMaskScalar : Swift.SIMDScalar, Self.SIMDMaskScalar : Swift.SignedInteger, Self.SIMDMaskScalar == Self.SIMDMaskScalar.SIMDMaskScalar, Self.SIMD16Storage.Scalar == Self.SIMD2Storage.Scalar, Self.SIMD2Storage.Scalar == Self.SIMD32Storage.Scalar, Self.SIMD32Storage.Scalar == Self.SIMD4Storage.Scalar, Self.SIMD4Storage.Scalar == Self.SIMD64Storage.Scalar, Self.SIMD64Storage.Scalar == Self.SIMD8Storage.Scalar>
4 changes: 4 additions & 0 deletions test/api-digester/stability-stdlib-abi-without-asserts.test
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ Constructor Result.init(__abi_catching:) is a new API without @available attribu
Constructor Result.init(catching:) has been removed
Func Result.get() has been renamed to Func __abi_get()
Func Result.get() has mangled name changing from 'Swift.Result.get() throws -> A' to 'Swift.Result.__abi_get() throws -> A'
Func withoutActuallyEscaping(_:do:) has been renamed to Func __abi_withoutActuallyEscaping(_:do:)
Func withoutActuallyEscaping(_:do:) has mangled name changing from 'Swift.withoutActuallyEscaping<A, B>(_: A, do: (A) throws -> B) throws -> B' to 'Swift.__abi_withoutActuallyEscaping<A, B>(_: A, do: (A) throws -> B) throws -> B'
Func withoutActuallyEscaping(_:do:) is now without @rethrows


// These haven't actually been removed; they are simply marked unavailable.
// This seems to be a false positive in the ABI checker. This is not an ABI
Expand Down