asummers / erlex

Convert Erlang style structs and error messages to equivalent Elixir.
Other
34 stars 16 forks source link

Lowercase Type Variables and Fix Underscore #42

Closed jeremyjh closed 5 years ago

jeremyjh commented 5 years ago

Follow-up to this discussion.

In Erlang and Elixir syntax, the names used in a spec when clause are variables - type variables. So in Erlang they must follow Erlang's variable syntax:

Variables start with an uppercase letter or underscore (_). Variables can contain alphanumeric characters, underscore and @.

In Elixir variables must begin with a lower case letter or underscore, and can contain upper/lower case letters, numbers and underscores.

So if we want Erlex pretty printed Erlang contracts to compile in Elixir, we need to transform legal Erlang type variable names to legal Elixir type variable names.

This PR helps us do that. It will lowercase the first character of an uppercase variable name used in when clauses. It also fixes a bug I introduced in the last PR that caused an exception on type variables including underscores, since those don't get lexed as a legal charlist and I was just calling to_string() on it.

But now the original example code:

  def bad_func do
    Atom.to_string("not a string")
  end

Will give the error referencing the Erlang spec:

%% atom_to_binary/2
-spec atom_to_binary(Atom, Encoding) -> binary() when
      Atom :: atom(),
      Encoding :: latin1 | unicode | utf8.

as:

lib/example.ex:8:call
The function call will not succeed.

:erlang.atom_to_binary("not a string", :utf8)

breaks the contract
(atom, encoding) :: binary() when atom: atom(), encoding: :latin1 | :unicode | :utf8

We can copy paste that contract into our own Elixir module, and it compiles and works with dialyzer.

defmodule Example do
  @spec to_string(atom, encoding) :: binary()  when atom: atom(), encoding: :latin1 | :utf8
  def to_string(atom, encoding) do
    :erlang.atom_to_binary(atom, encoding)
  end

  def bad_func do
    __MODULE__.to_string("not a string", :utf8)
  end
end

Gives:

lib/example.ex:8:call
The function call will not succeed.

Example.to_string("not a string", :utf8)

will never return since the success typing is:
(atom(), :latin1 | :unicode | :utf8) :: binary()

and the contract is
(atom, encoding) :: binary() when atom: atom(), encoding: :latin1 | :utf8

One possible issue still outstanding is that an Erlang variable could contain an ampersand. Currently that throws an error in the parser, I'm not sure why though.

codecov[bot] commented 5 years ago

Codecov Report

Merging #42 into master will increase coverage by 0.19%. The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #42      +/-   ##
==========================================
+ Coverage   85.81%   86.01%   +0.19%     
==========================================
  Files           1        1              
  Lines         141      143       +2     
==========================================
+ Hits          121      123       +2     
  Misses         20       20
Impacted Files Coverage Δ
lib/erlex.ex 86.01% <100%> (+0.19%) :arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update f5b2a4d...5fba426. Read the comment docs.

jeremyjh commented 5 years ago

Bump.

If you don't want the PR we should at least cherry-pick this bug fix: https://github.com/asummers/erlex/pull/42/files#diff-314c048f5422aeb5c6083de98d4b4404R540

Currently type variables with underscores in when clauses will throw an exception.

asummers commented 5 years ago

This seems reasonable! Totally missed this PR, sorry about that. I'll push out a release with these changes tonight.

jeremyjh commented 5 years ago

@asummers just a reminder to please cut a hex release when you can; I'm waiting on it for another dialyxir rc

asummers commented 5 years ago

Done.