Skip to content

Commit 467f684

Browse files
authored
Merge pull request #70913 from DougGregor/typed-throws-without-actually-escaping
[SE-0413] Adopt typed throws in `withoutActuallyEscaping(_:do:)`
2 parents 0e6a913 + 8912d4a commit 467f684

File tree

7 files changed

+92
-10
lines changed

7 files changed

+92
-10
lines changed

lib/AST/ASTScopeLookup.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,21 @@ getCatchNode(const ASTScopeImpl *scope) {
704704
return { CatchNode(), nullptr };
705705
}
706706

707+
/// Check whether the given location precedes the start of the catch location
708+
/// despite being technically within the catch node's source range.
709+
static bool locationIsPriorToStartOfCatchScope(SourceLoc loc, CatchNode node) {
710+
auto closure = node.dyn_cast<ClosureExpr *>();
711+
if (!closure)
712+
return false;
713+
714+
SourceManager &sourceMgr = closure->getASTContext().SourceMgr;
715+
SourceLoc inLoc = closure->getInLoc();
716+
if (inLoc.isValid())
717+
return sourceMgr.isBefore(loc, inLoc);
718+
719+
return sourceMgr.isAtOrBefore(loc, closure->getStartLoc());
720+
}
721+
707722
CatchNode ASTScopeImpl::lookupCatchNode(ModuleDecl *module, SourceLoc loc) {
708723
auto sourceFile = module->getSourceFileContainingLocation(loc);
709724
if (!sourceFile)
@@ -721,7 +736,8 @@ CatchNode ASTScopeImpl::lookupCatchNode(ModuleDecl *module, SourceLoc loc) {
721736
// If we are at a catch node and in the body of the region from which that
722737
// node catches thrown errors, we have our result.
723738
auto caught = getCatchNode(scope);
724-
if (caught.first && caught.second == innerBodyScope) {
739+
if (caught.first && caught.second == innerBodyScope &&
740+
!locationIsPriorToStartOfCatchScope(loc, caught.first)) {
725741
return caught.first;
726742
}
727743

lib/Sema/ConstraintSystem.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3108,12 +3108,15 @@ static DeclReferenceType getTypeOfReferenceWithSpecialTypeCheckingSemantics(
31083108
auto result = CS.createTypeVariable(
31093109
CS.getConstraintLocator(locator, ConstraintLocator::FunctionResult),
31103110
TVO_CanBindToNoEscape);
3111+
auto thrownError = CS.createTypeVariable(
3112+
CS.getConstraintLocator(locator, ConstraintLocator::ThrownErrorType),
3113+
0);
31113114
FunctionType::Param arg(escapeClosure);
31123115
auto bodyClosure = FunctionType::get(arg, result,
31133116
FunctionType::ExtInfoBuilder()
31143117
.withNoEscape(true)
31153118
.withAsync(true)
3116-
.withThrows(true, /*FIXME:*/Type())
3119+
.withThrows(true, thrownError)
31173120
.build());
31183121
FunctionType::Param args[] = {
31193122
FunctionType::Param(noescapeClosure),
@@ -3124,7 +3127,7 @@ static DeclReferenceType getTypeOfReferenceWithSpecialTypeCheckingSemantics(
31243127
FunctionType::ExtInfoBuilder()
31253128
.withNoEscape(false)
31263129
.withAsync(true)
3127-
.withThrows(true, /*FIXME:*/Type())
3130+
.withThrows(true, thrownError)
31283131
.build());
31293132
return {refType, refType, refType, refType, Type()};
31303133
}

stdlib/public/core/Builtin.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,12 +1022,28 @@ public func type<T, Metatype>(of value: T) -> Metatype {
10221022
/// - body: A closure that is executed immediately with an escapable copy of
10231023
/// `closure` as its argument.
10241024
/// - Returns: The return value, if any, of the `body` closure.
1025+
@_alwaysEmitIntoClient
10251026
@_transparent
10261027
@_semantics("typechecker.withoutActuallyEscaping(_:do:)")
1027-
public func withoutActuallyEscaping<ClosureType, ResultType>(
1028+
public func withoutActuallyEscaping<ClosureType, ResultType, Failure>(
1029+
_ closure: ClosureType,
1030+
do body: (_ escapingClosure: ClosureType) throws(Failure) -> ResultType
1031+
) throws(Failure) -> ResultType {
1032+
// This implementation is never used, since calls to
1033+
// `Swift.withoutActuallyEscaping(_:do:)` are resolved as a special case by
1034+
// the type checker.
1035+
Builtin.staticReport(_trueAfterDiagnostics(), true._value,
1036+
("internal consistency error: 'withoutActuallyEscaping(_:do:)' operation failed to resolve"
1037+
as StaticString).utf8Start._rawValue)
1038+
Builtin.unreachable()
1039+
}
1040+
1041+
@_silgen_name("$ss23withoutActuallyEscaping_2doq_x_q_xKXEtKr0_lF")
1042+
@usableFromInline
1043+
func __abi_withoutActuallyEscaping<ClosureType, ResultType>(
10281044
_ closure: ClosureType,
10291045
do body: (_ escapingClosure: ClosureType) throws -> ResultType
1030-
) rethrows -> ResultType {
1046+
) throws -> ResultType {
10311047
// This implementation is never used, since calls to
10321048
// `Swift.withoutActuallyEscaping(_:do:)` are resolved as a special case by
10331049
// the type checker.

test/Constraints/without_actually_escaping.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,8 @@ func rethrowThroughWAE(_ zz: (Int, Int, Int) throws -> Int, _ value: Int) throws
6060
}
6161
}
6262

63-
// There should be two errors - here one for async -> sync, and another throws -> non-throws
6463
let _: ((Int) -> Int, (@escaping (Int) -> Int) -> ()) -> () = withoutActuallyEscaping(_:do:)
65-
// 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) -> ()) -> ()'}}
66-
// 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) -> ()) -> ()'}}
64+
// 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) -> ()) -> ()'}}
6765

6866

6967
// Failing to propagate @noescape into non-single-expression
@@ -91,3 +89,24 @@ class Box<T> {
9189
}
9290
}
9391
}
92+
93+
enum HomeworkError: Error {
94+
case forgot
95+
case dogAteIt
96+
}
97+
98+
enum MyError: Error {
99+
case fail
100+
}
101+
102+
func letEscapeThrowTyped(f: () throws(HomeworkError) -> () -> ()) throws(HomeworkError) -> () -> () {
103+
// Note: thrown error type inference for closures will fix this error below.
104+
return try withoutActuallyEscaping(f) { return try $0() }
105+
// expected-error@-1{{thrown expression type 'any Error' cannot be converted to error type 'HomeworkError'}}
106+
}
107+
108+
func letEscapeThrowTypedBad(f: () throws(HomeworkError) -> () -> ()) throws(MyError) -> () -> () {
109+
// Note: thrown error type inference for closures will change this error below.
110+
return try withoutActuallyEscaping(f) { return try $0() }
111+
// expected-error@-1{{thrown expression type 'any Error' cannot be converted to error type 'MyError'}}
112+
}

test/SILGen/without_actually_escaping.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,31 @@ func letEscapeThrow(f: () throws -> () -> ()) throws -> () -> () {
5151
return try withoutActuallyEscaping(f) { return try $0() }
5252
}
5353

54-
// thunk for @callee_guaranteed () -> (@owned @escaping @callee_guaranteed () -> (), @error @owned Error)
54+
enum HomeworkError: Error {
55+
case forgot
56+
case dogAteIt
57+
}
58+
59+
// 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)
60+
// CHECK: bb0([[ARG:%.*]] : @guaranteed $@noescape @callee_guaranteed () -> (@owned @callee_guaranteed () -> (), @error HomeworkError)):
61+
// 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)
62+
// CHECK: bb2([[ERR:%.*]] : @owned $any Error):
63+
// CHECK: end_borrow [[BORROW]]
64+
// CHECK: destroy_value [[MD]]
65+
// CHECK: throw [[ERR]] : $any Error
66+
// CHECK: }
67+
68+
func letEscapeThrowTyped(f: () throws(HomeworkError) -> () -> ()) throws -> () -> () {
69+
return try withoutActuallyEscaping(f) { return try $0() }
70+
}
71+
5572
// The thunk must be [without_actually_escaping].
56-
// 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) {
73+
// 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)
74+
75+
func letEscapeThrowTyped2(f: () throws(HomeworkError) -> () -> ()) throws(HomeworkError) -> () -> () {
76+
return try withoutActuallyEscaping(f) { (g) throws(HomeworkError) in return try g() }
77+
}
78+
5779

5880
// We used to crash on this example because we would use the wrong substitution
5981
// map.

test/api-digester/Outputs/stability-stdlib-source-x86_64.swift.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,7 @@ Func Collection.map(_:) is now without @rethrows
4141
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>
4242
Func Sequence.map(_:) is now without @rethrows
4343
Constructor Result.init(catching:) has generic signature change from <Success, Failure where Failure == any Swift.Error> to <Success, Failure where Failure : Swift.Error>
44+
Func withoutActuallyEscaping(_:do:) has generic signature change from <ClosureType, ResultType> to <ClosureType, ResultType, Failure where Failure : Swift.Error>
45+
Func withoutActuallyEscaping(_:do:) is now without @rethrows
4446

4547
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>

test/api-digester/stability-stdlib-abi-without-asserts.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ Constructor Result.init(__abi_catching:) is a new API without @available attribu
9999
Constructor Result.init(catching:) has been removed
100100
Func Result.get() has been renamed to Func __abi_get()
101101
Func Result.get() has mangled name changing from 'Swift.Result.get() throws -> A' to 'Swift.Result.__abi_get() throws -> A'
102+
Func withoutActuallyEscaping(_:do:) has been renamed to Func __abi_withoutActuallyEscaping(_:do:)
103+
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'
104+
Func withoutActuallyEscaping(_:do:) is now without @rethrows
105+
102106

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

0 commit comments

Comments
 (0)