#Help me understand what's wrong with my auto-generated typespec

3 messages · Page 1 of 1 (latest)

ivory siren
#

Posted on elixir forums as well with more details: https://elixirforum.com/t/what-is-wrong-with-my-auto-generated-typespec/54344

Basically I don't get what's wrong with it. I'm auto-generating the definitions like this:

ts = args
|> Enum.map(&{String.to_atom(&1.name), ExOpenAI.type_to_spec(&1.type)})
|> Enum.reduce(fn item, acc -> {:|, [], [item, acc]} end)

Generating 2 types, one hard-coded, one through my generated above:

      @type test_1 :: unquote(ts)
      @type test_2 :: {:user, String.t()} | {:top_p, float()} | {:temperature, float()}

Then using it in a function @spec definition like usual with [test_1] or [test_2].

Checking with t:

iex(65)> t MyMod.test_1
@type test_1() ::
        {:user, String.t()}
        | {:top_p, float()}
        | {:temperature, float()}
        | {:stream, boolean()}
        | {:stop, any()}
        | {:presence_penalty, float()}
        | {:n, integer()}
        | {:max_tokens, integer()}
        | {:logit_bias, map()}
        | {:frequency_penalty, float()}

iex(65)> t MyMod.test_2
@type test_2() ::
        {:user, String.t()} | {:top_p, float()} | {:temperature, float()}

So far so good, they look identical to me, with test_1 being longer

However, dialyzer/LSP are not catching the spec violation when using type_1, but are reporting incorrect usage (as they should) with type_2.

arctic ravine
#

Show your usage of those types in the code

ivory siren
#

this is a bit out of context, but:

      # test_1 in my example above, dynamically generated and then unquoted
      @type unquote(name |> Macro.var(nil)) :: unquote(optional_args)

      # test_2 in my example above, manually defined
      @type unquote((Atom.to_string(name) <> "_wanted") |> String.to_atom() |> Macro.var(nil)) ::
              {:user, String.t()} | {:top_p, float()} | {:temperature, float()}

      # actually using the types in func spec here
      @spec unquote(name)(unquote_splicing(spec), [
              unquote(Atom.to_string(name) |> String.to_atom() |> Macro.var(nil))
            ]) ::
              {:ok, unquote(response_spec)} | {:error, any()}

I created those 2 types very ugly just so I could try back and forth between the 2, but the gist is

# pattern 1 - doesn't work with dialyzer, uses generated spec
@spec unquote(name)(unquote_splicing(spec), [
              unquote(Atom.to_string(name) |> String.to_atom() |> Macro.var(nil))
            ])

or

# pattern 2 - works with dialyzer, manually defined spec
@spec unquote(name)(unquote_splicing(spec), [
              unquote(Atom.to_string(name) <> "_wanted" |> String.to_atom() |> Macro.var(nil))
            ])