Skip to content
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

Perform return type inference and application across local calls #13984

Merged
merged 22 commits into from
Nov 11, 2024

Conversation

josevalim
Copy link
Member

WIP

@josevalim josevalim merged commit cb2e036 into main Nov 11, 2024
24 checks passed
@josevalim josevalim deleted the jv-local-inference branch November 11, 2024 22:06
@josevalim
Copy link
Member Author

💚 💙 💜 💛 ❤️

def_expr = {kind, meta, [guards_to_expr(guards, {fun, [], args}), [do: body]]}

exception =
RuntimeError.exception("""
found error while checking types for #{Exception.format_mfa(module, fun, length(args))}:
found error while checking types for #{Exception.format_mfa(stack.module, fun, length(args))}:

#{Exception.format_banner(:error, e, stack)}\
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be Exception.format_banner(:error, e, trace) ? Dialyzer is warning about that:

The call 'Elixir.Exception':format_banner
         ('error',
          _e@1 ::
              #{'__exception__' := 'true',
                '__struct__' := atom(),
                atom() => _},
          _stack@1 ::
              #{'function' := {_, _},
                'mode' := 'dynamic' | 'infer' | 'traversal',
                _ => _}) breaks the contract 
          (kind(), any(), stacktrace()) -> 'Elixir.String':t()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the report!

I tried to prove that we could catch this error too. Check this out. If I do this diff:

--- a/lib/elixir/lib/exception.ex
+++ b/lib/elixir/lib/exception.ex
@@ -131,7 +131,7 @@ def normalize(_kind, payload, _stacktrace), do: payload
   @spec format_banner(kind, any, stacktrace) :: String.t()
   def format_banner(kind, exception, stacktrace \\ [])

-  def format_banner(:error, exception, stacktrace) do
+  def format_banner(:error, exception, [_ | _] = stacktrace) do
     exception = normalize(:error, exception, stacktrace)
     "** (" <> inspect(exception.__struct__) <> ") " <> message(exception)
   end
diff --git a/lib/elixir/lib/module/types.ex b/lib/elixir/lib/module/types.ex
index acfb2f7d6..6976e6579 100644
--- a/lib/elixir/lib/module/types.ex
+++ b/lib/elixir/lib/module/types.ex
@@ -277,7 +277,7 @@ defp with_file_meta(stack, meta) do
     end
   end

-  defp internal_error!(e, trace, kind, meta, fun, args, guards, body, stack) do
+  defp internal_error!(e, trace, kind, meta, fun, args, guards, body, %{} = stack) do
     def_expr = {kind, meta, [guards_to_expr(guards, {fun, [], args}), [do: body]]}

     exception =

I get this report:


     warning: incompatible types given to Exception.format_banner/3:

         Exception.format_banner(:error, e, stack)

     given types:

         :error, dynamic(), dynamic(%{...})

     but expected one of:

         #1
         dynamic(:error), dynamic(), dynamic(non_empty_list(term(), term()))

         #2
         dynamic(:throw), dynamic(), dynamic()

         #3
         dynamic(:exit), dynamic(), dynamic()

         #4
         dynamic({:EXIT, term()}), dynamic(), dynamic()

     where "e" was given the type:

         # type: dynamic()
         # from: lib/elixir/lib/module/types.ex:280:24
         e

     where "stack" was given the type:

         # type: dynamic(%{...})
         # from: lib/elixir/lib/module/types.ex:280:75
         %{} = stack

     typing violation found at:
     │
 287 │       #{Exception.format_banner(:error, e, stack)}\
     │                   ~
     │
     └─ (elixir 1.18.0-dev) lib/elixir/lib/module/types.ex:287:19: Module.Types.internal_error!/9

Which is nice!

I think however Dialyzer is typechecking across the function boundaries, because that's the only way it could know that stack has mode :dynamic | :infer | :traversal. I will play with some of the ideas later. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants