Skip to content

Sema: Handle the edge case of in-out metatype parameter in canOpenExistentialCallArgument #78310

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 2 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions lib/Sema/OpenedExistentials.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -649,10 +649,11 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
if (param->isVariadic())
return std::nullopt;

// Look through an inout and optional types on the formal type of the
// parameter.
// Look through an inout and an optional type on the parameter types.
auto formalParamTy = param->getInterfaceType()->getInOutObjectType()
->lookThroughSingleOptionalType();
// Look through an inout and optional types on the parameter.
paramTy = paramTy->getInOutObjectType()->lookThroughSingleOptionalType();

// If the argument is of an existential metatype, look through the
// metatype on the parameter.
Expand All @@ -661,9 +662,6 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
paramTy = paramTy->getMetatypeInstanceType();
}

// Look through an inout and optional types on the parameter.
paramTy = paramTy->getInOutObjectType()->lookThroughSingleOptionalType();

// The parameter type must be a type variable.
auto paramTypeVar = paramTy->getAs<TypeVariableType>();
if (!paramTypeVar)
Expand Down
89 changes: 82 additions & 7 deletions test/Constraints/opened_existentials.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
// RUN: %target-typecheck-verify-swift -disable-availability-checking
// RUN: %target-typecheck-verify-swift -target %target-swift-5.7-abi-triple -dump-ast | %FileCheck %s

/// Used to verify the type of an expression. Use like this:
/// ```
/// var types = SwiftTypePair(typeOf: expr, type2: SwiftType<Int>.self)
/// types.assertTypesAreEqual()
/// ```
struct SwiftType<T> {}
struct SwiftTypePair<T1, T2> {
init(typeOf: T1, type2: SwiftType<T2>.Type) {}

mutating func assertTypesAreEqual() where T1 == T2 {}
}

protocol Q { }

Expand Down Expand Up @@ -26,16 +38,20 @@ func acceptCollection<C: Collection>(_ c: C) -> C.Element { c.first! }

// --- Simple opening of existential values
func testSimpleExistentialOpening(p: any P, pq: any P & Q, c: any Collection) {
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let pa = acceptGeneric(p)
let _: Int = pa // expected-error{{cannot convert value of type '(any Q)?' to specified type 'Int'}}

var vp = p
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let vpa = acceptGeneric(vp)
let _: Int = vpa // expected-error{{cannot convert value of type '(any Q)?' to specified type 'Int'}}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let pqa = acceptGeneric(pq)
let _: Int = pqa // expected-error{{cannot convert value of type '(any Q)?' to specified type 'Int'}}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let element = acceptCollection(c)
let _: Int = element // expected-error{{cannot convert value of type 'Any' to specified type 'Int'}}
}
Expand All @@ -50,6 +66,7 @@ func takeCollectionOfPs<C: Collection>(_: C) -> C.Element.A?
}

func testCollectionOfPs(cp: any CollectionOfPs) {
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let e = takeCollectionOfPs(cp)
let _: Int = e // expected-error{{cannot convert value of type '(any Q)?' to specified type 'Int'}}
}
Expand All @@ -62,9 +79,11 @@ extension P {
}

func testMultipleOpened(a: any P, b: any P & Q) {
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let r1 = takeTwoGenerics(a, b)
let _: Int = r1 // expected-error{{cannot convert value of type '(any P, any P & Q)' to specified type 'Int'}}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let r2 = a.combineThePs(b)
let _: Int = r2 // expected-error{{cannot convert value of type '(any Q, any Q)?' to specified type 'Int'}}
}
Expand All @@ -75,6 +94,7 @@ func conjureValue<T: P>(of type: T.Type) -> T? {
}

func testMagic(pt: any P.Type) {
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let pOpt = conjureValue(of: pt)
let _: Int = pOpt // expected-error{{cannot convert value of type '(any P)?' to specified type 'Int'}}
}
Expand Down Expand Up @@ -162,13 +182,15 @@ func takesInOut<T: P>(_ value: inout T) { }

func passesInOut(i: Int) {
var p: any P = i
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
takesInOut(&p)
}

func takesOptional<T: P>(_ value: T?) { }
// expected-note@-1{{required by global function 'takesOptional' where 'T' = 'any P'}}

func passesToOptional(p: any P, pOpt: (any P)?) {
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
takesOptional(p) // okay
takesOptional(pOpt) // expected-error{{type 'any P' cannot conform to 'P'}}
// expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}}
Expand All @@ -177,20 +199,24 @@ func passesToOptional(p: any P, pOpt: (any P)?) {

@available(SwiftStdlib 5.1, *)
func testReturningOpaqueTypes(p: any P) {
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let q = p.getQ()
let _: Int = q // expected-error{{cannot convert value of type 'any Q' to specified type 'Int'}}

p.getCollectionOf() // expected-error{{member 'getCollectionOf' cannot be used on value of type 'any P'; consider using a generic constraint instead}}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let q2 = getPQ(p)
let _: Int = q2 // expected-error{{cannot convert value of type 'any Q' to specified type 'Int'}}

getCollectionOfP(p) // expected-error{{type 'any P' cannot conform to 'P'}}
// expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
let fi = funnyIdentity(p)
let _: Int = fi // expected-error{{cannot convert value of type '(any P)?' to specified type 'Int'}}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = arrayOfOne(p) // okay, arrays are covariant in their argument

_ = createX(p) // expected-error{{type 'any P' cannot conform to 'P'}}
Expand All @@ -210,13 +236,20 @@ func overloadedGenericFunctionTakingP<T: P>(_: T) { }

func testTakeValueAndClosure(p: any P) {
// Type-erase when not provided with a generic function.
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
takeValueAndClosure(p) { x in
print(x)
let _: Int = x // expected-error{{cannot convert value of type 'any P' to specified type 'Int'}}
var types = SwiftTypePair(typeOf: x, type2: SwiftType<any P>.self)
types.assertTypesAreEqual()

return ()
}

// Do not erase when referring to a generic function.
// FIXME:
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
takeValueAndClosure(p, body: genericFunctionTakingP)
// FIXME:
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
takeValueAndClosure(p, body: overloadedGenericFunctionTakingP)
takeValueAndClosure(p, body: genericFunctionTakingPQ) // expected-error{{global function 'genericFunctionTakingPQ' requires that 'T' conform to 'Q'}}

Expand Down Expand Up @@ -255,32 +288,47 @@ func testExplicitCoercionRequirement(v: any B, otherV: any B & D) {
func overloaded<T: B>(_: T) -> (x: Int, y: T.C) { fatalError() }
func overloaded<T: P>(_: T) -> Int { 42 }

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getC(v) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getC(v) as any P // Ok


// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getE(v) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getE(v) as any P1<Double> // Ok


// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getTuple(v) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getTuple(v) as (any B, any P) // Ok
// Ok because T.C.A == Double
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getNoError(v)

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getComplex(v) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getComplex(v) as ([(x: (a: any P, b: Int), y: Int)], [Int : any P]) // Ok

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = overloaded(v) // Ok

func acceptsAny<T>(_: T) {}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
acceptsAny(getC(v)) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
acceptsAny(getC(v) as any P) // Ok

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
acceptsAny(getComplex(v)) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
acceptsAny(getComplex(v) as ([(x: (a: any P, b: Int), y: Int)], [Int : any P]))

func getAssocNoRequirements<T: B>(_: T) -> (Int, [T.D]) { fatalError() }

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getAssocNoRequirements(v) // Ok, `D` doesn't have any requirements

// Test existential opening from protocol extension access
Expand All @@ -292,24 +340,35 @@ func testExplicitCoercionRequirement(v: any B, otherV: any B & D) {

func getF<T: D>(_: T) -> T.E { fatalError() }

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getF(otherV) // Ok `E` doesn't have a `where` clause

func getSelf<T: B>(_: T) -> T { fatalError() } // expected-note {{found this candidate}}
func getSelf<T: D>(_: T) -> T { fatalError() } // expected-note {{found this candidate}}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getSelf(v) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getSelf(v) as any B // Ok
_ = getSelf(otherV) as any B & D // expected-error {{ambiguous use of 'getSelf'}}

func getBDSelf<T: D>(_: T) -> T { fatalError() }

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getBDSelf(otherV) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
_ = getBDSelf(otherV) as any B & D // Ok

func getP<T: P>(_: T) {}

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
getP(getC(v)) // Ok
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
getP(v.getC()) // Ok

// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
getP((getC(v) as any P)) // Ok - parens avoid opening suppression
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
getP((v.getC() as any P)) // Ok - parens avoid opening suppression
}

Expand Down Expand Up @@ -417,9 +476,25 @@ struct S<T, U> {
}
}

func nestedMetatypeCallee<T>(_ t: T) {}
do {
func nestedMetatypeCallee<T>(_ t: T) {}

func nestedMetatypeCaller() {
let t = String.Type.Type.Type.self as (any Q.Type.Type.Type.Type)
// CHECK-NOT: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
nestedMetatypeCallee(t)
}

do {
protocol P {}

func foo<T: P>(_ m: inout T.Type) {}

// expected-note@+1 {{change 'let' to 'var' to make it mutable}}
let rValueP: P.Type
var lValueP: P.Type

// expected-error@+1 {{cannot pass immutable value as inout argument: 'rValueP' is a 'let' constant}}
foo(&rValueP)
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
foo(&lValueP)
}