diff --git a/include/swift/AST/KnownDecls.def b/include/swift/AST/KnownDecls.def index 8420546717b08..894350851bfac 100644 --- a/include/swift/AST/KnownDecls.def +++ b/include/swift/AST/KnownDecls.def @@ -20,13 +20,16 @@ #endif FUNC_DECL(ArrayForceCast, "_arrayForceCast") +FUNC_DECL(ArrayWitnessCast, "_arrayWitnessCast") FUNC_DECL(ArrayConditionalCast, "_arrayConditionalCast") FUNC_DECL(DictionaryUpCast, "_dictionaryUpCast") +FUNC_DECL(DictionaryWitnessCast, "_dictionaryWitnessCast") FUNC_DECL(DictionaryDownCast, "_dictionaryDownCast") FUNC_DECL(DictionaryDownCastConditional, "_dictionaryDownCastConditional") FUNC_DECL(SetUpCast, "_setUpCast") +FUNC_DECL(SetWitnessCast, "_setWitnessCast") FUNC_DECL(SetDownCast, "_setDownCast") FUNC_DECL(SetDownCastConditional, "_setDownCastConditional") diff --git a/lib/SIL/Utils/DynamicCasts.cpp b/lib/SIL/Utils/DynamicCasts.cpp index d13d875672ba6..c5a3c0f2cbb47 100644 --- a/lib/SIL/Utils/DynamicCasts.cpp +++ b/lib/SIL/Utils/DynamicCasts.cpp @@ -656,6 +656,9 @@ swift::classifyDynamicCast(SILFunction *function, // Function casts. if (auto sourceFunction = dyn_cast(source)) { if (auto targetFunction = dyn_cast(target)) { + // Note that this logic must be aligned with with tryCastToFunction() in + // stdlib/public/runtime/DynamicCast.cpp + // A function cast can succeed if the function types can be identical, // or if the target type is throwier than the original. @@ -674,10 +677,10 @@ swift::classifyDynamicCast(SILFunction *function, != sourceFunction->getRepresentation()) return DynamicCastFeasibility::WillFail; - if (AnyFunctionType::equalParams(sourceFunction.getParams(), - targetFunction.getParams()) && + if (!AnyFunctionType::equalParams(sourceFunction.getParams(), + targetFunction.getParams()) && sourceFunction.getResult() == targetFunction.getResult()) - return DynamicCastFeasibility::WillSucceed; + return DynamicCastFeasibility::WillFail; // Be conservative about function type relationships we may add in // the future. diff --git a/lib/SILGen/SILGenDynamicCast.cpp b/lib/SILGen/SILGenDynamicCast.cpp index 31be3f15ef3cf..10293f2f675bf 100644 --- a/lib/SILGen/SILGenDynamicCast.cpp +++ b/lib/SILGen/SILGenDynamicCast.cpp @@ -394,7 +394,7 @@ static RValue emitCollectionDowncastExpr(SILGenFunction &SGF, } return SGF.emitCollectionConversion(loc, fn, fromCollection, toCollection, - source, C); + source, nullptr, nullptr, C); } static ManagedValue diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 9f20526949ef0..c08d545edf0b0 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -474,6 +474,8 @@ namespace { RValue visitCollectionUpcastConversionExpr( CollectionUpcastConversionExpr *E, SGFContext C); + ClosureExpr *synthesizeConversionClosure( + CollectionUpcastConversionExpr::ConversionPair pair); RValue visitBridgeToObjCExpr(BridgeToObjCExpr *E, SGFContext C); RValue visitPackExpansionExpr(PackExpansionExpr *E, SGFContext C); RValue visitPackElementExpr(PackElementExpr *E, SGFContext C); @@ -1509,12 +1511,10 @@ RValue RValueEmitter::visitMetatypeConversionExpr(MetatypeConversionExpr *E, return RValue(SGF, E, ManagedValue::forObjectRValueWithoutOwnership(upcast)); } -RValue SILGenFunction::emitCollectionConversion(SILLocation loc, - FuncDecl *fn, - CanType fromCollection, - CanType toCollection, - ManagedValue mv, - SGFContext C) { +RValue SILGenFunction::emitCollectionConversion( + SILLocation loc, FuncDecl *fn, CanType fromCollection, CanType toCollection, + ManagedValue mv, ClosureExpr *keyConversion, ClosureExpr *valueConversion, + SGFContext C) { SmallVector replacementTypes; auto fromArgs = cast(fromCollection)->getGenericArgs(); @@ -1529,7 +1529,104 @@ RValue SILGenFunction::emitCollectionConversion(SILLocation loc, auto subMap = SubstitutionMap::get(genericSig, replacementTypes, LookUpConformanceInModule()); - return emitApplyOfLibraryIntrinsic(loc, fn, subMap, {mv}, C); + + SmallVector args = { mv }; + unsigned argIndex = 1; + for (ClosureExpr *closure : {keyConversion, valueConversion}) { + if (!closure) { + continue; + } + + CanType origParamType = fn->getParameters() + ->get(argIndex) + ->getInterfaceType() + ->getCanonicalType(); + CanType substParamType = origParamType.subst(subMap)->getCanonicalType(); + + // Ensure that the closure has the appropriate type. + AbstractionPattern origParam( + fn->getGenericSignature().getCanonicalSignature(), origParamType); + + auto paramConversion = Conversion::getSubstToOrig( + origParam, substParamType, getLoweredType(closure->getType()), + getLoweredType(origParam, substParamType)); + + auto value = emitConvertedRValue(closure, paramConversion); + args.push_back(value); + argIndex++; + } + + return emitApplyOfLibraryIntrinsic(loc, fn, subMap, args, C); +} + +static bool +needsCustomConversion(CollectionUpcastConversionExpr::ConversionPair const & pair) { + assert(pair); + + if (auto conv = dyn_cast(pair.Conversion)) { + if (isa(conv)) { + if (auto B2O = dyn_cast(conv->getSubExpr())) { + conv = B2O; + } + } + if (conv->getSubExpr() == pair.OrigValue) { + switch (conv->getKind()) { + case ExprKind::Erasure: + case ExprKind::DerivedToBase: + case ExprKind::AnyHashableErasure: + case ExprKind::MetatypeConversion: + case ExprKind::BridgeToObjC: + case ExprKind::InjectIntoOptional: + return false; + case ExprKind::FunctionConversion: + return true; + case ExprKind::CollectionUpcastConversion: { + auto upcast = cast(conv); + if (upcast->getKeyConversion() && needsCustomConversion(upcast->getKeyConversion())) { + return true; + } + return needsCustomConversion(upcast->getValueConversion()); + } + case ExprKind::Load: + case ExprKind::ABISafeConversion: + case ExprKind::DestructureTuple: + case ExprKind::CovariantFunctionConversion: + case ExprKind::CovariantReturnConversion: + case ExprKind::BridgeFromObjC: + case ExprKind::ConditionalBridgeFromObjC: + case ExprKind::ArchetypeToSuper: + case ExprKind::ClassMetatypeToObject: + case ExprKind::ExistentialMetatypeToObject: + case ExprKind::ProtocolMetatypeToObject: + case ExprKind::InOutToPointer: + case ExprKind::ArrayToPointer: + case ExprKind::StringToPointer: + case ExprKind::PointerToPointer: + case ExprKind::ForeignObjectConversion: + case ExprKind::UnevaluatedInstance: + case ExprKind::UnderlyingToOpaque: + case ExprKind::Unreachable: + case ExprKind::DifferentiableFunction: + case ExprKind::LinearFunction: + case ExprKind::DifferentiableFunctionExtractOriginal: + case ExprKind::LinearFunctionExtractOriginal: + case ExprKind::LinearToDifferentiableFunction: + case ExprKind::ActorIsolationErasure: + case ExprKind::UnsafeCast: + conv->dump(llvm::errs()); + llvm::errs() << "\n"; + assert(false && "TODO"); + default: + break; + } + } + } + if (auto OE = dyn_cast(pair.Conversion)) { + if (OE->getExistentialValue() == pair.OrigValue) { + return false; + } + } + return true; } RValue RValueEmitter:: @@ -1547,18 +1644,187 @@ visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E, // Get the intrinsic function. FuncDecl *fn = nullptr; + ClosureExpr *keyConversion = nullptr; + ClosureExpr *valueConversion = nullptr; if (fromCollection->isArray()) { - fn = SGF.SGM.getArrayForceCast(loc); + if (needsCustomConversion(E->getValueConversion())) { + fn = SGF.SGM.getArrayWitnessCast(loc); + valueConversion = synthesizeConversionClosure(E->getValueConversion()); + } else { + fn = SGF.SGM.getArrayForceCast(loc); + } } else if (fromCollection->isDictionary()) { - fn = SGF.SGM.getDictionaryUpCast(loc); + if (needsCustomConversion(E->getKeyConversion()) || + needsCustomConversion(E->getValueConversion())) { + fn = SGF.SGM.getDictionaryWitnessCast(loc); + keyConversion = synthesizeConversionClosure(E->getKeyConversion()); + valueConversion = synthesizeConversionClosure(E->getValueConversion()); + } else { + fn = SGF.SGM.getDictionaryUpCast(loc); + } } else if (fromCollection->isSet()) { - fn = SGF.SGM.getSetUpCast(loc); + if (needsCustomConversion(E->getValueConversion())) { + fn = SGF.SGM.getSetWitnessCast(loc); + valueConversion = synthesizeConversionClosure(E->getValueConversion()); + } else { + fn = SGF.SGM.getSetUpCast(loc); + } } else { llvm_unreachable("unsupported collection upcast kind"); } - return SGF.emitCollectionConversion(loc, fn, fromCollection, toCollection, - mv, C); + return SGF.emitCollectionConversion(loc, fn, fromCollection, toCollection, mv, + keyConversion, valueConversion, C); +} + +namespace { +class ConversionClosureEmitter: public Lowering::ExprVisitor { + ASTContext &Context; + OpaqueValueExpr *OrigValue; + ParamDecl *Param; +public: + ConversionClosureEmitter(ASTContext &Context, OpaqueValueExpr *origValue, ParamDecl *param) + : Context(Context), OrigValue(origValue), Param(param) + {} + + /* + case ExprKind::: + return false; + case ExprKind::FunctionConversion: + */ + + Expr *visitOpaqueValueExpr(OpaqueValueExpr *E) { + if (E == OrigValue) { + return new (Context) DeclRefExpr(Param, DeclNameLoc(), true, AccessSemantics::Ordinary, OrigValue->getType()); + } + // Other opaque values are possible, e.g. intrudced by the OpenExistentialExpr + return nullptr; + } + + Expr *visitErasureExpr(ErasureExpr *E) { + Expr* subExpr = visit(E->getSubExpr()); + if (!subExpr) return E; + return ErasureExpr::create(Context, subExpr, E->getType(), + E->getConformances(), + E->getArgumentConversions()); + } + + Expr *visitDerivedToBaseExpr(DerivedToBaseExpr *E) { + Expr* subExpr = visit(E->getSubExpr()); + if (!subExpr) return E; + return new (Context) DerivedToBaseExpr(subExpr, E->getType()); + } + + Expr *visitAnyHashableErasureExpr(AnyHashableErasureExpr *E) { + Expr* subExpr = visit(E->getSubExpr()); + if (!subExpr) return E; + return new (Context) AnyHashableErasureExpr(subExpr, E->getType(), E->getConformance()); + } + + Expr *visitMetatypeConversionExpr(MetatypeConversionExpr *E) { + Expr* subExpr = visit(E->getSubExpr()); + if (!subExpr) return E; + return new (Context) MetatypeConversionExpr(subExpr, E->getType()); + } + + Expr *visitBridgeToObjCExpr(BridgeToObjCExpr *E) { + Expr* subExpr = visit(E->getSubExpr()); + if (!subExpr) return E; + return new (Context) BridgeToObjCExpr(subExpr, E->getType()); + } + + Expr *visitInjectIntoOptionalExpr(InjectIntoOptionalExpr *E) { + Expr* subExpr = visit(E->getSubExpr()); + if (!subExpr) return E; + return new (Context) InjectIntoOptionalExpr(subExpr, E->getType()); + } + + Expr *visitFunctionConversionExpr(FunctionConversionExpr *E) { + Expr* subExpr = visit(E->getSubExpr()); + if (!subExpr) return E; + return new (Context) FunctionConversionExpr(subExpr, E->getType()); + } + + Expr *visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E) { + Expr* subExpr = visit(E->getSubExpr()); + if (!subExpr) return E; + return new (Context) CollectionUpcastConversionExpr(subExpr, E->getType(), E->getKeyConversion(), E->getValueConversion()); + } + + Expr *visitOpenExistentialExpr(OpenExistentialExpr *E) { + Expr* existential = visit(E->getExistentialValue()); + Expr* subExpr = visit(E->getSubExpr()); + if (!existential && !subExpr) return E; + return new (Context) OpenExistentialExpr( + existential ? existential : E->getExistentialValue(), + E->getOpaqueValue(), + subExpr ? subExpr : E->getSubExpr(), + subExpr->getType()); + } + + Expr *visitExpr(Expr *E) { + E->dump(llvm::errs()); + llvm::errs() << "\n"; + llvm_unreachable("unimplemented conversion expr"); + } +}; +} // namespace + +ClosureExpr *RValueEmitter::synthesizeConversionClosure( + CollectionUpcastConversionExpr::ConversionPair pair) { + ASTContext &Context = SGF.getASTContext(); + DeclContext *declContext = SGF.FunctionDC; + + DeclAttributes attributes; + SourceRange bracketRange; + SmallVector captureList; + VarDecl *capturedSelfDecl = nullptr; + SourceLoc asyncLoc; + SourceLoc throwsLoc; + TypeExpr *thrownType = nullptr; + SourceLoc arrowLoc; + TypeExpr *explicitResultType = nullptr; + SourceLoc inLoc; + + ClosureExpr *closure = new (Context) ClosureExpr( + attributes, bracketRange, capturedSelfDecl, nullptr, asyncLoc, throwsLoc, + thrownType, arrowLoc, inLoc, explicitResultType, declContext + ); + + + Identifier ident = Context.getDollarIdentifier(0); + ParamDecl* param = new (Context) ParamDecl(SourceLoc(), SourceLoc(), + Identifier(), SourceLoc(), ident, closure); + + param->setSpecifier(ParamSpecifier::Default); + param->setInterfaceType(pair.OrigValue->getType()); + param->setImplicit(); + + ParameterList *params = ParameterList::create(Context, SourceLoc(), ArrayRef(param), SourceLoc()); + closure->setParameterList(params); + + ConversionClosureEmitter emitter(Context, pair.OrigValue, param); + Expr *result = emitter.visit(pair.Conversion); + auto *RS = ReturnStmt::createImplicit(Context, result); + ASTNode bodyNode(RS); + auto *BS = BraceStmt::createImplicit(Context, ArrayRef(bodyNode)); + closure->setBody(BS); + + auto closureParam = AnyFunctionType::Param(pair.OrigValue->getType()); + auto extInfo = FunctionType::ExtInfo() + .withNoEscape() + .withSendable() + .withoutIsolation(); + auto *fnTy = FunctionType::get(ArrayRef(closureParam), pair.Conversion->getType(), extInfo); + closure->setType(fnTy); + + closure->setCaptureInfo(CaptureInfo::empty()); + + auto discriminator = Context.getNextDiscriminator(declContext); + closure->setDiscriminator(discriminator++); + Context.setMaxAssignedDiscriminator(declContext, discriminator); + + return closure; } RValue diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 3711fa02f46f7..6d9077ddb4238 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -1758,12 +1758,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction CanType existentialType, SGFContext C = SGFContext()); - RValue emitCollectionConversion(SILLocation loc, - FuncDecl *fn, - CanType fromCollection, - CanType toCollection, - ManagedValue mv, - SGFContext C); + RValue emitCollectionConversion(SILLocation loc, FuncDecl *fn, + CanType fromCollection, CanType toCollection, + ManagedValue mv, ClosureExpr *keyConversion, + ClosureExpr *valueConversion, SGFContext C); //===--------------------------------------------------------------------===// // Recursive entry points diff --git a/lib/SILGen/SILGenPoly.cpp b/lib/SILGen/SILGenPoly.cpp index 9050585a39769..f5e7f17cdf230 100644 --- a/lib/SILGen/SILGenPoly.cpp +++ b/lib/SILGen/SILGenPoly.cpp @@ -647,9 +647,10 @@ ManagedValue Transform::transform(ManagedValue v, llvm::report_fatal_error("unsupported collection upcast kind"); } - return SGF.emitCollectionConversion(Loc, fn, inputSubstType, - outputSubstType, v, ctxt) - .getScalarValue(); + return SGF + .emitCollectionConversion(Loc, fn, inputSubstType, outputSubstType, v, + nullptr, nullptr, ctxt) + .getScalarValue(); } } diff --git a/stdlib/public/core/ArrayCast.swift b/stdlib/public/core/ArrayCast.swift index 0cf88496df515..e1983b99bf1db 100644 --- a/stdlib/public/core/ArrayCast.swift +++ b/stdlib/public/core/ArrayCast.swift @@ -52,6 +52,14 @@ public func _arrayForceCast( return source.map { $0 as! TargetElement } } +@_alwaysEmitIntoClient +public func _arrayWitnessCast( + _ source: Array, + _ witness: (SourceElement) -> TargetElement +) -> Array { + return source.map(witness) +} + /// Called by the casting machinery. @_silgen_name("_swift_arrayDownCastConditionalIndirect") internal func _arrayDownCastConditionalIndirect( diff --git a/stdlib/public/core/DictionaryCasting.swift b/stdlib/public/core/DictionaryCasting.swift index 349827e90f0bc..32741a45ee939 100644 --- a/stdlib/public/core/DictionaryCasting.swift +++ b/stdlib/public/core/DictionaryCasting.swift @@ -57,6 +57,23 @@ public func _dictionaryUpCast( }! } +@_alwaysEmitIntoClient +public func _dictionaryWitnessCast( + _ source: Dictionary, + _ keyWitness: (SourceKey) -> TargetKey, + _ valueWitness: (SourceValue) -> TargetValue +) -> Dictionary { + return Dictionary( + _mapping: source, + // String and NSString have different concepts of equality, so + // NSString-keyed Dictionaries may generate key collisions when "upcasted" + // to String. See rdar://problem/35995647 + allowingDuplicates: (TargetKey.self == String.self) + ) { k, v in + (keyWitness(k), valueWitness(v)) + }! +} + /// Called by the casting machinery. @_silgen_name("_swift_dictionaryDownCastIndirect") @_unavailableInEmbedded diff --git a/stdlib/public/core/SetCasting.swift b/stdlib/public/core/SetCasting.swift index 76208d91649c6..f5a7d83c854e5 100644 --- a/stdlib/public/core/SetCasting.swift +++ b/stdlib/public/core/SetCasting.swift @@ -56,6 +56,22 @@ public func _setUpCast( }! } +@_alwaysEmitIntoClient +public func _setWitnessCast( + _ source: Set, + _ witness: (SourceElement) -> TargetElement +) -> Set { + return Set( + _mapping: source, + // String and NSString have different concepts of equality, so Set + // may generate key collisions when "upcasted" to Set. + // See rdar://problem/35995647 + allowingDuplicates: (TargetElement.self == String.self) + ) { member in + witness(member) + }! +} + /// Called by the casting machinery. @_silgen_name("_swift_setDownCastIndirect") @_unavailableInEmbedded diff --git a/stdlib/public/runtime/DynamicCast.cpp b/stdlib/public/runtime/DynamicCast.cpp index df73bff917fbb..916cbb59f4db0 100644 --- a/stdlib/public/runtime/DynamicCast.cpp +++ b/stdlib/public/runtime/DynamicCast.cpp @@ -1386,6 +1386,8 @@ tryCastToTuple( /******************************************************************************/ // The only thing that can be legally cast to a function is another function. +// Note that this logic must be aligned with with swift::classifyDynamicCast() +// in lib/SIL/Utils/DynamicCast.cpp static DynamicCastResult tryCastToFunction( diff --git a/test/Casting/CollectionCasts.swift b/test/Casting/CollectionCasts.swift new file mode 100644 index 0000000000000..bbd3cf0a3d2c0 --- /dev/null +++ b/test/Casting/CollectionCasts.swift @@ -0,0 +1,675 @@ +// CollectionCasts.swift - Tests for conversion between collection types. +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// Contains tests for non-trapping type conversions reported by users. +/// +// ----------------------------------------------------------------------------- +// RUN: %empty-directory(%t) +// +// RUN: %target-build-swift -Xfrontend -verify -swift-version 6 -g -Onone -module-name a %s -o %t/a.swift6.Onone.out +// RUN: %target-codesign %t/a.swift6.Onone.out +// RUN: %target-run %t/a.swift6.Onone.out +// +// RAN: %target-build-swift -Xfrontend -verify -swift-version 6 -g -O -module-name a %s -o %t/a.swift6.O.out +// RAN: %target-codesign %t/a.swift6.O.out +// RAN: %target-run %t/a.swift6.O.out +// +// RAN: %target-build-swift -Xfrontend -verify -swift-version 5 -g -Onone -module-name a %s -o %t/a.swift5.Onone.out +// RAN: %target-codesign %t/a.swift5.Onone.out +// RAN: %target-run %t/a.swift5.Onone.out +// +// RAN: %target-build-swift -Xfrontend -verify -swift-version 5 -g -O -module-name a %s -o %t/a.swift5.O.out +// RAN: %target-codesign %t/a.swift5.O.out +// RAN: %target-run %t/a.swift5.O.out +// +// REQUIRES: executable_test +// REQUIRES: objc_interop +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime + +import StdlibUnittest +#if _runtime(_ObjC) +import Foundation +#endif + +#if _runtime(_ObjC) +@objc +class Root: NSObject {} +#else +class Root {} +#endif + +@objc +class Base: Root { + var k: Int + + init(_ k: Int) { + self.k = k + } +} + +@objc +class Derived: Base { + +} + +@preconcurrency func id(_ x: [@MainActor () -> String]) -> [@MainActor () -> String] { x } + +extension TestSuite { + @inline(never) + public func testSync( + _ name: String, + file: String = #file, line: UInt = #line, + _ testFunction: @escaping @isolated(any) () -> Void + ) { + self.test(name, file: file, line: line) { + await testFunction() + } + } +} + +let ArrayCasts = TestSuite("ArrayCasts") + +ArrayCasts.testSync("Funtion Conversion: covariant result") { + typealias X = [() -> Derived] + typealias Y = [() -> Base] + + let a: X = [ + { Derived(42) }, + { Derived(-1) }, + { Derived(37) } + ] + + let b: Y = a + expectEqual(b.count, 3) + expectEqual(b[0]().k, 42) + expectEqual(b[1]().k, -1) + expectEqual(b[2]().k, 37) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<() -> Derived>') to 'Y' (aka 'Array<() -> Base>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} + +ArrayCasts.testSync("Funtion Conversion: contravariant argument") { + typealias X = [(Base) -> Int] + typealias Y = [(Derived) -> Int] + + let a: X = [ + { $0.k + 42 }, + { $0.k - 1 }, + { $0.k + 37 } + ] + + let b: Y = a + expectEqual(b.count, 3) + let probe = Derived(10) + expectEqual(b[0](probe), 52) + expectEqual(b[1](probe), 9) + expectEqual(b[2](probe), 47) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<(Base) -> Int>') to 'Y' (aka 'Array<(Derived) -> Int>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} + +#if _runtime(_ObjC) +ArrayCasts.testSync("Funtion Conversion: change calling convention 1") { + typealias X = [(Base) -> Int] + // This crashes compiler + // See https://github.com/swiftlang/swift/issues/85082 + // typealias YA = [@convention(block) (Derived) -> Int] + + let a: X = [ + { $0.k + 42 }, + { $0.k - 1 }, + { $0.k + 37 } + ] + + let b: [@convention(block) (Derived) -> Int] = a + expectEqual(b.count, 3) + let probe = Derived(10) + expectEqual(b[0](probe), 52) + expectEqual(b[1](probe), 9) + expectEqual(b[2](probe), 47) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<(Base) -> Int>') to '[@convention(block) (Derived) -> Int]' always succeeds}} + let c = a as? [@convention(block) (Derived) -> Int] + expectNil(c) + + let d = (a as Any) as? [@convention(block) (Derived) -> Int] + expectNil(d) +} + +ArrayCasts.testSync("Funtion Conversion: change calling convention 2") { + // This crashes compiler + // See https://github.com/swiftlang/swift/issues/85082 + // typealias X = [@convention(block) (Base) -> Int] + typealias Y = [(Derived) -> Int] + + let a: [@convention(block) (Base) -> Int] = [ + { $0.k + 42 }, + { $0.k - 1 }, + { $0.k + 37 } + ] + + let b: Y = a + expectEqual(b.count, 3) + let probe = Derived(10) + expectEqual(b[0](probe), 52) + expectEqual(b[1](probe), 9) + expectEqual(b[2](probe), 47) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from '[@convention(block) (Base) -> Int]' to 'Y' (aka 'Array<(Derived) -> Int>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} +#endif + +ArrayCasts.testSync("Funtion Conversion: add isolation") { + typealias X = [@Sendable () -> Int] + typealias Y = [@MainActor @Sendable () -> Int] + + let a: X = [ + { 42 }, + { -1 }, + { 37 } + ] + + let b: Y = a + expectEqual(b.count, 3) + expectEqual(b[0](), 42) + expectEqual(b[1](), -1) + expectEqual(b[2](), 37) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<@Sendable () -> Int>') to 'Y' (aka 'Array<@MainActor @Sendable () -> Int>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} + +ArrayCasts.test("Funtion Conversion: as @isolated(any)").require(.stdlib_6_0).code { + guard #available(SwiftStdlib 6.0, *) else { return } + typealias X = [@MainActor @Sendable () -> Int] + typealias Y = [@isolated(any) () -> Int] + + let a: X = [ + { 42 }, + { -1 }, + { 37 } + ] + + let b: Y = a + expectEqual(b.count, 3) + expectEqual(await b[0](), 42) + expectEqual(await b[1](), -1) + expectEqual(await b[2](), 37) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<@MainActor @Sendable () -> Int>') to 'Y' (aka 'Array<@isolated(any) () -> Int>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} + +ArrayCasts.testSync("Metatype Conversion") { + typealias X = [Derived.Type] + typealias Y = [Base.Type] + + let a: X = [ + Derived.self + ] + + let b: Y = a + expectEqual(b.count, 1) + expectTrue(b[0] === Derived.self) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectTrue(c![0] === Derived.self) + + let d = (a as Any) as? Y + expectTrue(d![0] === Derived.self) +} + +ArrayCasts.testSync("Erasure 1") { + typealias X = [Base] + typealias Y = [AnyObject] + + let a: X = [ + Base(42), + Base(-1), + Base(37) + ] + + let b: Y = a + expectEqual(b.count, 3) + expectTrue(b[0] === a[0]) + expectTrue(b[1] === a[1]) + expectTrue(b[2] === a[2]) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 3) + expectTrue(c![0] === a[0]) + expectTrue(c![1] === a[1]) + expectTrue(c![2] === a[2]) + + let d = (a as Any) as? Y + expectEqual(d!.count, 3) + expectTrue(d![0] === a[0]) + expectTrue(d![1] === a[1]) + expectTrue(d![2] === a[2]) +} + +ArrayCasts.testSync("Erasure 2") { + typealias X = [String] + typealias Y = [any Hashable] + + let a: X = [ + "abc", + "xyz", + "ijk" + ] + + let b: Y = a + expectEqual(b.count, 3) + expectTrue(b[0].hashValue == a[0].hashValue) + expectTrue(b[1].hashValue == a[1].hashValue) + expectTrue(b[2].hashValue == a[2].hashValue) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 3) + expectTrue(c![0].hashValue == a[0].hashValue) + expectTrue(c![1].hashValue == a[1].hashValue) + expectTrue(c![2].hashValue == a[2].hashValue) + + let d = (a as Any) as? Y + expectEqual(d!.count, 3) + expectTrue(d![0].hashValue == a[0].hashValue) + expectTrue(d![1].hashValue == a[1].hashValue) + expectTrue(d![2].hashValue == a[2].hashValue) +} + +ArrayCasts.testSync("AnyHashable erasure") { + typealias X = [String] + typealias Y = [AnyHashable] + + let a: X = [ + "abc", + "xyz", + "ijk" + ] + + let b: Y = a + expectEqual(b.count, 3) + expectTrue(b[0].hashValue == a[0].hashValue) + expectTrue(b[1].hashValue == a[1].hashValue) + expectTrue(b[2].hashValue == a[2].hashValue) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 3) + expectTrue(c![0].hashValue == a[0].hashValue) + expectTrue(c![1].hashValue == a[1].hashValue) + expectTrue(c![2].hashValue == a[2].hashValue) + + let d = (a as Any) as? Y + expectEqual(d!.count, 3) + expectTrue(d![0].hashValue == a[0].hashValue) + expectTrue(d![1].hashValue == a[1].hashValue) + expectTrue(d![2].hashValue == a[2].hashValue) +} + +ArrayCasts.testSync("Bridge To ObjC 1") { + typealias X = [String] + typealias Y = [AnyObject] + + let a: X = [ + "abc", + "xyz", + "ijk" + ] + + let b = a as Y + expectEqual(b.count, 3) + expectTrue((b[0] as! String) == a[0]) + expectTrue((b[1] as! String) == a[1]) + expectTrue((b[2] as! String) == a[2]) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 3) + expectTrue((c![0] as! String) == a[0]) + expectTrue((c![1] as! String) == a[1]) + expectTrue((c![2] as! String) == a[2]) + + let d = (a as Any) as? Y + expectEqual(d!.count, 3) + expectTrue((d![0] as! String) == a[0]) + expectTrue((d![1] as! String) == a[1]) + expectTrue((d![2] as! String) == a[2]) +} + +#if _runtime(_ObjC) +ArrayCasts.testSync("Bridge To ObjC 2") { + typealias X = [String] + typealias Y = [NSString] + + let a: X = [ + "abc", + "xyz", + "ijk" + ] + + let b = a as Y + expectEqual(b.count, 3) + expectTrue(b[0] as String == a[0]) + expectTrue(b[1] as String == a[1]) + expectTrue(b[2] as String == a[2]) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 3) + expectTrue((c![0] as String) == a[0]) + expectTrue((c![1] as String) == a[1]) + expectTrue((c![2] as String) == a[2]) + + let d = (a as Any) as? Y + expectEqual(d!.count, 3) + expectTrue((d![0] as String) == a[0]) + expectTrue((d![1] as String) == a[1]) + expectTrue((d![2] as String) == a[2]) +} + +ArrayCasts.testSync("Bridge To ObjC 3") { + typealias X = [String] + typealias Y = [CFString] + + let a: X = [ + "abc", + "xyz", + "ijk" + ] + + let b = a as Y + expectEqual(b.count, 3) + expectTrue(b[0] as String == a[0]) + expectTrue(b[1] as String == a[1]) + expectTrue(b[2] as String == a[2]) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 3) + expectTrue((c![0] as String) == a[0]) + expectTrue((c![1] as String) == a[1]) + expectTrue((c![2] as String) == a[2]) + + let d = (a as Any) as? Y + expectEqual(d!.count, 3) + expectTrue((d![0] as String) == a[0]) + expectTrue((d![1] as String) == a[1]) + expectTrue((d![2] as String) == a[2]) +} +#endif + +ArrayCasts.testSync("Metatype to AnyObject 1") { + typealias X = [String.Type] + typealias Y = [AnyObject] + + let a: X = [ + String.self + ] + + let b = a as Y + expectEqual(b.count, 1) + expectTrue((b[0] as! Any.Type) == (a[0] as Any.Type)) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 1) + expectTrue((c![0] as! Any.Type) == (a[0] as Any.Type)) + + let d = (a as Any) as? Y + expectEqual(d!.count, 1) + expectTrue((d![0] as! Any.Type) == (a[0] as Any.Type)) +} + +ArrayCasts.testSync("Metatype to AnyObject 2") { + typealias X = [(any Hashable).Type] + typealias Y = [AnyObject] + + let a: X = [ + (any Hashable).self + ] + + let b = a as Y + expectEqual(b.count, 1) + expectTrue((b[0] as! Any.Type) == (a[0] as Any.Type)) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<(any Hashable).Type>') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 1) + expectTrue((c![0] as! Any.Type) == (a[0] as Any.Type)) + + let d = (a as Any) as? Y + expectEqual(d!.count, 1) + expectTrue((d![0] as! Any.Type) == (a[0] as Any.Type)) +} + +ArrayCasts.testSync("Metatype to AnyObject 3") { + typealias X = [any Hashable.Type] + typealias Y = [AnyObject] + + let a: X = [ + Int.self, + String.self, + AnyHashable.self + ] + + let b = a as Y + expectEqual(b.count, 3) + expectTrue((b[0] as! Any.Type) == a[0]) + expectTrue((b[1] as! Any.Type) == a[1]) + expectTrue((b[2] as! Any.Type) == a[2]) + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array') to 'Y' (aka 'Array') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 3) + expectTrue((c![0] as! Any.Type) == a[0]) + expectTrue((c![1] as! Any.Type) == a[1]) + expectTrue((c![2] as! Any.Type) == a[2]) + + let d = (a as Any) as? Y + expectEqual(d!.count, 3) + expectTrue((d![0] as! Any.Type) == a[0]) + expectTrue((d![1] as! Any.Type) == a[1]) + expectTrue((d![2] as! Any.Type) == a[2]) +} + +ArrayCasts.testSync("Nested") { + typealias X = [[String]] + typealias Y = [[AnyObject]] + + let a: X = [ + ["xyz", "abc"], + ["ijk"], + [] + ] + + let b = a as Y + expectEqual(b.count, 3) + expectEqual(b[0].count, 2) + expectEqual(b[1].count, 1) + expectEqual(b[2].count, 0) + expectTrue((b[0][0] as! String) == "xyz") + expectTrue((b[0][1] as! String) == "abc") + expectTrue((b[1][0] as! String) == "ijk") + + // Indeed succeeds + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array>') to 'Y' (aka 'Array>') always succeeds}} + let c = a as? Y + expectEqual(c!.count, 3) + expectEqual(c![0].count, 2) + expectEqual(c![1].count, 1) + expectEqual(c![2].count, 0) + expectTrue((c![0][0] as! String) == "xyz") + expectTrue((c![0][1] as! String) == "abc") + expectTrue((c![1][0] as! String) == "ijk") + + let d = (a as Any) as? Y + expectEqual(d!.count, 3) + expectEqual(d![0].count, 2) + expectEqual(d![1].count, 1) + expectEqual(d![2].count, 0) + expectTrue((d![0][0] as! String) == "xyz") + expectTrue((d![0][1] as! String) == "abc") + expectTrue((d![1][0] as! String) == "ijk") +} + +ArrayCasts.testSync("Covariant") { + + typealias X = [([any StringProtocol]) -> [String]] + typealias Y = [([String]) -> [any StringProtocol]] + + let a: X = [ + { $0 as! [String] }, + { _ in ["abc"] }, + { ($0 + $0) as! [String] } + ] + + let b = a as Y + expectEqual(b.count, 3) + expectEqual(b[0](["xyz", "ijk"]) as! [String], ["xyz", "ijk"]) + expectEqual(b[1](["xyz", "ijk"]) as! [String], ["abc"]) + expectEqual(b[2](["xyz", "ijk"]) as! [String], ["xyz", "ijk", "xyz", "ijk"]) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<(Array) -> Array>') to 'Y' (aka 'Array<(Array) -> Array>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} + +ArrayCasts.testSync("Covariant nested") { + + typealias X = [([() -> any StringProtocol]) -> [String]] + typealias Y = [([() -> String]) -> [any StringProtocol]] + + let a: X = [ + { x in x.map { $0() as! String } }, + { _ in ["abc"] }, + { x in (x + x).map { $0() as! String } } + ] + + let b = a as Y + expectEqual(b.count, 3) + let probe: [() -> String] = [{ "xyz" }, { "ijk" } ] + expectEqual(b[0](probe) as! [String], ["xyz", "ijk"]) + expectEqual(b[1](probe) as! [String], ["abc"]) + expectEqual(b[2](probe) as! [String], ["xyz", "ijk", "xyz", "ijk"]) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<(Array<() -> any StringProtocol>) -> Array>') to 'Y' (aka 'Array<(Array<() -> String>) -> Array>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} + +ArrayCasts.testSync("Preconcurrency") { + + typealias X = [() -> String] + typealias Y = [@MainActor () -> String] + + let a: X = [ + { "abc" }, + { "xyz" }, + { "ijk" } + ] + + // expected-warning@+1 {{converting non-Sendable function value to '@MainActor @Sendable () -> String' may introduce data races}} + let b: Y = id(a) + expectEqual(b.count, 3) + expectEqual(b[0](), "abc") + expectEqual(b[1](), "xyz") + expectEqual(b[2](), "ijk") + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Array<() -> String>') to 'Y' (aka 'Array<@MainActor @Sendable () -> String>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} + +let DictionaryCasts = TestSuite("DictionaryCasts") + +DictionaryCasts.testSync("Funtion Conversion: covariant result") { + typealias X = [String: () -> Derived] + typealias Y = [AnyHashable: () -> Base] + + let a: X = [ + "abc": { Derived(42) }, + "xyz": { Derived(-1) }, + "ijk": { Derived(37) } + ] + + let b: Y = a + expectEqual(b.count, 3) + expectEqual(b["abc"]?().k, 42) + expectEqual(b["xyz"]?().k, -1) + expectEqual(b["ijk"]?().k, 37) + + // Despite the warning saying that this cast always succeeds, it actually fails + // expected-warning@+1 {{conditional cast from 'X' (aka 'Dictionary Derived>') to 'Y' (aka 'Dictionary Base>') always succeeds}} + let c = a as? Y + expectNil(c) + + let d = (a as Any) as? Y + expectNil(d) +} + +await runAllTestsAsync() diff --git a/test/Interpreter/collection_casts.swift b/test/Interpreter/collection_casts.swift index c8c1b1286b4a8..84301a93f30e2 100644 --- a/test/Interpreter/collection_casts.swift +++ b/test/Interpreter/collection_casts.swift @@ -79,6 +79,13 @@ a_array_4.forEach { $0.preen() } // CHECK-NEXT: A10 // CHECK-NEXT: A20 +let get_a_array_1: [() -> A] = [ { A(5) }, { A(10) }, { A(20) } ] +let get_preening_array_1 = get_a_array_1 as [Preening] +get_preening_array_1.forEach { $0.preen() } +// CHECK-NEXT: A5 +// CHECK-NEXT: A10 +// CHECK-NEXT: A20 + } do {