From 69e450e7d2643eda1979911b502af151a5df5dab Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Thu, 2 Oct 2025 19:20:40 +0200 Subject: [PATCH 1/9] WIP --- include/swift/AST/KnownDecls.def | 1 + lib/SILGen/SILGenDynamicCast.cpp | 2 +- lib/SILGen/SILGenExpr.cpp | 225 ++++++++++++++++++++++++++++- lib/SILGen/SILGenFunction.h | 2 + lib/SILGen/SILGenPoly.cpp | 2 +- stdlib/public/core/ArrayCast.swift | 8 + 6 files changed, 235 insertions(+), 5 deletions(-) diff --git a/include/swift/AST/KnownDecls.def b/include/swift/AST/KnownDecls.def index 8420546717b08..d47cac9a2d06f 100644 --- a/include/swift/AST/KnownDecls.def +++ b/include/swift/AST/KnownDecls.def @@ -20,6 +20,7 @@ #endif FUNC_DECL(ArrayForceCast, "_arrayForceCast") +FUNC_DECL(ArrayWitnessCast, "_arrayWitnessCast") FUNC_DECL(ArrayConditionalCast, "_arrayConditionalCast") FUNC_DECL(DictionaryUpCast, "_dictionaryUpCast") diff --git a/lib/SILGen/SILGenDynamicCast.cpp b/lib/SILGen/SILGenDynamicCast.cpp index 31be3f15ef3cf..397cfa91a5316 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, ManagedValue(), ManagedValue(), C); } static ManagedValue diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 9f20526949ef0..527dd081effcf 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -474,6 +474,7 @@ namespace { RValue visitCollectionUpcastConversionExpr( CollectionUpcastConversionExpr *E, SGFContext C); + ManagedValue emitConversionClosure(CollectionUpcastConversionExpr::ConversionPair pair, SGFContext C); RValue visitBridgeToObjCExpr(BridgeToObjCExpr *E, SGFContext C); RValue visitPackExpansionExpr(PackExpansionExpr *E, SGFContext C); RValue visitPackElementExpr(PackElementExpr *E, SGFContext C); @@ -1514,6 +1515,8 @@ RValue SILGenFunction::emitCollectionConversion(SILLocation loc, CanType fromCollection, CanType toCollection, ManagedValue mv, + ManagedValue keyConversion, + ManagedValue valueConversion, SGFContext C) { SmallVector replacementTypes; @@ -1529,7 +1532,79 @@ RValue SILGenFunction::emitCollectionConversion(SILLocation loc, auto subMap = SubstitutionMap::get(genericSig, replacementTypes, LookUpConformanceInModule()); - return emitApplyOfLibraryIntrinsic(loc, fn, subMap, {mv}, C); + + SmallVector args = { mv }; + if (keyConversion.isValid()) { + args.push_back(keyConversion); + } + + if (valueConversion.isValid()) { + args.push_back(valueConversion); + } + + return emitApplyOfLibraryIntrinsic(loc, fn, subMap, args, C); +} + +static bool +needsCustomConversion(CollectionUpcastConversionExpr::ConversionPair const & 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::Load: + case ExprKind::ABISafeConversion: + case ExprKind::DestructureTuple: + case ExprKind::CovariantFunctionConversion: + case ExprKind::CovariantReturnConversion: + case ExprKind::CollectionUpcastConversion: + 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,8 +1622,15 @@ visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E, // Get the intrinsic function. FuncDecl *fn = nullptr; + ManagedValue keyConversion; + ManagedValue valueConversion; if (fromCollection->isArray()) { - fn = SGF.SGM.getArrayForceCast(loc); + if (needsCustomConversion(E->getValueConversion())) { + fn = SGF.SGM.getArrayWitnessCast(loc); + valueConversion = emitConversionClosure(E->getValueConversion(), C); + } else { + fn = SGF.SGM.getArrayForceCast(loc); + } } else if (fromCollection->isDictionary()) { fn = SGF.SGM.getDictionaryUpCast(loc); } else if (fromCollection->isSet()) { @@ -1558,7 +1640,144 @@ visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E, } return SGF.emitCollectionConversion(loc, fn, fromCollection, toCollection, - mv, C); + 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 + +ManagedValue RValueEmitter::emitConversionClosure(CollectionUpcastConversionExpr::ConversionPair pair, SGFContext C) { + 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 *body = emitter.visit(pair.Conversion); + ASTNode bodyNode(body); + auto *BS = BraceStmt::createImplicit(Context, ArrayRef(bodyNode)); + closure->setBody(BS); + + //closure->setExplicitResultType(); + + RValue result = visitAbstractClosureExpr(closure, C); + return std::move(result).getScalarValue(); } RValue diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 3711fa02f46f7..2768b77a52d75 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -1763,6 +1763,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction CanType fromCollection, CanType toCollection, ManagedValue mv, + ManagedValue keyConversion, + ManagedValue valueConversion, SGFContext C); //===--------------------------------------------------------------------===// diff --git a/lib/SILGen/SILGenPoly.cpp b/lib/SILGen/SILGenPoly.cpp index 9050585a39769..655f1f5333e63 100644 --- a/lib/SILGen/SILGenPoly.cpp +++ b/lib/SILGen/SILGenPoly.cpp @@ -648,7 +648,7 @@ ManagedValue Transform::transform(ManagedValue v, } return SGF.emitCollectionConversion(Loc, fn, inputSubstType, - outputSubstType, v, ctxt) + outputSubstType, v, ManagedValue(), ManagedValue(), 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( From e7d8ed8972666232123dc63103667b215511723d Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Fri, 3 Oct 2025 12:04:54 +0200 Subject: [PATCH 2/9] Added closure type, captures and discriminator --- lib/SILGen/SILGenExpr.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 527dd081effcf..19c6e763a87ba 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -1774,7 +1774,19 @@ ManagedValue RValueEmitter::emitConversionClosure(CollectionUpcastConversionExpr auto *BS = BraceStmt::createImplicit(Context, ArrayRef(bodyNode)); closure->setBody(BS); - //closure->setExplicitResultType(); + 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); RValue result = visitAbstractClosureExpr(closure, C); return std::move(result).getScalarValue(); From 9784c7aa917159909375306d79ead9eb13ba3ace Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Fri, 17 Oct 2025 23:21:13 +0200 Subject: [PATCH 3/9] Emit converted closure --- lib/SILGen/SILGenExpr.cpp | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 19c6e763a87ba..05810b4719b85 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -474,7 +474,9 @@ namespace { RValue visitCollectionUpcastConversionExpr( CollectionUpcastConversionExpr *E, SGFContext C); - ManagedValue emitConversionClosure(CollectionUpcastConversionExpr::ConversionPair pair, SGFContext C); + ManagedValue + emitConversionClosure(CollectionUpcastConversionExpr::ConversionPair pair, + SGFContext C, FuncDecl *castFunc, unsigned argIndex); RValue visitBridgeToObjCExpr(BridgeToObjCExpr *E, SGFContext C); RValue visitPackExpansionExpr(PackExpansionExpr *E, SGFContext C); RValue visitPackElementExpr(PackElementExpr *E, SGFContext C); @@ -1627,7 +1629,8 @@ visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E, if (fromCollection->isArray()) { if (needsCustomConversion(E->getValueConversion())) { fn = SGF.SGM.getArrayWitnessCast(loc); - valueConversion = emitConversionClosure(E->getValueConversion(), C); + valueConversion = + emitConversionClosure(E->getValueConversion(), C, fn, 1); } else { fn = SGF.SGM.getArrayForceCast(loc); } @@ -1736,7 +1739,9 @@ class ConversionClosureEmitter: public Lowering::ExprVisitorsetDiscriminator(discriminator++); Context.setMaxAssignedDiscriminator(declContext, discriminator); - RValue result = visitAbstractClosureExpr(closure, C); - return std::move(result).getScalarValue(); + Type replacementTypes[] = {pair.OrigValue->getType(), + pair.Conversion->getType()}; + auto subs = + SubstitutionMap::get(castFunc->getGenericSignature(), replacementTypes, + LookUpConformanceInModule()); + + CanType origParamType = castFunc->getParameters() + ->get(argIndex) + ->getInterfaceType() + ->getCanonicalType(); + CanType substParamType = origParamType.subst(subs)->getCanonicalType(); + + // Ensure that the closure has the appropriate type. + AbstractionPattern origParam( + castFunc->getGenericSignature().getCanonicalSignature(), origParamType); + + auto conversion = Conversion::getSubstToOrig( + origParam, substParamType, SGF.getLoweredType(closure->getType()), + SGF.getLoweredType(origParam, substParamType)); + + return SGF.emitConvertedRValue(closure, conversion); } RValue From 4febfb13681b2021c670eb859fdb257459a9bc32 Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Sat, 18 Oct 2025 00:11:37 +0200 Subject: [PATCH 4/9] Added functions for dictionary and set --- include/swift/AST/KnownDecls.def | 2 ++ lib/SILGen/SILGenExpr.cpp | 15 ++++++++++++++- stdlib/public/core/DictionaryCasting.swift | 17 +++++++++++++++++ stdlib/public/core/SetCasting.swift | 16 ++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/include/swift/AST/KnownDecls.def b/include/swift/AST/KnownDecls.def index d47cac9a2d06f..894350851bfac 100644 --- a/include/swift/AST/KnownDecls.def +++ b/include/swift/AST/KnownDecls.def @@ -24,10 +24,12 @@ 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/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 05810b4719b85..72e15ce6db8b3 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -1635,9 +1635,22 @@ visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E, fn = SGF.SGM.getArrayForceCast(loc); } } else if (fromCollection->isDictionary()) { + if (needsCustomConversion(E->getKeyConversion()) || + needsCustomConversion(E->getValueConversion())) { + fn = SGF.SGM.getDictionaryWitnessCast(loc); + keyConversion = emitConversionClosure(E->getKeyConversion(), C, fn, 1); + valueConversion = + emitConversionClosure(E->getValueConversion(), C, fn, 2); + } fn = SGF.SGM.getDictionaryUpCast(loc); } else if (fromCollection->isSet()) { - fn = SGF.SGM.getSetUpCast(loc); + if (needsCustomConversion(E->getValueConversion())) { + fn = SGF.SGM.getSetWitnessCast(loc); + valueConversion = + emitConversionClosure(E->getValueConversion(), C, fn, 1); + } else { + fn = SGF.SGM.getSetUpCast(loc); + } } else { llvm_unreachable("unsupported collection upcast kind"); } 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 From 85da32a66c1475ea43c8583ce1a86d0b9368b084 Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Sat, 18 Oct 2025 01:07:28 +0200 Subject: [PATCH 5/9] Moved emitting closure value inside emitCollectionConversion() --- lib/SILGen/SILGenDynamicCast.cpp | 2 +- lib/SILGen/SILGenExpr.cpp | 98 ++++++++++++++------------------ lib/SILGen/SILGenFunction.h | 12 ++-- lib/SILGen/SILGenPoly.cpp | 7 ++- 4 files changed, 53 insertions(+), 66 deletions(-) diff --git a/lib/SILGen/SILGenDynamicCast.cpp b/lib/SILGen/SILGenDynamicCast.cpp index 397cfa91a5316..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, ManagedValue(), ManagedValue(), C); + source, nullptr, nullptr, C); } static ManagedValue diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 72e15ce6db8b3..f93eb9a432547 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -474,9 +474,8 @@ namespace { RValue visitCollectionUpcastConversionExpr( CollectionUpcastConversionExpr *E, SGFContext C); - ManagedValue - emitConversionClosure(CollectionUpcastConversionExpr::ConversionPair pair, - SGFContext C, FuncDecl *castFunc, unsigned argIndex); + ClosureExpr *synthesizeConversionClosure( + CollectionUpcastConversionExpr::ConversionPair pair); RValue visitBridgeToObjCExpr(BridgeToObjCExpr *E, SGFContext C); RValue visitPackExpansionExpr(PackExpansionExpr *E, SGFContext C); RValue visitPackElementExpr(PackElementExpr *E, SGFContext C); @@ -1512,14 +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, - ManagedValue keyConversion, - ManagedValue valueConversion, - 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(); @@ -1536,12 +1531,29 @@ RValue SILGenFunction::emitCollectionConversion(SILLocation loc, LookUpConformanceInModule()); SmallVector args = { mv }; - if (keyConversion.isValid()) { - args.push_back(keyConversion); - } + 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); - if (valueConversion.isValid()) { - args.push_back(valueConversion); + 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); @@ -1624,13 +1636,12 @@ visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E, // Get the intrinsic function. FuncDecl *fn = nullptr; - ManagedValue keyConversion; - ManagedValue valueConversion; + ClosureExpr *keyConversion = nullptr; + ClosureExpr *valueConversion = nullptr; if (fromCollection->isArray()) { if (needsCustomConversion(E->getValueConversion())) { fn = SGF.SGM.getArrayWitnessCast(loc); - valueConversion = - emitConversionClosure(E->getValueConversion(), C, fn, 1); + valueConversion = synthesizeConversionClosure(E->getValueConversion()); } else { fn = SGF.SGM.getArrayForceCast(loc); } @@ -1638,16 +1649,15 @@ visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E, if (needsCustomConversion(E->getKeyConversion()) || needsCustomConversion(E->getValueConversion())) { fn = SGF.SGM.getDictionaryWitnessCast(loc); - keyConversion = emitConversionClosure(E->getKeyConversion(), C, fn, 1); - valueConversion = - emitConversionClosure(E->getValueConversion(), C, fn, 2); + keyConversion = synthesizeConversionClosure(E->getKeyConversion()); + valueConversion = synthesizeConversionClosure(E->getValueConversion()); + } else { + fn = SGF.SGM.getDictionaryUpCast(loc); } - fn = SGF.SGM.getDictionaryUpCast(loc); } else if (fromCollection->isSet()) { if (needsCustomConversion(E->getValueConversion())) { fn = SGF.SGM.getSetWitnessCast(loc); - valueConversion = - emitConversionClosure(E->getValueConversion(), C, fn, 1); + valueConversion = synthesizeConversionClosure(E->getValueConversion()); } else { fn = SGF.SGM.getSetUpCast(loc); } @@ -1655,8 +1665,8 @@ visitCollectionUpcastConversionExpr(CollectionUpcastConversionExpr *E, llvm_unreachable("unsupported collection upcast kind"); } - return SGF.emitCollectionConversion(loc, fn, fromCollection, toCollection, - mv, keyConversion, valueConversion, C); + return SGF.emitCollectionConversion(loc, fn, fromCollection, toCollection, mv, + keyConversion, valueConversion, C); } namespace { @@ -1752,9 +1762,8 @@ class ConversionClosureEmitter: public Lowering::ExprVisitorsetParameterList(params); ConversionClosureEmitter emitter(Context, pair.OrigValue, param); - Expr *body = emitter.visit(pair.Conversion); - ASTNode bodyNode(body); + 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); @@ -1806,27 +1816,7 @@ ManagedValue RValueEmitter::emitConversionClosure( closure->setDiscriminator(discriminator++); Context.setMaxAssignedDiscriminator(declContext, discriminator); - Type replacementTypes[] = {pair.OrigValue->getType(), - pair.Conversion->getType()}; - auto subs = - SubstitutionMap::get(castFunc->getGenericSignature(), replacementTypes, - LookUpConformanceInModule()); - - CanType origParamType = castFunc->getParameters() - ->get(argIndex) - ->getInterfaceType() - ->getCanonicalType(); - CanType substParamType = origParamType.subst(subs)->getCanonicalType(); - - // Ensure that the closure has the appropriate type. - AbstractionPattern origParam( - castFunc->getGenericSignature().getCanonicalSignature(), origParamType); - - auto conversion = Conversion::getSubstToOrig( - origParam, substParamType, SGF.getLoweredType(closure->getType()), - SGF.getLoweredType(origParam, substParamType)); - - return SGF.emitConvertedRValue(closure, conversion); + return closure; } RValue diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 2768b77a52d75..6d9077ddb4238 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -1758,14 +1758,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction CanType existentialType, SGFContext C = SGFContext()); - RValue emitCollectionConversion(SILLocation loc, - FuncDecl *fn, - CanType fromCollection, - CanType toCollection, - ManagedValue mv, - ManagedValue keyConversion, - ManagedValue valueConversion, - 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 655f1f5333e63..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, ManagedValue(), ManagedValue(), ctxt) - .getScalarValue(); + return SGF + .emitCollectionConversion(Loc, fn, inputSubstType, outputSubstType, v, + nullptr, nullptr, ctxt) + .getScalarValue(); } } From aced19215415bfa7f13ccb05a69ecfdfdb71bda1 Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Thu, 23 Oct 2025 12:10:17 +0200 Subject: [PATCH 6/9] WIP: Unit tests --- test/Casting/CollectionCasts.swift | 280 ++++++++++++++++++++++++ test/Interpreter/collection_casts.swift | 7 + 2 files changed, 287 insertions(+) create mode 100644 test/Casting/CollectionCasts.swift diff --git a/test/Casting/CollectionCasts.swift b/test/Casting/CollectionCasts.swift new file mode 100644 index 0000000000000..b7feba280f6b3 --- /dev/null +++ b/test/Casting/CollectionCasts.swift @@ -0,0 +1,280 @@ +// 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. +/// +// ----------------------------------------------------------------------------- +// X-RUN: %empty-directory(%t) +// +// RUN: %target-build-swift -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 +// +// X-RUN: %target-build-swift -swift-version 6 -g -O -module-name a %s -o %t/a.swift6.O.out +// X-RUN: %target-codesign %t/a.swift6.O.out +// X-RUN: %target-run %t/a.swift6.O.out +// +// X-RUN: %target-build-swift -swift-version 5 -g -Onone -module-name a %s -o %t/a.swift5.Onone.out +// X-RUN: %target-codesign %t/a.swift5.Onone.out +// X-RUN: %target-run %t/a.swift5.Onone.out +// +// X-RUN: %target-build-swift -swift-version 5 -g -O -module-name a %s -o %t/a.swift5.O.out +// X-RUN: %target-codesign %t/a.swift5.O.out +// X-RUN: %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 { + +} + +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 + 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 + 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 + 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 + 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 () -> Int] + + print("0") + let a: X = [ + { 42 }, + { -1 }, + { 37 } + ] + + print("1") + let b: Y = a + print("2") + expectEqual(b.count, 3) + expectEqual(b[0](), 42) + expectEqual(b[1](), -1) + expectEqual(b[2](), 37) + print("3") + + // Despite the warning saying that this cast always succeeds, it actually fails + let c = a as? Y + print("4") + expectNil(c) + print("5") + + let d = (a as Any) as? Y + print("6") + expectNil(d) + print("7") +} + +ArrayCasts.test("Funtion Conversion: as @isolated(any)").require(.stdlib_6_0).code { + guard #available(SwiftStdlib 6.0, *) else { return } + typealias X = [@MainActor () -> 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 + 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) + + let c = a as? Y + expectTrue(c![0] === Derived.self) + + let d = (a as Any) as? Y + expectTrue(d![0] === Derived.self) +} + +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) +} + +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 { From 074120eb64f9ac446f898abbb775a931045eed53 Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Fri, 24 Oct 2025 09:09:23 +0200 Subject: [PATCH 7/9] Fixed overly optimistic optimization of checked cast into unchecked one for function types --- lib/SIL/Utils/DynamicCasts.cpp | 9 ++++++--- stdlib/public/runtime/DynamicCast.cpp | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) 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/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( From e42461e99d91b05490af5742a4878c605d069f5f Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Fri, 24 Oct 2025 17:42:59 +0200 Subject: [PATCH 8/9] Handle CollectionUpcastConversion recursively and add more unit tests --- lib/SILGen/SILGenExpr.cpp | 10 +- test/Casting/CollectionCasts.swift | 408 +++++++++++++++++++++++++++-- 2 files changed, 396 insertions(+), 22 deletions(-) diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index f93eb9a432547..c08d545edf0b0 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -1561,6 +1561,8 @@ RValue SILGenFunction::emitCollectionConversion( 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())) { @@ -1578,12 +1580,18 @@ needsCustomConversion(CollectionUpcastConversionExpr::ConversionPair const & pai 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::CollectionUpcastConversion: case ExprKind::BridgeFromObjC: case ExprKind::ConditionalBridgeFromObjC: case ExprKind::ArchetypeToSuper: diff --git a/test/Casting/CollectionCasts.swift b/test/Casting/CollectionCasts.swift index b7feba280f6b3..5ba2cf21fb831 100644 --- a/test/Casting/CollectionCasts.swift +++ b/test/Casting/CollectionCasts.swift @@ -13,23 +13,23 @@ /// Contains tests for non-trapping type conversions reported by users. /// // ----------------------------------------------------------------------------- -// X-RUN: %empty-directory(%t) +// RAN: %empty-directory(%t) // -// RUN: %target-build-swift -swift-version 6 -g -Onone -module-name a %s -o %t/a.swift6.Onone.out +// 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 // -// X-RUN: %target-build-swift -swift-version 6 -g -O -module-name a %s -o %t/a.swift6.O.out -// X-RUN: %target-codesign %t/a.swift6.O.out -// X-RUN: %target-run %t/a.swift6.O.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 // -// X-RUN: %target-build-swift -swift-version 5 -g -Onone -module-name a %s -o %t/a.swift5.Onone.out -// X-RUN: %target-codesign %t/a.swift5.Onone.out -// X-RUN: %target-run %t/a.swift5.Onone.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 // -// X-RUN: %target-build-swift -swift-version 5 -g -O -module-name a %s -o %t/a.swift5.O.out -// X-RUN: %target-codesign %t/a.swift5.O.out -// X-RUN: %target-run %t/a.swift5.O.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 @@ -94,6 +94,7 @@ ArrayCasts.testSync("Funtion Conversion: covariant result") { 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) @@ -119,6 +120,7 @@ ArrayCasts.testSync("Funtion Conversion: contravariant argument") { 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) @@ -147,6 +149,7 @@ ArrayCasts.testSync("Funtion Conversion: change calling convention 1") { 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) @@ -174,6 +177,7 @@ ArrayCasts.testSync("Funtion Conversion: change calling convention 2") { 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) @@ -184,39 +188,32 @@ ArrayCasts.testSync("Funtion Conversion: change calling convention 2") { ArrayCasts.testSync("Funtion Conversion: add isolation") { typealias X = [@Sendable () -> Int] - typealias Y = [@MainActor () -> Int] + typealias Y = [@MainActor @Sendable () -> Int] - print("0") let a: X = [ { 42 }, { -1 }, { 37 } ] - print("1") let b: Y = a - print("2") expectEqual(b.count, 3) expectEqual(b[0](), 42) expectEqual(b[1](), -1) expectEqual(b[2](), 37) - print("3") // 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 - print("4") expectNil(c) - print("5") let d = (a as Any) as? Y - print("6") expectNil(d) - print("7") } ArrayCasts.test("Funtion Conversion: as @isolated(any)").require(.stdlib_6_0).code { guard #available(SwiftStdlib 6.0, *) else { return } - typealias X = [@MainActor () -> Int] + typealias X = [@MainActor @Sendable () -> Int] typealias Y = [@isolated(any) () -> Int] let a: X = [ @@ -232,6 +229,7 @@ ArrayCasts.test("Funtion Conversion: as @isolated(any)").require(.stdlib_6_0).co 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) @@ -251,6 +249,8 @@ ArrayCasts.testSync("Metatype Conversion") { 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) @@ -258,6 +258,364 @@ ArrayCasts.testSync("Metatype Conversion") { 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) +} + let DictionaryCasts = TestSuite("DictionaryCasts") DictionaryCasts.testSync("Funtion Conversion: covariant result") { @@ -275,6 +633,14 @@ DictionaryCasts.testSync("Funtion Conversion: covariant result") { 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() From 55e02dc1ede355cd572ec4f4b6282cea4c63967e Mon Sep 17 00:00:00 2001 From: Mykola Pokhylets Date: Fri, 24 Oct 2025 19:50:37 +0200 Subject: [PATCH 9/9] Added test for the preconcurrency case --- test/Casting/CollectionCasts.swift | 31 +++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/Casting/CollectionCasts.swift b/test/Casting/CollectionCasts.swift index 5ba2cf21fb831..bbd3cf0a3d2c0 100644 --- a/test/Casting/CollectionCasts.swift +++ b/test/Casting/CollectionCasts.swift @@ -13,7 +13,7 @@ /// Contains tests for non-trapping type conversions reported by users. /// // ----------------------------------------------------------------------------- -// RAN: %empty-directory(%t) +// 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 @@ -62,6 +62,8 @@ class Derived: Base { } +@preconcurrency func id(_ x: [@MainActor () -> String]) -> [@MainActor () -> String] { x } + extension TestSuite { @inline(never) public func testSync( @@ -616,6 +618,33 @@ ArrayCasts.testSync("Covariant nested") { 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") {