-
Notifications
You must be signed in to change notification settings - Fork 35
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
Invalid polymorphic code accepted #549
Comments
Hi @erszcz and thanks for your investigation! Maybe I'm wrong but I think we have to stick to rank-1 polymorphism as we don't even have syntax for higher-rank polymorphism in the Erlang typespecs. To support rank-2 polymorphism, you would have to be able to specify the "scope" in which type variables are bound and quantified. For example, in System F, this is done via the for-all sign. Take for example these two types: But AFAIK in typespecs we don't have any way ho to specify the scopes in which type variables are bound and quantified, and thus all type variables are implicitly quantified over the whole term (i.e. rank-1). It is true that the runtime can handle higher-rank polymorphic functions. But I think this is true for any dynamically typed language. What do you think? I agree that the second snippet -spec poly_2(fun((A) -> A)) -> {integer(), integer()}.
poly_2(F) -> {F(42), F(84)}. should be rejected as the type variable poly_2 :: (a -> a) -> (Integer, Integer)
poly_2 f = (f 42, f 84) In the last snippet, I think the issue is again in the function Maybe I was needlessly explaining something you know well, I just wanted to be sure that we all have the same understanding of it. |
Hi, @xxdavid!
Good thinking. We're generally on the same page here. If we really, really wanted to work around the existing syntax, we could introduce some abomination like
I'm not sure if that's strictly necessary, but it would make sense. After all the caller could pass in a Gradualizer/src/typechecker.erl Line 3839 in 01fb022
Gradualizer/src/typechecker.erl Line 3875 in 01fb022
If we registered these constraints above, I think -spec poly_2(fun((A) -> A)) -> {integer(), integer()}.
poly_2(F) -> {F(42), F(84)}. would not, given the current constraint solving logic. The solver currently throws an error if constraints conflict. In case of
which do not conflict. These are not the only places where we don't registers/pass constraints, but these seem relevant to this bug. |
The following code is correctly rejected by Gradualizer:
However, the following code is not rejected:
while by itself it seems harmless. We could argue whether the above code is valid or not based on polymorphism rank we'd like to support (rank-1 - invalid, rank-2 - valid). However, an invalid application like the following should be rejected in any case, whereas it's not:
See WhatsApp/eqwalizer#33 for the original issue where this was spotted.
The text was updated successfully, but these errors were encountered: