esl / gradient

Gradient is a static typechecker for Elixir
Apache License 2.0
436 stars 13 forks source link

Unclear cyclic dependency warning #37

Open eksperimental opened 2 years ago

eksperimental commented 2 years ago

Running Gradient in this project. https://github.com/eksperimental/beam_meta/tree/1de809c6266d9ee877354f8c2b6dd32adc7d4bf9

lib/beam_meta/compatibility/otp_elixir.ex: The pattern %{version: elixir_version, version_requirement: elixir_requirement, otp_versions: otp_versions} on line 138 doesn't have the type any()

lib/beam_meta/compatibility/otp_elixir.ex: The type spec bounded_fun((deep_list -> list()), [{:type, 0, :constraint, [{:atom, 0, :is_subtype}, [{:var, 0, :deep_list}, {:type, 0, :list, [{:type, 0, :union, [{:type, 0, :any, []}, {:var, 0, :deep_list}]}]}]]}]) has a cyclic dependency in variable deep_list

I assume these two are unrelated. If they are, the second warning gives no clue where this happens.

erszcz commented 2 years ago

Thanks for the report, @eksperimental :+1: Both of these need some research on our side.

simonprev commented 2 years ago

I also have many reports like:

The pattern %{context: %{current_user: current_user}} on line 57 doesn't have the type any()

It seems that when we use pattern matching on a function without a @spec (like every private functions), the default any() raises an error for our "typed" map in the pattern.

I thought that the pattern match would "define" a typing like in TypeScript:

image
erszcz commented 2 years ago

@simonprev Indeed, your diagnosis is correct. Thanks for looking into it :+1:

I thought that the pattern match would "define" a typing like in TypeScript.

I think that's a reasonable path forward - we're just not there yet :)

We've run into this problem in another issue, let me quote my comment also here:

lib/project/auth.ex: The pattern %Project.Accounts.User{role: "admin"} on line 23 doesn't have the type any()

These stem from functions with no specs using pattern matching in their heads. In other words, Gradient assumes argument type any() if there's no spec. The function matches on %User{role: "admin"} as one of the params - this obviously isn't of type any(). Therefore, we get the above warning. This is a common pattern both in Erlang and Elixir, so I imagine inference of such more specific specs would be handy, but currently the only way to get rid of them is to add specs to such functions.

eksperimental commented 2 years ago

I have investigated it and tracked down what is causing this, it is reported in #129.