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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-522][SR-629][SR-4161] Allow covariance in protocol conformance. #8718
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1038,9 +1038,13 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2, | |
|
||
increaseScore(ScoreKind::SK_FunctionConversion); | ||
|
||
// Input types can be contravariant (or equal). | ||
SolutionKind result = matchTypes(func2->getInput(), func1->getInput(), | ||
subKind, subflags, | ||
// Input types can be contravariant (or equal), | ||
// but witness constranit might be covarian (or equal). | ||
bool isWitnessConstraint = locator.last() && | ||
locator.last()->getKind() == ConstraintLocator::Witness; | ||
Type type1 = isWitnessConstraint ? func1->getInput() : func2->getInput(); | ||
Type type2 = isWitnessConstraint ? func2->getInput() : func1->getInput(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The caller should instead call matchTypes() with the right order, instead of flipping them here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 馃憤 |
||
SolutionKind result = matchTypes(type1, type2, subKind, subflags, | ||
locator.withPathElement( | ||
ConstraintLocator::FunctionArgument)); | ||
if (result == SolutionKind::Error) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1170,9 +1170,8 @@ matchWitness(TypeChecker &tc, | |
|
||
// Initialized by the setup operation. | ||
Optional<ConstraintSystem> cs; | ||
ConstraintLocator *locator = nullptr; | ||
ConstraintLocator *reqLocator = nullptr; | ||
ConstraintLocator *witnessLocator = nullptr; | ||
ConstraintLocator *locator = nullptr; | ||
Type witnessType, openWitnessType; | ||
Type openedFullWitnessType; | ||
Type reqType, openedFullReqType; | ||
|
@@ -1225,19 +1224,17 @@ matchWitness(TypeChecker &tc, | |
|
||
// Open up the witness type. | ||
witnessType = witness->getInterfaceType(); | ||
// FIXME: witness as a base locator? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you remove the FIXME? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously consranits were added with nullptr locator, I've changed it to witness locator, I thought that's what FIXME was about, am I wrong here? locator = cs->getConstraintLocator(nullptr);
...
cs->addConstraint(ConstraintKind::Equal, reqType, witnessType, locator); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see. I would make the change in an earlier patch so we can test it independently. I think @DougGregor should look at this since I don't understand why we need the locator here. |
||
locator = cs->getConstraintLocator(nullptr); | ||
witnessLocator = cs->getConstraintLocator( | ||
static_cast<Expr *>(nullptr), | ||
LocatorPathElt(ConstraintLocator::Witness, witness)); | ||
locator = cs->getConstraintLocator( | ||
static_cast<Expr *>(nullptr), | ||
LocatorPathElt(ConstraintLocator::Witness, witness)); | ||
OpenedTypeMap witnessReplacements; | ||
if (witness->getDeclContext()->isTypeContext()) { | ||
std::tie(openedFullWitnessType, openWitnessType) | ||
= cs->getTypeOfMemberReference(selfTy, witness, dc, | ||
/*isTypeReference=*/false, | ||
/*isDynamicResult=*/false, | ||
FunctionRefKind::DoubleApply, | ||
witnessLocator, | ||
locator, | ||
/*base=*/nullptr, | ||
&witnessReplacements); | ||
} else { | ||
|
@@ -1246,7 +1243,7 @@ matchWitness(TypeChecker &tc, | |
/*isTypeReference=*/false, | ||
/*isSpecialized=*/false, | ||
FunctionRefKind::DoubleApply, | ||
witnessLocator, | ||
locator, | ||
/*base=*/nullptr); | ||
} | ||
openWitnessType = openWitnessType->getRValueType(); | ||
|
@@ -1255,9 +1252,16 @@ matchWitness(TypeChecker &tc, | |
}; | ||
|
||
// Match a type in the requirement to a type in the witness. | ||
auto matchTypes = [&](Type reqType, Type witnessType) | ||
auto matchTypes = [&](Type reqType, Type witnessType) | ||
-> Optional<RequirementMatch> { | ||
cs->addConstraint(ConstraintKind::Equal, reqType, witnessType, locator); | ||
ConstraintKind kind = ConstraintKind::Conversion; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'Conversion' is too general, because there are conversions that SILGenPoly cannot express. At most this should be 'Subtype'. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, probably that's way I'm getting an assert in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What was the assert you were getting? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (auto func = dyn_cast<FuncDecl>(req)) { | ||
auto name = req->getFullName().getBaseName(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this perhaps be |
||
if (func->isOperator() && name.str() == "==") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this restriction specifically in place? Going by name alone isn't going to only pick out Equatable requirements. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is bunch of errors specifically to I guess you are right, but I coudn't come up with a failing case. Probably we could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like that's your answer then: Variance constraints inside protocols with typealiases or generic requirements in general are going to cause inconsistencies. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, please add a test so we can take a look. Once we figure out the problem in more detail, we need to solve it in a general way -- special-casing Equatable is not going to work (imagine if the user defines their own protocol with a similar property). |
||
kind = ConstraintKind::Equal; | ||
} | ||
} | ||
cs->addConstraint(kind, witnessType, reqType, locator); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you change the order of arguments in the constraint here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because relaxing In this example: protocol P {
var a: Int? { get }
}
struct S: P {
var a: Int
} Constraint system would try to match In contrast to function type convertion: func f() -> Int { return 3 }
let g : ()->Int? = f Constraint system would try to match There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh. So I think the problem is for function arguments, we need to match the witness against the requirement; for results, we need to match the requirement against the witness. This should give you the correct variance and eliminate the need to swap the arguments in matchTypes() above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :-/ Why? I thought to get covariant behaviour we should always match witness agains requirement. Also,
but can they? Seems there is some inconsistency: func subtypeArgument1(_ x: (_ fn: ((String) -> Int)) -> Int) { }
func testSubtypeArgument1(x2: (_ fn: ((String) throws -> Int)) -> Int) {
subtypeArgument1(x2)
} This works fine. func subtypeArgument1(_ x:(Int) -> Int) {}
func testSubtypeArgument1(x2: (Int) throws -> Int) {
subtypeArgument1(x2)
} This gives an error:
Is this behavior intended and should we persist it? That's why I've used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The second example is correct. Passing a throwing function as a non-throwing function does not work, because the caller does not expect it to throw. If the function does throw (and we admitted this conversion) you would encounter undefined behavior. Think of |
||
// FIXME: Check whether this has already failed. | ||
return None; | ||
}; | ||
|
@@ -1286,7 +1290,7 @@ matchWitness(TypeChecker &tc, | |
// Compute the set of substitutions we'll need for the witness. | ||
solution->computeSubstitutions( | ||
witness->getInnermostDeclContext()->getGenericSignatureOfContext(), | ||
witnessLocator, | ||
locator, | ||
result.WitnessSubstitutions); | ||
|
||
return result; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// RUN: %target-typecheck-verify-swift | ||
|
||
class A {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please put this test in test/decl/protocol, and add some error cases as well. Also you need a SILGen test and an executable test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 馃憤 |
||
class B: A {} | ||
|
||
protocol P { | ||
func f1(o: Int?) | ||
func f2() -> Int? | ||
} | ||
|
||
protocol P1 { | ||
var a: A { get set } | ||
func f() -> A | ||
} | ||
|
||
struct S : P1 { | ||
func f() -> B { | ||
return B() | ||
} | ||
var a = B() | ||
} | ||
|
||
extension S: P { | ||
func f1(o: Int) { | ||
print(o) | ||
} | ||
func f2() -> Int { | ||
return 1 | ||
} | ||
} | ||
|
||
class C<G: A>: P1 { | ||
func f() -> G { | ||
return B() as! G | ||
} | ||
var a: G = B() as! G | ||
} | ||
|
||
|
||
protocol P2 { | ||
func f(_:(Int?)->()) | ||
} | ||
struct X : P2 { | ||
func f(_:(Int)->()) {} | ||
} | ||
|
||
protocol Q { | ||
var i: Int? { get } | ||
} | ||
struct Y : Q { | ||
var i: Int = 0 | ||
} | ||
|
||
protocol A1 {} | ||
protocol B2 {} | ||
|
||
protocol HasA1 { | ||
var a: A1 { get } | ||
} | ||
|
||
struct Baz: HasA1 { | ||
var a: A1 & B2 | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not correct -- why are you ending up here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert
is firing in this function, because requirement isOptional
, and witness isn't.I'd love to avoid ending up here :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the input is optional and the output is not, you're forcing off a level of optionality. Maybe something is going in the wrong direction?
Also in general,
Optional<T>
is not bitwise-castable toT
-- this only holds for classes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably, anyway there's more ambiguity issues after switching constraint to
Subtype
, it will take me some time to figure this out, I'll make an update when ready.