Skip to content

[SR-13088] Fix false positive downcast unrelated of types that cannot be statically known #32592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
addc6b8
[TypeCheckConstraints] Adjusting cases where checked casts that canno…
LucianoPAlmeida Jun 28, 2020
6bcb9a9
[tests] Adding regression tests for SR-13088
LucianoPAlmeida Jun 28, 2020
43f1401
[TypeCheckConstraints] Adjusting comment and adding an extra test cas…
LucianoPAlmeida Jun 29, 2020
a563883
[TypeCheckConstraints] Fixing typos in comments
LucianoPAlmeida Jun 29, 2020
2a06563
[AST] Moving implementation of isCollection from ConstraintSystem to …
LucianoPAlmeida Jun 30, 2020
b09e34a
[TypeCheckConstraints] Adjusting logic to verify specific conformance…
LucianoPAlmeida Jun 30, 2020
8ea2a69
[TypeCheckConstraints] Creating new CheckedCastContextKind::Collectio…
LucianoPAlmeida Jun 30, 2020
ec3acbf
[TypeCheckConstraints] Adjusting logic around generic substitution to…
LucianoPAlmeida Jun 30, 2020
53e1278
[Sema] Adding isKnownStdlibCollectionType and replacing all usages co…
LucianoPAlmeida Jun 30, 2020
c2e060e
[TypeChecker] Reverting fixes around array element types
LucianoPAlmeida Jun 30, 2020
d01e808
[TypeChecker] Abstract logic of check for conditional requirements on…
LucianoPAlmeida Jul 1, 2020
b14dc2d
[TypeChecker] Ajdustinc can conformDynamically conform and adjust rev…
LucianoPAlmeida Jul 1, 2020
969ede9
[TypeChecker] Ajusting comments and fixing typos
LucianoPAlmeida Jul 2, 2020
101a5c1
[TypeChecker] Adjusting existential and archetype logic to check insi…
LucianoPAlmeida Jul 2, 2020
026f697
[TypeChecker] Adjusting minor and adding existential check into could…
LucianoPAlmeida Jul 2, 2020
31808e8
[TypeChecker] Adjusting comments
LucianoPAlmeida Jul 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions lib/Sema/TypeCheckConstraints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3249,6 +3249,11 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType,

auto checkElementCast = [&](Type fromElt, Type toElt,
CheckedCastKind castKind) -> CheckedCastKind {
// Let's not emit diagnostic when the element type is erased because
// we can't statically know if element is convertible.
if (fromElt->isAny() || toElt->isAny())
return castKind;

switch (typeCheckCheckedCast(fromElt, toElt, CheckedCastContextKind::None,
dc, SourceLoc(), nullptr, SourceRange())) {
case CheckedCastKind::Coercion:
Expand Down Expand Up @@ -3357,7 +3362,14 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType,
const auto &fromElt = fromTuple->getElement(i);
const auto &toElt = toTuple->getElement(i);

if (fromElt.getName() != toElt.getName())
// We should only perform name validation if both element have a label,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- if both element
+ if both elements

// because unlabeled tuple elements can be converted to labeled ones
// e.g.
//
// let tup: (Any, Any) = (1, 1)
// _ = tup as! (a: Int, Int)
if ((!fromElt.getName().empty() && !toElt.getName().empty()) &&
fromElt.getName() != toElt.getName())
return failed();

auto result = checkElementCast(fromElt.getType(), toElt.getType(),
Expand Down Expand Up @@ -3487,6 +3499,17 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType,
bool toExistential = toType->isExistentialType();
bool fromExistential = fromType->isExistentialType();

auto genericTypeHasExistentialArgument = [](Type type) {
if (auto toGeneric = type->getAs<BoundGenericType>()) {
return llvm::any_of(toGeneric->getGenericArgs(), [](Type argumentType) {
return argumentType->isExistentialType();
});
}
return false;
};

bool toHasGenericExistential = genericTypeHasExistentialArgument(toType);

bool toRequiresClass;
if (toType->isExistentialType())
toRequiresClass = toType->getExistentialLayout().requiresClass();
Expand Down Expand Up @@ -3544,7 +3567,16 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType,
// Note: we relax the restriction if the type we're casting to is a
// non-final class because it's possible that we might have a subclass
// that conforms to the protocol.
if (fromExistential && !toExistential) {
//
// Also, relax the restriction when toType is a bounded generic with
// an existential argument because it may have conditional protocol
// conformances. e.g.
//
// func encodable(_ value: Encodable) {
// _ = value as! [String : Encodable]
// }
//
if (fromExistential && (!toExistential && !toHasGenericExistential)) {
if (auto NTD = toType->getAnyNominal()) {
if (!toType->is<ClassType>() || NTD->isFinal()) {
auto protocolDecl =
Expand Down Expand Up @@ -3616,10 +3648,10 @@ CheckedCastKind TypeChecker::typeCheckCheckedCast(Type fromType,
return CheckedCastKind::ValueCast;
}

// If the destination type can be a supertype of the source type, we are
// performing what looks like an upcast except it rebinds generic
// parameters.
if (fromType->isBindableTo(toType))
// If it is possible to substitute the generic arguments of source type
// with the destination generic archetypes, we are performing what looks
// like an upcast except it rebinds generic parameters.
if (toType->isBindableTo(fromType))
return CheckedCastKind::ValueCast;

// Objective-C metaclasses are subclasses of NSObject in the ObjC runtime,
Expand Down
77 changes: 75 additions & 2 deletions test/Constraints/casts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,8 @@ func test_tuple_casts_no_warn() {
_ = arr as! [(Foo, Foo, Foo)] // expected-warning {{cast from '[(Any, Any)]' to unrelated type '[(Foo, Foo, Foo)]' always fails}}
_ = tup as! (Foo, Foo, Foo) // expected-warning {{cast from '(Any, Any)' to unrelated type '(Foo, Foo, Foo)' always fails}}

_ = arr as! [(a: Foo, Foo)] // expected-warning {{cast from '[(Any, Any)]' to unrelated type '[(a: Foo, Foo)]' always fails}}
_ = tup as! (a: Foo, Foo) // expected-warning {{cast from '(Any, Any)' to unrelated type '(a: Foo, Foo)' always fails}}
_ = arr as! [(a: Foo, Foo)] // Ok
_ = tup as! (a: Foo, Foo) // Ok
}

infix operator ^^^
Expand Down Expand Up @@ -335,3 +335,76 @@ func test_compatibility_coercions(_ arr: [Int], _ optArr: [Int]?, _ dict: [Strin
// The array can also be inferred to be [Any].
_ = ([] ?? []) as Array // expected-warning {{left side of nil coalescing operator '??' has non-optional type '[Any]', so the right side is never used}}
}

// SR-13088
protocol JSON { }
protocol JSONLeaf: JSON {}
extension Int: JSONLeaf { }
extension Array: JSON where Element: JSON { }

protocol SR13035Error: Error {}
class ChildError: SR13035Error {}

protocol AnyC {
func foo()
}

protocol AnyEvent {}

protocol A {
associatedtype C: AnyC
}

protocol EventA: A {
associatedtype Event
}

typealias Container<Namespace>
= (event: Namespace.Event, c: Namespace.C) where Namespace: EventA

enum ConcreteA: EventA {
struct C: AnyC {
func foo() {}
}

enum Event: AnyEvent {
case test
}
}

func tests_SR13088_false_positive_always_fail_casts() {
// SR-13081
let x: JSON = [4] // [4]
_ = x as? [Any] // Ok

// SR-7187
let a: [Any] = [String?.some("hello") as Any, String?.none as Any]

_ = a is [String?] // Ok
_ = a as? [String?] // Ok

// SR-13035
func SR13035<SomeError: SR13035Error>(_ mockResult: Result<String, ChildError>, _: Result<String, SomeError>) {
let _ = mockResult as? Result<String, SomeError> // Ok
}

func SR13035_1<SomeError: SR13035Error, Child: ChildError>(_ mockResult: Result<String, Child>, _: Result<String, SomeError>) {
let _ = mockResult as? Result<String, SomeError> // Ok
}

// SR-11434 and SR-12321
func encodable(_ value: Encodable) {
_ = value as! [String : Encodable] // Ok
_ = value as? [String: Encodable] // Ok
}

// SR-13025
func coordinate(_ event: AnyEvent, from c: AnyC) {
switch (event, c) {
case let container as Container<ConcreteA>: // OK
container.c.foo()
default:
break
}
}
}