Skip to content

[Typed throws] Support overrides that are contravariant in the thrown error #69839

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 12 commits into from
Nov 16, 2023
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
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3362,6 +3362,9 @@ ERROR(override_class_declaration_in_extension,none,
ERROR(override_with_more_effects,none,
"cannot override non-%1 %0 with %1 %0",
(DescriptiveDeclKind, StringRef))
ERROR(override_typed_throws,none,
"%0 that throws %1 cannot override one that throws %2",
(DescriptiveDeclKind, Type, Type))
ERROR(override_throws_objc,none,
"overriding a throwing @objc %select{method|initializer}0 with "
"a non-throwing %select{method|initializer}0 is not supported", (bool))
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/ExtInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ class ASTExtInfoBuilder {
return bits == other.bits &&
(useClangTypes ? (clangTypeInfo == other.clangTypeInfo) : true) &&
globalActor.getPointer() == other.globalActor.getPointer() &&
thrownError.getPointer() == thrownError.getPointer();
thrownError.getPointer() == other.thrownError.getPointer();
}

constexpr std::tuple<unsigned, const void *, const void *, const void *>
Expand Down
40 changes: 26 additions & 14 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3421,7 +3421,8 @@ static bool matchesFunctionType(CanAnyFunctionType fn1, CanAnyFunctionType fn2,
if (matchMode.contains(TypeMatchFlags::AllowOverride)) {
// Removing 'throwing' is ABI-compatible for synchronous functions, but
// not for async ones.
if (ext2.isThrowing() &&
if (ext2.isThrowing() && !ext1.isThrowing() &&
ext2.getThrownError().isNull() &&
!(ext2.isAsync() &&
matchMode.contains(TypeMatchFlags::AllowABICompatible))) {
ext1 = ext1.withThrows(true, ext2.getThrownError());
Expand Down Expand Up @@ -5065,6 +5066,28 @@ case TypeKind::Id:
isUnchanged = false;
}

llvm::Optional<ASTExtInfo> extInfo;
if (function->hasExtInfo()) {
extInfo = function->getExtInfo()
.withGlobalActor(globalActorType)
.withThrows(function->isThrowing(), thrownError);

// If there was a generic thrown error and it substituted with
// 'any Error' or 'Never', map to 'throws' or non-throwing rather than
// maintaining the sugar.
if (auto origThrownError = function->getThrownError()) {
if (origThrownError->isTypeParameter() ||
origThrownError->isTypeVariableOrMember()) {
// 'any Error'
if (thrownError->isEqual(
thrownError->getASTContext().getErrorExistentialType()))
extInfo = extInfo->withThrows(true, Type());
else if (thrownError->isNever())
extInfo = extInfo->withThrows(false, Type());
}
}
}

if (auto genericFnType = dyn_cast<GenericFunctionType>(base)) {
#ifndef NDEBUG
// Check that generic parameters won't be transformed.
Expand All @@ -5080,24 +5103,13 @@ case TypeKind::Id:
if (isUnchanged) return *this;

auto genericSig = genericFnType->getGenericSignature();
if (!function->hasExtInfo())
return GenericFunctionType::get(genericSig, substParams, resultTy);
return GenericFunctionType::get(
genericSig, substParams, resultTy,
function->getExtInfo()
.withGlobalActor(globalActorType)
.withThrows(function->isThrowing(), thrownError));
genericSig, substParams, resultTy, extInfo);
}

if (isUnchanged) return *this;

if (!function->hasExtInfo())
return FunctionType::get(substParams, resultTy);
return FunctionType::get(
substParams, resultTy,
function->getExtInfo()
.withGlobalActor(globalActorType)
.withThrows(function->isThrowing(), thrownError));
return FunctionType::get(substParams, resultTy, extInfo);
}

case TypeKind::ArraySlice: {
Expand Down
19 changes: 14 additions & 5 deletions lib/SILOptimizer/LoopTransforms/ForEachLoopUnroll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,10 +483,11 @@ static void unrollForEach(ArrayInfo &arrayInfo, TryApplyInst *forEachCall,
// targets must be taking a phi argument.
SILBasicBlock *normalBB = forEachCall->getNormalBB();
SILBasicBlock *errorBB = forEachCall->getErrorBB();
assert(errorBB->getSILPhiArguments().size() == 1 &&
normalBB->getSILPhiArguments().size() == 1);
assert(normalBB->getSILPhiArguments().size() == 1);
SILPhiArgument *normalArgument = normalBB->getSILPhiArguments()[0];
SILPhiArgument *errorArgument = errorBB->getSILPhiArguments()[0];
SILPhiArgument *errorArgument = nullptr;
if (errorBB->getSILPhiArguments().size() == 1)
errorArgument = errorBB->getSILPhiArguments()[0];

// A generator for creating a basic block for use as the target of the
// "normal" branch of a try_apply.
Expand All @@ -503,8 +504,12 @@ static void unrollForEach(ArrayInfo &arrayInfo, TryApplyInst *forEachCall,
auto errorTargetGenerator = [&](SILBasicBlock *insertionBlock,
SILValue borrowedElem, SILValue storeBorrow) {
SILBasicBlock *newErrorBB = fun->createBasicBlockBefore(insertionBlock);
SILValue argument = newErrorBB->createPhiArgument(
SILValue argument;
if (errorArgument) {
argument = newErrorBB->createPhiArgument(
errorArgument->getType(), errorArgument->getOwnershipKind());
}

// Make the errorBB jump to the error target of the original forEach.
SILBuilderWithScope builder(newErrorBB, forEachCall);
if (storeBorrow) {
Expand All @@ -513,7 +518,11 @@ static void unrollForEach(ArrayInfo &arrayInfo, TryApplyInst *forEachCall,
if (borrowedElem) {
builder.createEndBorrow(forEachLoc, borrowedElem);
}
builder.createBranch(forEachLoc, errorBB, argument);

if (argument)
builder.createBranch(forEachLoc, errorBB, argument);
else
builder.createBranch(forEachLoc, errorBB);
return newErrorBB;
};

Expand Down
3 changes: 2 additions & 1 deletion lib/SILOptimizer/Mandatory/TransferNonSendable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,8 @@ class PartitionOpTranslator {

if (auto tryApplyInst = dyn_cast<TryApplyInst>(inst)) {
foundResults.emplace_back(tryApplyInst->getNormalBB()->getArgument(0));
foundResults.emplace_back(tryApplyInst->getErrorBB()->getArgument(0));
if (tryApplyInst->getErrorBB()->getNumArguments() > 0)
foundResults.emplace_back(tryApplyInst->getErrorBB()->getArgument(0));
return;
}

Expand Down
16 changes: 12 additions & 4 deletions lib/SILOptimizer/Transforms/SpeculativeDevirtualizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,19 @@ static FullApplySite speculateMonomorphicTarget(FullApplySite AI,
// Split critical edges resulting from VirtAI.
if (auto *TAI = dyn_cast<TryApplyInst>(VirtAI)) {
auto *ErrorBB = TAI->getFunction()->createBasicBlock();
ErrorBB->createPhiArgument(TAI->getErrorBB()->getArgument(0)->getType(),
OwnershipKind::Owned);
SILArgument *ErrorArg = nullptr;
if (TAI->getErrorBB()->getNumArguments() == 1) {
ErrorArg = TAI->getErrorBB()->getArgument(0);
ErrorBB->createPhiArgument(ErrorArg->getType(), OwnershipKind::Owned);
}
Builder.setInsertionPoint(ErrorBB);
Builder.createBranch(TAI->getLoc(), TAI->getErrorBB(),
{ErrorBB->getArgument(0)});

if (ErrorArg) {
Builder.createBranch(TAI->getLoc(), TAI->getErrorBB(),
{ErrorBB->getArgument(0)});
} else {
Builder.createBranch(TAI->getLoc(), TAI->getErrorBB());
}

auto *NormalBB = TAI->getFunction()->createBasicBlock();
NormalBB->createPhiArgument(TAI->getNormalBB()->getArgument(0)->getType(),
Expand Down
47 changes: 43 additions & 4 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "TypeCheckAvailability.h"
#include "TypeCheckConcurrency.h"
#include "TypeCheckDecl.h"
#include "TypeCheckEffects.h"
#include "TypeCheckObjC.h"
#include "TypeChecker.h"
#include "swift/AST/ASTVisitor.h"
Expand Down Expand Up @@ -2036,16 +2037,54 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
diags.diagnose(base, diag::overridden_here);
}
}
// If the overriding declaration is 'throws' but the base is not,
// complain. Do the same for 'async'

// Check effects.
if (auto overrideFn = dyn_cast<AbstractFunctionDecl>(override)) {
if (overrideFn->hasThrows() &&
!cast<AbstractFunctionDecl>(base)->hasThrows()) {
// Determine the thrown errors in the base and override declarations.
auto baseFn = cast<AbstractFunctionDecl>(base);
Type overrideThrownError =
overrideFn->getEffectiveThrownErrorType().value_or(ctx.getNeverType());
Type baseThrownError =
baseFn->getEffectiveThrownErrorType().value_or(ctx.getNeverType());

if (baseThrownError && baseThrownError->hasTypeParameter()) {
auto subs = SubstitutionMap::getOverrideSubstitutions(base, override);
baseThrownError = baseThrownError.subst(subs);
baseThrownError = overrideFn->mapTypeIntoContext(baseThrownError);
}

if (overrideThrownError)
overrideThrownError = overrideFn->mapTypeIntoContext(overrideThrownError);

// Check for a subtyping relationship.
switch (compareThrownErrorsForSubtyping(
overrideThrownError, baseThrownError, overrideFn)) {
case ThrownErrorSubtyping::DropsThrows:
diags.diagnose(override, diag::override_with_more_effects,
override->getDescriptiveKind(), "throwing");
diags.diagnose(base, diag::overridden_here);
break;

case ThrownErrorSubtyping::Mismatch:
diags.diagnose(override, diag::override_typed_throws,
override->getDescriptiveKind(), overrideThrownError,
baseThrownError);
diags.diagnose(base, diag::overridden_here);
break;

case ThrownErrorSubtyping::ExactMatch:
case ThrownErrorSubtyping::Subtype:
// Proper subtyping.
break;

case ThrownErrorSubtyping::Dependent:
// Only in already ill-formed code.
assert(ctx.Diags.hadAnyError());
break;
}

// If the override is 'async' but the base declaration is not, we have a
// problem.
if (overrideFn->hasAsync() &&
!cast<AbstractFunctionDecl>(base)->hasAsync()) {
diags.diagnose(override, diag::override_with_more_effects,
Expand Down
7 changes: 7 additions & 0 deletions lib/Sema/TypeCheckEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3549,6 +3549,13 @@ ThrownErrorSubtyping
swift::compareThrownErrorsForSubtyping(
Type subThrownError, Type superThrownError, DeclContext *dc
) {
// Deal with NULL errors. This should only occur when there is no standard
// library.
if (!subThrownError || !superThrownError) {
assert(!dc->getASTContext().getStdlibModule() && "NULL thrown error type");
return ThrownErrorSubtyping::ExactMatch;
}

// Easy case: exact match.
if (superThrownError->isEqual(subThrownError))
return ThrownErrorSubtyping::ExactMatch;
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ endif()
# STAGING: Temporarily avoids having to write #fileID in Swift.swiftinterface.
list(APPEND swift_stdlib_compile_flags "-Xfrontend" "-enable-experimental-concise-pound-file")

list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "Macros")
list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "TypedThrows")
list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "FreestandingMacros")
list(APPEND swift_stdlib_compile_flags "-enable-experimental-feature" "Extern")

Expand Down
18 changes: 15 additions & 3 deletions stdlib/public/core/Collection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1190,9 +1190,10 @@ extension Collection {
/// - Returns: An array containing the transformed elements of this
/// sequence.
@inlinable
public func map<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
@_alwaysEmitIntoClient
public func map<T, E>(
_ transform: (Element) throws(E) -> T
) throws(E) -> [T] {
// TODO: swift-3-indexing-model - review the following
let n = self.count
if n == 0 {
Expand All @@ -1213,6 +1214,17 @@ extension Collection {
return Array(result)
}

// ABI-only entrypoint for the rethrows version of map, which has been
// superseded by the typed-throws version. Expressed as "throws", which is
// ABI-compatible with "rethrows".
@usableFromInline
@_silgen_name("$sSlsE3mapySayqd__Gqd__7ElementQzKXEKlF")
func __rethrows_map<T>(
_ transform: (Element) throws -> T
) throws -> [T] {
try map(transform)
}

/// Returns a subsequence containing all but the given number of initial
/// elements.
///
Expand Down
Loading