whatyouhide/corsica

Dialyzer errors with new opaque typespecs

doorgan opened this issue · 1 comments

It seems the commit a2f7128 introduced new options structs and typespecs. Unfortunately, dialyzer doesn't seem to be happy about it

In a module with just the following code:

defmodule MyAppWeb.CORS do
  use Corsica.Router

  resource("/path/*", origins: "*")
end

I'm getting the following dialyzer error reports:

lib/my_app_web/cors.ex:1:call_without_opaque
Function call without opaqueness type mismatch.

Call does not have expected term of type Keyword.t() | Corsica.sanitized_options() (with opaque subterms) in the 2nd position.

Corsica.put_cors_simple_resp_headers(
  _ :: %Plug.Conn{
    :adapter => {atom(), _},
    :assigns => %{atom() => _},
    :body_params => %Plug.Conn.Unfetched{:aspect => atom(), binary() => _},
    :cookies => %Plug.Conn.Unfetched{:aspect => atom(), binary() => _},
    :halted => boolean(),
    :host => binary(),
    :method => binary(),
    :owner => pid(),
    :params => %Plug.Conn.Unfetched{:aspect => atom(), binary() => _},
    :path_info => [binary()],
    :path_params => %{
      binary() =>
        binary()
        | [binary() | [any()] | %{binary() => _}]
        | %{binary() => binary() | [any()] | %{binary() => _}}
    },
    :port => char(),
    :private => %{atom() => _},
    :query_params => %Plug.Conn.Unfetched{
      :aspect => atom(),
      binary() =>
        binary()
        | [binary() | [any()] | %{binary() => _}]
        | %{binary() => binary() | [any()] | %{binary() => _}}
    },
    :query_string => binary(),
    :remote_ip =>
      {byte(), byte(), byte(), byte()}
      | {char(), char(), char(), char(), char(), char(), char(), char()},
    :req_cookies => %Plug.Conn.Unfetched{:aspect => atom(), binary() => binary()},
    :req_headers => [{binary(), binary()}],
    :request_path => binary(),
    :resp_body =>
      nil
      | binary()
      | maybe_improper_list(
          binary() | maybe_improper_list(any(), binary() | []) | byte(),
          binary() | []
        ),
    :resp_cookies => %{binary() => map()},
    :resp_headers => [{binary(), binary()}],
    :scheme => :http | :https,
    :script_name => [binary()],
    :secret_key_base => nil | binary(),
    :state =>
      :chunked | :file | :sent | :set | :set_chunked | :set_file | :unset | :upgraded,
    :status => nil | non_neg_integer()
  },
  %Corsica.Options{
    :allow_credentials => false,
    :allow_headers => [],
    :allow_methods => [<<_::24, _::size(8)>>, ...],
    :allow_private_network => false,
    :expose_headers => nil,
    :max_age => nil,
    :origins => <<_::8>>,
    :passthrough_non_cors_requests => false,
    :telemetry_metadata => %{}
  }
)

________________________________________________________________________________
lib/my_app_web/cors.ex:1:call_without_opaque
Function call without opaqueness type mismatch.

Call does not have expected term of type Keyword.t() | Corsica.sanitized_options() (with opaque subterms) in the 2nd position.

Corsica.send_preflight_resp(
  _ :: %Plug.Conn{
    :adapter => {atom(), _},
    :assigns => %{atom() => _},
    :body_params => %Plug.Conn.Unfetched{:aspect => atom(), binary() => _},
    :cookies => %Plug.Conn.Unfetched{:aspect => atom(), binary() => _},
    :halted => boolean(),
    :host => binary(),
    :method => binary(),
    :owner => pid(),
    :params => %Plug.Conn.Unfetched{:aspect => atom(), binary() => _},
    :path_info => [binary()],
    :path_params => %{
      binary() =>
        binary()
        | [binary() | [any()] | %{binary() => _}]
        | %{binary() => binary() | [any()] | %{binary() => _}}
    },
    :port => char(),
    :private => %{atom() => _},
    :query_params => %Plug.Conn.Unfetched{
      :aspect => atom(),
      binary() =>
        binary()
        | [binary() | [any()] | %{binary() => _}]
        | %{binary() => binary() | [any()] | %{binary() => _}}
    },
    :query_string => binary(),
    :remote_ip =>
      {byte(), byte(), byte(), byte()}
      | {char(), char(), char(), char(), char(), char(), char(), char()},
    :req_cookies => %Plug.Conn.Unfetched{:aspect => atom(), binary() => binary()},
    :req_headers => [{binary(), binary()}],
    :request_path => binary(),
    :resp_body =>
      nil
      | binary()
      | maybe_improper_list(
          binary() | maybe_improper_list(any(), binary() | []) | byte(),
          binary() | []
        ),
    :resp_cookies => %{binary() => map()},
    :resp_headers => [{binary(), binary()}],
    :scheme => :http | :https,
    :script_name => [binary()],
    :secret_key_base => nil | binary(),
    :state =>
      :chunked | :file | :sent | :set | :set_chunked | :set_file | :unset | :upgraded,
    :status => nil | non_neg_integer()
  },
  %Corsica.Options{
    :allow_credentials => false,
    :allow_headers => [],
    :allow_methods => [<<_::24, _::size(8)>>, ...],
    :allow_private_network => false,
    :expose_headers => nil,
    :max_age => nil,
    :origins => <<_::8>>,
    :passthrough_non_cors_requests => false,
    :telemetry_metadata => %{}
  }
)

From what I understand by briefly looking at the report and following the code, the line

use Corsica.Router

is injecting code that makes use of the Options struct in these two places:

Corsica.send_preflight_resp(conn, unquote(corsica_opts))

Corsica.put_cors_simple_resp_headers(conn, unquote(corsica_opts))

I haven't worked with opaque types and macros enough to suggest a solution or PR but hopefully this is enough context :D

Nice find! Closed by 1560dcc. 🙃