-
Notifications
You must be signed in to change notification settings - Fork 10.6k
[Sema] Fix compiler error when extending a typealias of a partially specialized generic type #73169
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
base: main
Are you sure you want to change the base?
Conversation
|
@swift-ci please smoke test Linux |
|
i was curious to see how this was solved and guess I was overcomplicating the issue 😭 |
|
This is far from solved, FWIW 🙂. I only triggered the tests to encourage the author to iterate on failures before any feedback arrives. |
|
Is it possible to make this PR as a draft? |
|
I was converting to draft but Anthony was faster |
|
For posterity, the convert to draft button is at the bottom of the Reviewers section. |
|
@AnthonyLatsis |
|
@AnthonyLatsis reminder |
|
You once said a ping every two weeks is fine. So here I am pinging you @AnthonyLatsis |
AnthonyLatsis
left a comment
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.
Thank you for the reminders and the patience. Another somewhat random test case I think worth having:
struct S1<T> {
struct Inner<U> {}
}
struct S2<T> {
typealias A1<U> = S1<T>.Inner<U>
typealias A2<U> = S1<T>.Inner<U> where T == Int
}
extension S2<Int>.A1 {
func foo1() {
let int: Int
let _: T = int // OK
}
}
extension S2.A2 {
func foo2() {
let int: Int
let _: T = int // OK
}
}
lib/Sema/TypeCheckDecl.cpp
Outdated
| if (!typealiasBoundGenericType->hasTypeParameter()) { | ||
| isInferredType = true; | ||
| } else { | ||
| isInferredType = false; | ||
| break; | ||
| } |
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 we remove isInferredType, this can be
| if (!typealiasBoundGenericType->hasTypeParameter()) { | |
| isInferredType = true; | |
| } else { | |
| isInferredType = false; | |
| break; | |
| } | |
| if (typealiasBoundGenericType->hasTypeParameter()) { | |
| return false; | |
| } |
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.
I removed isInferredType. I dislike the new solution because I have to write return true at the end of the function and I would like to have return false as default.
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.
I prefer returns over writing to a variable because they are generally more robust and help to streamline the flow. Do you find it counterintuitive to return true at the end here? You could invert the name of the function, but probably not worth it right now; we need to come up with a more descriptive name.
| if (!std::equal(nominalGenericParams.begin(), nominalGenericParams.end(), | ||
| typealiasGenericParams.begin(), | ||
| [](GenericTypeParamType *gp1, GenericTypeParamType *gp2) { | ||
| return gp1->isEqual(gp2); | ||
| })) { | ||
| return false; | ||
| } |
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.
We need to perform this check regardless of whether the original number of generic params matched.
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.
Confused here, added a comment above about this
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.
The comment: #73169 (comment)
So the goal is to relax the fall-through type alias criteria a little bit by allowing a mismatch in the number of generic parameters at the greatest depth, but only if that difference is due to some of those generic parameters being bound to fully concrete types. As for other depths, the current rules still apply. For example, <A><B,C>... vs. <A, B><C>... or <A, B, C>... vs. <A, B>... must not meet the criteria regardless of the nature of .... I.e., we should still return early if the size check or the std::equal check fail for what remains after we shave off max depth elements.
Could you rebase (I have renamed isGeneric to hasGenericParamList) and see if the test in test/decl and test/type pass with the current change? I would expect some of them to fail. If not, we should get this case covered.
lib/Sema/TypeCheckDecl.cpp
Outdated
| for (const auto &type : nominalGenericParams) { | ||
| if (type->getDepth() == maxDepth) { | ||
| nominalGenericParams = nominalGenericParams.drop_back(); | ||
| } | ||
| } |
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.
I like how this appears to be unsafe but is actually safe because we are trimming a non-owning view into the array. But this is probably better:
| for (const auto &type : nominalGenericParams) { | |
| if (type->getDepth() == maxDepth) { | |
| nominalGenericParams = nominalGenericParams.drop_back(); | |
| } | |
| } | |
| while (!nominalGenericParams.empty() && | |
| nominalGenericParams.back().getDepth() == maxDepth) { | |
| nominalGenericParams = nominalGenericParams.drop_back(); | |
| } |
Test case for why we need !nominalGenericParams.empty():
struct S1<T> {
struct Inner<U> {}
}
struct S2 {
typealias A<T> = S1<T>.Inner<Int>
}
extension S2.A {
func test() {
let int: Int
let _: U = int // error
}
}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.
I add the test case and changed it to while.
The test does not produce an error.
So the nested struct parameter becomes Int. Then it checks for U which is Int now. Why is it supposed to produce an error?
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.
I think this passes now because you moved the std::equal check into the if. I don’t think there is anything inherently wrong with this case, but it belongs to a larger family of cases, and supporting it properly will require more work.
…ypealiases.swift, decl/nested/type_in_type.swift, stdlib/Filter.swift, stdlib/Map.swift
|
@AnthonyLatsis |
|
@AnthonyLatsis reminder |
|
@AnthonyLatsis |
|
@AnthonyLatsis another reminder |
|
@slavapestov Could you take a look at this too? |
|
@AnthonyLatsis |
|
@AnthonyLatsis could you have a look? |
|
@AnthonyLatsis @slavapestov |
|
swift typecheckdecl isGeneric seems to have changed since last time I worked on it. Can be reviewed either way though |
Resolves #68212.