-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Turn type inference up to 1000 #14145
base: main
Are you sure you want to change the base?
Conversation
What a great start to 2025 José. Will expression typing still work if an operator is implemented in a user function? |
Remote functions outside of the stdlib won't be used during inference but they will be used during type checking. In very simplified terms, type inference is how callers perceive a function. Type checking is how a function perceives itself. So imagine you write this code:
Assuming In other words, we will find bugs within EDIT: I believe we can improve this on Elixir v1.19 to perform inference using all of your app/package dependencies. |
@@ -290,9 +347,61 @@ defmodule Module.Types.ExprTest do | |||
<<x::integer>> | |||
""" | |||
end | |||
|
|||
test "requires all combinations to be compatible" do |
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.
Isn't there a risk of this being too restrictive and refusing valid dynamic programs?
I would have imagined inferring the dynamic union of string or charlist
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.
That's a very good question. So far our decisions around syntax constructs is that they will behave the same on both static and dynamic. In other words, we don't want type checking to behave differently depending on some flag, because that will only introduce confusion.
In a static program, the behaviour above is the better choice, as it reveals the possibility of a runtime error (a static program tells us about errors that can happen, even if they don't in practice), which would be a false warning in existing code base. I would say that's a fine compromise for two reasons:
-
You can move the function inside the conditional:
if some_condition?, do: String.to_integer(arg), else: List.to_integer(arg)
-
You can use
apply
, which this PR changes to make it always dynamic
Another possibility is for us to introduce a function, dynamic
, that converts a type into dynamic, so you could do: dynamic(mod).to_integer(arg)
but we so far make all function dispatch the same, regardless if the module we are being called is dynamic or not, because stuff like checking for undefined functions, deprecations, etc, should happen regardless if the mod is dynamic or not.
Furthermore modules are defined at compile-time, which means we can often check and assert these properties at runtime rather than compile-time (generally preferable).
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.
Makes perfect sense, thank you for the detailed explanation ! 💜
Prior to this PR, we performed type inference only in patterns. This pull requests adds type inference to most expressions in Elixir, including function calls, paving the way for us to add inference of guards.
The best way to show this is with an example. The following code:
Will automatically infer that x is a map, which has at least the
.foo
and.bar
keys, where their values must be integers or floats. The return type is either an integer or a float.There are about 10 TODOs I must tackle before merging this.