├── .credo.exs ├── .gitignore ├── README.md ├── config └── config.exs ├── lib ├── ml_elixir.ex ├── ml_elixir │ ├── ml_macro.ex │ └── type_storage.ex ├── ml_elixir_old.ex ├── typed_elixir.ex └── typed_elixir │ ├── hm_env.ex │ └── type.ex ├── mix.exs ├── mix.lock ├── notes.txt ├── scratchpad.ex └── test ├── ml_elixir └── ml_module_test.exs ├── ml_elixir_test.exs ├── test_helper.exs └── typed_elixir_test.exs /.credo.exs: -------------------------------------------------------------------------------- 1 | # This file contains the configuration for Credo and you are probably reading 2 | # this after creating it with `mix credo.gen.config`. 3 | # 4 | # If you find anything wrong or unclear in this file, please report an 5 | # issue on GitHub: https://github.com/rrrene/credo/issues 6 | # 7 | %{ 8 | # 9 | # You can have as many configs as you like in the `configs:` field. 10 | configs: [ 11 | %{ 12 | # 13 | # Run any config using `mix credo -C `. If no config name is given 14 | # "default" is used. 15 | name: "default", 16 | # 17 | # these are the files included in the analysis 18 | files: %{ 19 | # 20 | # you can give explicit globs or simply directories 21 | # in the latter case `**/*.{ex,exs}` will be used 22 | included: ["lib/", "src/", "web/", "apps/"], 23 | excluded: [~r"/_build/", ~r"/deps/"] 24 | }, 25 | # 26 | # If you create your own checks, you must specify the source files for 27 | # them here, so they can be loaded by Credo before running the analysis. 28 | requires: [], 29 | # 30 | # Credo automatically checks for updates, like e.g. Hex does. 31 | # You can disable this behaviour below: 32 | check_for_updates: true, 33 | # 34 | # You can customize the parameters of any check by adding a second element 35 | # to the tuple. 36 | # 37 | # To disable a check put `false` as second element: 38 | # 39 | # {Credo.Check.Design.DuplicatedCode, false} 40 | # 41 | checks: [ 42 | {Credo.Check.Consistency.ExceptionNames}, 43 | {Credo.Check.Consistency.LineEndings}, 44 | {Credo.Check.Consistency.SpaceAroundOperators}, 45 | {Credo.Check.Consistency.SpaceInParentheses}, 46 | {Credo.Check.Consistency.TabsOrSpaces}, 47 | 48 | # For some checks, like AliasUsage, you can only customize the priority 49 | # Priority values are: `low, normal, high, higher` 50 | {Credo.Check.Design.AliasUsage, priority: :low}, 51 | 52 | # For others you can set parameters 53 | 54 | # If you don't want the `setup` and `test` macro calls in ExUnit tests 55 | # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just 56 | # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. 57 | {Credo.Check.Design.DuplicatedCode, excluded_macros: []}, 58 | 59 | # You can also customize the exit_status of each check. 60 | # If you don't want TODO comments to cause `mix credo` to fail, just 61 | # set this value to 0 (zero). 62 | {Credo.Check.Design.TagTODO, exit_status: 2}, 63 | {Credo.Check.Design.TagFIXME}, 64 | 65 | {Credo.Check.Readability.FunctionNames}, 66 | {Credo.Check.Readability.LargeNumbers}, 67 | {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 120}, 68 | {Credo.Check.Readability.ModuleAttributeNames}, 69 | {Credo.Check.Readability.ModuleDoc}, 70 | {Credo.Check.Readability.ModuleNames}, 71 | {Credo.Check.Readability.ParenthesesInCondition}, 72 | {Credo.Check.Readability.PredicateFunctionNames}, 73 | {Credo.Check.Readability.TrailingBlankLine}, 74 | {Credo.Check.Readability.TrailingWhiteSpace}, 75 | {Credo.Check.Readability.VariableNames}, 76 | 77 | {Credo.Check.Refactor.ABCSize}, 78 | # {Credo.Check.Refactor.CaseTrivialMatches}, # deprecated in 0.4.0 79 | {Credo.Check.Refactor.CondStatements}, 80 | {Credo.Check.Refactor.FunctionArity}, 81 | {Credo.Check.Refactor.MatchInCondition}, 82 | {Credo.Check.Refactor.PipeChainStart}, 83 | {Credo.Check.Refactor.CyclomaticComplexity}, 84 | {Credo.Check.Refactor.NegatedConditionsInUnless}, 85 | {Credo.Check.Refactor.NegatedConditionsWithElse}, 86 | {Credo.Check.Refactor.Nesting}, 87 | {Credo.Check.Refactor.UnlessWithElse}, 88 | 89 | {Credo.Check.Warning.IExPry}, 90 | {Credo.Check.Warning.IoInspect, false}, 91 | {Credo.Check.Warning.NameRedeclarationByAssignment}, 92 | {Credo.Check.Warning.NameRedeclarationByCase}, 93 | {Credo.Check.Warning.NameRedeclarationByDef}, 94 | {Credo.Check.Warning.NameRedeclarationByFn}, 95 | {Credo.Check.Warning.OperationOnSameValues}, 96 | {Credo.Check.Warning.BoolOperationOnSameValues}, 97 | {Credo.Check.Warning.UnusedEnumOperation}, 98 | {Credo.Check.Warning.UnusedKeywordOperation}, 99 | {Credo.Check.Warning.UnusedListOperation}, 100 | {Credo.Check.Warning.UnusedStringOperation}, 101 | {Credo.Check.Warning.UnusedTupleOperation}, 102 | {Credo.Check.Warning.OperationWithConstantResult}, 103 | 104 | # Custom checks can be created using `mix credo.gen.check`. 105 | # 106 | ] 107 | } 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypedElixir 2 | 3 | TypedElixir defmodule replacement testing. 4 | 5 | PR's welcome and encouraged! 6 | 7 | 8 | ## Installation 9 | 10 | If [available in Hex](https://hex.pm/docs/publish) (not yet), the package can be installed as: 11 | 12 | 1. Add `typed_elixir` to your list of dependencies in `mix.exs`: 13 | 14 | ```elixir 15 | def deps do 16 | [{:typed_elixir, "~> 0.1.0"}] 17 | end 18 | ``` 19 | 20 | 2. Ensure `typed_elixir` is started before your application: 21 | 22 | ```elixir 23 | def application do 24 | [applications: [:typed_elixir]] 25 | end 26 | ``` 27 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :typed_elixir, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:typed_elixir, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/ml_elixir/ml_macro.ex: -------------------------------------------------------------------------------- 1 | import MLElixir 2 | defmlmodule MLElixir.MLMacro do 3 | 4 | type ast 5 | | none 6 | | one 7 | | integer integer 8 | | two 9 | | float float 10 | | float2 float 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/ml_elixir/type_storage.ex: -------------------------------------------------------------------------------- 1 | defmodule MLElixir.TypeStorage do 2 | @moduledoc """ 3 | """ 4 | 5 | @ets_table_name :mlelixir_typestorage 6 | 7 | def ets do 8 | case :global.whereis_name(@ets_table_name) do 9 | :undefined -> 10 | {:ok, task} = Task.start(fn -> receive do x -> x end; receive do x -> x end end) # receive ones for table transfer, then again to wait forever 11 | @ets_table_name = :ets.new(@ets_table_name, [:named_table, :public, :set, read_concurrency: true]) 12 | :ets.give_away(@ets_table_name, task, nil) 13 | :global.register_name(@ets_table_name, task) 14 | fill_defaults(@ets_table_name) 15 | @ets_table_name 16 | pid when is_pid(pid) -> @ets_table_name 17 | end 18 | end 19 | 20 | 21 | def fill_defaults(table) do 22 | define_type(table, Kernel, :+, {}) 23 | end 24 | 25 | def define_type(table \\ ets(), module, name, typedef) do 26 | :ets.insert_new(table, {{module, name}, typedef}) 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /lib/ml_elixir_old.ex: -------------------------------------------------------------------------------- 1 | defmodule MLElixirOld do 2 | @moduledoc """ 3 | Entirely new syntax within the elixir parser 4 | """ 5 | 6 | # import MLElixir, only: [defml: 1]; defml 42 7 | # import MLElixir, only: [defml: 1]; defml let a = 42 in a 8 | # import MLElixir, only: [defml: 1]; defml 1+2 9 | # import MLElixir, only: [defml: 1]; defml let a = 1 in a+2 10 | # import MLElixir, only: [defml: 1]; defml module TestML: [id = fn(x)->x end] 11 | # import MLElixir, only: [defml: 1]; defml let id x = x in module TestML: [x = x, y] 12 | 13 | 14 | defmodule MLEnv do 15 | defstruct counter: -1, funs: %{}, type_funs: %{}, type_bindings: %{}, type_vars: %{} 16 | end 17 | 18 | defp increment_counter(env) do 19 | %{env | counter: env.counter+1} 20 | end 21 | 22 | # Exceptions 23 | 24 | defmodule UnknownCall do 25 | defexception env: %MLEnv{}, name: "", args: [], meta: nil 26 | def message(e) do 27 | args = Enum.map(e.args, fn({_, meta, _}) -> elem(meta[:type], 0) end) 28 | line = if e.meta === nil, do: -1, else: e.meta[:line] || -1 29 | case e.env.type_funs[e.name] do 30 | nil -> "#{line}:Unknown Call of `#{e.name}` with args of #{inspect args} because the function is not known" 31 | heads -> 32 | choices = Enum.map(heads, fn(type) -> elem(type, 0) end) 33 | "#{line}:Unknown Call of `#{e.name}` with args of #{inspect args} in: #{inspect choices}" 34 | end 35 | end 36 | end 37 | 38 | defmodule UnknownVar do 39 | defexception message: "Unknown variable accessed", env: %MLEnv{}, name: "", scope: [], meta: nil 40 | def message(e) do 41 | line = if e.meta === nil, do: -1, else: e.meta[:line] || -1 42 | "#{line}:Access of unknown var `#{e.name}`" 43 | end 44 | end 45 | 46 | defmodule InvalidCall do 47 | defexception message: "Unknown reason", name: "", meta: nil 48 | def message(e) do 49 | line = if e.meta === nil, do: -1, else: e.meta[:line] || -1 50 | "#{line}:Invalid call of `#{e.name}` because of: #{e.message}" 51 | end 52 | end 53 | 54 | # TODO: Use the message arg around 55 | defmodule UnificationError do 56 | defexception message: "", env: %MLEnv{}, type0: nil, type1: nil 57 | def message(e) do 58 | "Unification error between `#{inspect e.type0}` and `#{inspect e.type1}` with message: #{e.message}" 59 | end 60 | end 61 | 62 | 63 | 64 | 65 | # Types 66 | # @type_const_ :"$$TCONST$$" # {@type_const, atom, meta} 67 | # @type_app :"$$TAPP$$" # {@type_app, type, [type], meta} 68 | # @type_func :"$$TFUNC$$" # {@type_func, [type], type, meta} 69 | # @type_var :"$$TVAR$$" # {@type_var, env.type_vars, meta} 70 | # 71 | # # env.type_vars 72 | # @type_var_unbound :"$$TVARUNBOUND$$" # {@type_var_unbound, string, int, meta} 73 | # @type_var_link :"$$TVARLINK$$" # {@type_var_link, type, meta} 74 | # @type_var_generic :"$$TVARGENERIC$$" # {@type_var_generic, string, meta} 75 | 76 | @tag_lit :"$$LIT$$" 77 | @tag_var :"$$VAR$$" 78 | @tag_let :"$$LET$$" 79 | @tag_mbind :"$$MULTIBIND$$" 80 | @tag_call :"$$CALL$$" 81 | @tag_func :"$$FUNC$$" 82 | @tag_func_head :"$$FUNCHEAD$$" 83 | 84 | @type_const :"$$TCONST$$" # {@type_const, type, meta} 85 | @type_ptr :"$$TPTR$$" # {@type_ptr, ptr, meta} 86 | # @type_or :"$$TPTR$$" # {@type_or, [types], meta} 87 | @type_func :"$$TFUNC$$" # {@type_func, [heads], meta} 88 | @type_func_head :"$$TFUNCHEAD$$" # {@type_func_head, {args, return}, meta} 89 | 90 | # @type_ptr_generic :"$$TGENERIC$$" # {@type_ptr_generic, nil, meta} 91 | @type_ptr_unbound :"$$TUNBOUND$$" # {@type_ptr_unbound, nil, meta} 92 | 93 | 94 | 95 | @module_open_func :__ml_open__ 96 | 97 | 98 | # TODO: Add the disc_union library or something to replace the above constants so they are actually useful elsewhere... 99 | 100 | 101 | defmodule Core do 102 | 103 | def __ml_open__ do 104 | %{ 105 | funs: %{ 106 | +: fn # Passes in the environment, meta of the call, and the calls arguments AST's 107 | (env, meta, [{_,leftMeta,_}, {_,rightMeta,_}]=args) -> 108 | left = leftMeta[:type] 109 | right = rightMeta[:type] 110 | # {typeTag, type, _typeMeta} = MLElixir.unify_types!(env, left, right) 111 | {typeTag, type, _typeMeta} = TypedElixir.Type.unify_types!(env, left, right) 112 | # TODO: Make addition refine the values properly on the type 113 | if typeTag === :"$$TCONST$$" and type in [:int, :float] do 114 | ast = {:"$$CALL$$", [type: {typeTag, type, []}] ++ meta, [{Kernel, :+} | args]} 115 | {env, ast} 116 | else 117 | raise %InvalidCall{message: "Invalid arguments types, only `int` or `float` is allowed", name: :+, meta: meta} 118 | end 119 | end, 120 | }, 121 | types: %{ 122 | }, 123 | } 124 | end 125 | 126 | end 127 | 128 | 129 | 130 | 131 | defmacro defml(opts) when is_list(opts) do 132 | case opts[:do] do 133 | nil -> 134 | [ast] = opts 135 | defml_impl(ast, []) 136 | ast when is_tuple(ast) -> defml_impl(ast, opts) 137 | end 138 | end 139 | defmacro defml(expr) do 140 | defml_impl(expr, []) 141 | end 142 | 143 | defp defml_impl(expr, opts) do 144 | # IO.inspect {:ML, expr} 145 | no_default_opens = opts[:no_default_opens] || false 146 | env = if no_default_opens, do: %MLEnv{}, else: open_module(%MLEnv{}, MLElixir.Core) 147 | env = case opts[:open] do 148 | nil -> env 149 | module when is_atom(module) -> 150 | open_module(env, module) 151 | modules when is_list(modules) -> 152 | Enum.reduce(modules, env, fn(module, env) -> open_module(env, module) end) 153 | end 154 | {ml_env, ml_ast} = parse_ml_expr(env, expr) 155 | # IO.inspect {:MLAST, ml_ast} 156 | # IO.inspect {:MLENV, ml_env} 157 | ast = reify_ml_expr(ml_env, ml_ast) 158 | # IO.inspect {:MLDONE, ast} 159 | ast 160 | end 161 | 162 | 163 | 164 | defp parse_ml_expr(env, expr) 165 | 166 | # Literals... why does Elixir not give line information for these?!? 167 | defp parse_ml_expr(env, int) when is_integer(int), do: {env, {@tag_lit, [type: {@type_const, :int, [values: [int]]}], int}} 168 | defp parse_ml_expr(env, float) when is_float(float), do: {env, {@tag_lit, [type: {@type_const, :float, [values: [float]]}], float}} 169 | defp parse_ml_expr(env, atom) when is_atom(atom), do: {env, {@tag_lit, [type: {@type_const, :atom, [values: [atom]]}], atom}} 170 | 171 | # defp parse_ml_expr(env, {:module, module_meta, [[{module_name, module_defs}]]}) when is_atom(module_name) do 172 | # defs = Enum.map(module_defs, &parse_module_def(&1, env)) 173 | # IO.inspect {:MODULE, module_name, defs} 174 | # :todo 175 | # end 176 | 177 | # TODO: Add `rec`/`and` handling here 178 | 179 | # Let variable binding to expression with following 180 | defp parse_ml_expr(env, {:let, let_meta, let_ast}) do 181 | parse_let(env, let_meta, let_ast) 182 | end 183 | # defp parse_ml_expr(env, {:let, let_meta, [{:=, equal_meta, [binding_ast, {:in, in_meta, [inner_expr_ast, after_expr_ast]}]}]}) do 184 | # {envBinding, binding} = parse_binding(env, binding_ast) 185 | # {envExprInner, exprInner} = parse_ml_expr(envBinding, inner_expr_ast) 186 | # {envInnerResolved, bindingResolved} = resolve_binding(envExprInner, binding, exprInner) 187 | # {envExprAfter, exprAfter} = parse_ml_expr(envInnerResolved, after_expr_ast) 188 | # ast = {@tag_let, [type: type_of_expr(envExprAfter, exprAfter)] ++ let_meta, [bindingResolved, exprInner, exprAfter]} 189 | # {envExprAfter, ast} 190 | # end 191 | 192 | # # Let variable binding to expression without following 193 | # defp parse_ml_expr(env, {:let, let_meta, [{:=, equal_meta, [binding_ast, expr_ast]}]}) do 194 | # {envInner, binding} = parse_binding(binding_ast) 195 | # end 196 | 197 | # A variable lookup 198 | defp parse_ml_expr(env, {name, meta, scope}) when is_atom(name) and is_atom(scope) do 199 | var_type = type_binding(env, name, scope) 200 | ast = {@tag_var, [type: var_type] ++ meta, [name, scope]} 201 | {env, ast} 202 | end 203 | 204 | # A function definition 205 | defp parse_ml_expr(env, {:->, _body_meta, [[{:fun, fun_meta, [head_ast]}], body_ast]}) do 206 | throw {:TODO, :parse_ml_expr, body_ast} 207 | {env, {_, headMeta, _} = head} = parse_fn_head(env, head_ast) 208 | type = {@type_func, headMeta[:type], []} 209 | ast = {@tag_func, [type: type] ++ fun_meta, [head]} 210 | {env, ast} 211 | end 212 | 213 | # A function call 214 | defp parse_ml_expr(env, {name, call_meta, call_args}) when is_atom(name) and is_list(call_args) do 215 | case env.funs[name] do 216 | nil -> 217 | case env.type_bindings[name] do # TODO: Combine the func and bindings maps... 218 | nil -> raise %InvalidCall{message: "No such function found", name: name, meta: call_meta} 219 | {@type_ptr, ptr, _meta} -> 220 | case env.type_vars[ptr] do 221 | {@type_func, heads, _funcMeta} -> 222 | # TODO: Find matching head? Or change @type_func to give an anonymous function like env.funs does? 223 | throw {:TODO, :parse_ml_expr, :heads, heads} 224 | _ -> raise %InvalidCall{message: "Trying to call a non-function", name: name, meta: call_meta} 225 | end 226 | _var -> raise %InvalidCall{message: "Found var of unknown type", name: name, meta: call_meta} 227 | end 228 | fun -> 229 | args = Enum.map(call_args, &elem(parse_ml_expr(env, &1), 1)) 230 | {env, ast} = fun.(env, call_meta, args) 231 | {env, ast} 232 | end 233 | end 234 | 235 | 236 | 237 | defp parse_fn_head(env, {:->, meta, [args, expr]}) do 238 | {envBinding, bindings} = Enum.reduce(args, {env, []}, fn(binding_ast, {e, a}) -> 239 | {en, binding} = parse_binding(e, binding_ast) 240 | {en, a ++ [binding]} 241 | end) 242 | {_envExprInner, {_,bodyMeta,_}=exprInner} = parse_ml_expr(envBinding, expr) 243 | # TODO: Parse bindings and expression based on the bindings 244 | args_types = Enum.map(bindings, &(elem(&1,1)[:type])) 245 | return_type = bodyMeta[:type] 246 | type = {@type_func_head, {args_types, return_type}, []} 247 | {@tag_func_head, [type: type] ++ meta, [bindings, exprInner]} 248 | end 249 | 250 | 251 | 252 | # defp parse_module_def({:=, equal_meta, [name_ast, expr]}, env) do 253 | # :blah 254 | # end 255 | 256 | 257 | 258 | defp parse_let(env, let_meta, ast) 259 | 260 | # binding no name 261 | defp parse_let(env, let_meta, [{:=, _equal_meta, [binding_ast, {:in, _in_meta, [inner_expr_ast, after_expr_ast]}]}]) do 262 | {envBinding, binding} = parse_binding(env, binding_ast) 263 | {envExprInner, exprInner} = parse_ml_expr(envBinding, inner_expr_ast) 264 | # IO.inspect {:BLORP, envExprInner.type_vars, binding, exprInner} 265 | {envInnerResolved, bindingResolved} = resolve_binding(envExprInner, binding, exprInner) 266 | # IO.inspect {:BLEEP, envInnerResolved.type_vars, bindingResolved} 267 | {envExprAfter, exprAfter} = parse_ml_expr(envInnerResolved, after_expr_ast) 268 | ast = {@tag_let, [type: type_of_expr(envExprAfter, exprAfter)] ++ let_meta, [bindingResolved, exprInner, exprAfter]} 269 | {envExprAfter, ast} 270 | end 271 | 272 | # binding with name 273 | defp parse_let(env, let_meta, [[{name_ast, {:=, equal_meta, [binding_ast, {:in, _in_meta, [inner_expr_ast, after_expr_ast]}]}}]]) when is_atom(name_ast) do 274 | {envBinding, {_, bindMeta, _}=binding} = parse_binding(env, binding_ast) 275 | {envBinding, nameType} = env_storeBinding(envBinding, name_ast, bindMeta[:type]) 276 | name = {@tag_var, [type: nameType] ++ let_meta, [name_ast, nil]} 277 | args = {@tag_mbind, [type: nameType] ++ equal_meta, [name, binding]} 278 | {envExprInner, exprInner} = parse_ml_expr(envBinding, inner_expr_ast) 279 | {envInnerResolved, bindingResolved} = resolve_binding(envExprInner, args, exprInner) 280 | {envExprAfter, exprAfter} = parse_ml_expr(envInnerResolved, after_expr_ast) 281 | ast = {@tag_let, [type: type_of_expr(envExprAfter, exprAfter)] ++ let_meta, [bindingResolved, exprInner, exprAfter]} 282 | {envExprAfter, ast} 283 | end 284 | 285 | # open a module into here 286 | defp parse_let(env, _let_meta, [{:open, _open_meta, [{:in, _in_meta, [module_ast, expr_ast]}]}]) do 287 | {env, module} = parse_modulename(env, module_ast) 288 | env = open_module(env, module) 289 | parse_ml_expr(env, expr_ast) 290 | end 291 | 292 | 293 | defp parse_modulename(env, module_ast) 294 | defp parse_modulename(env, atom) when is_atom(atom), do: {env, atom} 295 | defp parse_modulename(env, {:__aliases__, _meta, atomList}) when is_list(atomList), do: {env, Module.concat(atomList)} 296 | 297 | 298 | 299 | defp open_module(env, module, opts \\ []) 300 | defp open_module(env, module, opts) when is_atom(module) do 301 | case Code.ensure_compiled(module) do 302 | {:error, reason} -> raise %InvalidCall{message: "Module is not loadable", name: module, meta: reason} 303 | {:module, _} -> 304 | case function_exported?(module, @module_open_func, 0) do 305 | false -> raise %InvalidCall{message: "Module does not exist or is not an ML module", name: module} 306 | true -> 307 | moduleDef = apply(module, @module_open_func, []) 308 | moduledef_into_env(env, moduleDef, opts) 309 | end 310 | end 311 | end 312 | 313 | defp moduledef_into_env(env, moduleDef, _opts) do 314 | funs = Map.merge(env.funs, moduleDef.funs) 315 | %{env | 316 | funs: funs 317 | } 318 | end 319 | 320 | 321 | 322 | defp env_storeBinding(env, name, ptr_type \\ {@type_ptr_unbound, nil, []}) do 323 | env = increment_counter(env) 324 | type = {@type_ptr, env.counter, []} 325 | # Hmm, cancel this, still bind them even if unused, good for lookups later to ensure typing 326 | # if String.starts_with?(to_string(name), "_") do 327 | # {env, type} 328 | # else 329 | { 330 | %{env | 331 | type_bindings: Map.put(env.type_bindings, name, type), 332 | type_vars: Map.put(env.type_vars, env.counter, ptr_type) 333 | }, 334 | type 335 | } 336 | # end 337 | end 338 | 339 | 340 | # STATIC BINDING: Typeless variable binding 341 | # TODO: Make version of this where scope defines the function 342 | defp parse_binding(env, {name, meta, scope}) when is_atom(name) and is_atom(scope) do 343 | {newEnv, type} = env_storeBinding(env, name) 344 | ast = {@tag_var, [type: type] ++ meta, [name, scope]} 345 | {newEnv, ast} 346 | end 347 | # MATCHING BINDING: Constants - Copied from parse_ml_expr... 348 | defp parse_binding(env, int) when is_integer(int), do: {env, {@tag_lit, [type: {@type_const, :int, [values: [int]]}], int}} 349 | defp parse_binding(env, float) when is_float(float), do: {env, {@tag_lit, [type: {@type_const, :float, [values: [float]]}], float}} 350 | defp parse_binding(env, atom) when is_atom(atom), do: {env, {@tag_lit, [type: {@type_const, :atom, [values: [atom]]}], atom}} 351 | # STATIC TYPED BINDING: Typed variable binding 352 | defp parse_binding(env, {:!, meta, [[{name, type_expr}]]}) when is_atom(name) do 353 | {env, varType} = parse_type(env, name, type_expr) 354 | {newEnv, type} = env_storeBinding(env, name, varType) 355 | ast = {@tag_var, [type: type] ++ meta, [name, nil]} # TODO: Really need to constrain the scopes well... `nil` is wide open 356 | {newEnv, ast} 357 | end 358 | 359 | 360 | defp parse_type(env, varname, type_ast) 361 | # Parse a constant int/float/atom type 362 | defp parse_type(env, varname, {name, _meta, constraints}) when name in [:int, :float, :atom] do 363 | {env, values} = parse_type_const_constraints(env, varname, name, constraints) 364 | {env, {@type_const, name, values}} 365 | end 366 | 367 | defp parse_type_const_constraints(env, varname, ptype, constraint) 368 | defp parse_type_const_constraints(env, _varname, _ptype, scope) when is_atom(scope), do: {env, []} 369 | defp parse_type_const_constraints(env, varname, :int, [{:=, _meta, [{varname,_,varnameScope}, val]}]) when is_atom(varnameScope) and is_integer(val), do: {env, [values: [val]]} 370 | defp parse_type_const_constraints(env, varname, :int, [{:>=, _meta, [{varname,_,varnameScope}, val]}]) when is_atom(varnameScope) and is_integer(val), do: {env, [values: [{val, :infinite}]]} 371 | defp parse_type_const_constraints(env, varname, :int, [{:<=, _meta, [{varname,_,varnameScope}, val]}]) when is_atom(varnameScope) and is_integer(val), do: {env, [values: [{:infinite, val}]]} 372 | defp parse_type_const_constraints(env, varname, :float, [{:=, _meta, [{varname,_,varnameScope}, val]}]) when is_atom(varnameScope) and is_float(val), do: {env, [values: [val]]} 373 | defp parse_type_const_constraints(env, varname, :float, [{:>=, _meta, [{varname,_,varnameScope}, val]}]) when is_atom(varnameScope) and is_float(val), do: {env, [values: [{val, :infinite}]]} 374 | defp parse_type_const_constraints(env, varname, :float, [{:<=, _meta, [{varname,_,varnameScope}, val]}]) when is_atom(varnameScope) and is_float(val), do: {env, [values: [{:infinite, val}]]} 375 | 376 | 377 | 378 | # TODO: Add support for matching bindings here 379 | 380 | # The binding is but a single variable, resolve it 381 | defp resolve_binding(env, {@tag_var, bindMeta, [_name, _scope]} = binding, {_, exprMeta, _}) do 382 | bindType = bindMeta[:type] 383 | exprType = exprMeta[:type] 384 | case bindType do 385 | {@type_ptr, ptr, _typePtrMeta} -> # Bindings should always have a ptr type if not starting with "_" 386 | case env.type_vars[ptr] do 387 | nil -> raise %UnificationError{type0: bindType} 388 | t -> 389 | tightestType = resolve_types!(env, exprType, t) 390 | resolvedEnv = %{env | 391 | type_vars: Map.put(env.type_vars, ptr, tightestType) 392 | } 393 | # IO.inspect {:BLERGH, exprType, t, ptr, resolvedEnv, binding} 394 | {resolvedEnv, binding} 395 | end 396 | t -> raise %UnificationError{type0: t, type1: exprType} 397 | end 398 | end 399 | 400 | # Literal binding 401 | defp resolve_binding(env, {@tag_lit, bindMeta, value}, {_, exprMeta, _}) do 402 | bindType = bindMeta[:type] 403 | exprType = exprMeta[:type] 404 | resolvedType = resolve_types!(env, bindType, exprType) 405 | binding = {@tag_lit, [type: resolvedType] ++ bindMeta, value} 406 | {env, binding} 407 | end 408 | 409 | # Multi-bindings 410 | defp resolve_binding(env, {@tag_mbind, bindMeta, values}, {_, _exprMeta, _}=expr) do 411 | {env, newValues} = Enum.reduce(values, {env, []}, fn(value, {env, acc}) -> 412 | {e, v} = resolve_binding(env, value, expr) 413 | {e, acc ++ [v]} 414 | end) 415 | {env, {@tag_mbind, bindMeta, newValues}} 416 | end 417 | 418 | 419 | 420 | # Inferring 421 | 422 | # defp new_var(env, depth, meta \\ [], vmeta \\ []) do 423 | # env = increment_counter(env) 424 | # env = %{env | 425 | # type_vars: Map.put_new(env.type_vars, env.counter, {@type_var_unbound, env.counter, depth, vmeta}) 426 | # } 427 | # {env, {@type_var, env.counter, meta}} 428 | # end 429 | # 430 | # defp new_var_generic(env, meta \\ [], vmeta \\ []) do 431 | # env = increment_counter(env) 432 | # env = %{env | 433 | # type_vars: Map.put_new(env.type_vars, env.counter, {@type_var_generic, env.counter, vmeta}) 434 | # } 435 | # {env, {@type_var, env.counter, meta}} 436 | # end 437 | # 438 | # 439 | # defp unify_check_depth(env, _id, _int, {@type_const, _name, _meta}), do: env 440 | # defp unify_check_depth(env, id, int, {@type_app, type, type_args, _meta}) do 441 | # env = Enum.reduce(type_args, env, fn(t, e) -> unify_check_depth(e, id, int, t) end) 442 | # unify_check_depth(env, type) 443 | # end 444 | # defp unify_check_depth(env, id, int, {@type_func, type_args, type, _meta}) do 445 | # env = unify_check_depth(env, type) 446 | # Enum.reduce(type_args, env, fn(t, e) -> unify_check_depth(e, id, int, t) end) 447 | # end 448 | # defp unify_check_depth(env, id, int, {@type_var, vid, _meta}=t) do 449 | # case env.type_vars[vid] do 450 | # {@type_var_link, vvtype, _vvmeta} -> unify_check_depth(env, id, int, vvtype) 451 | # {@type_var_generic, _vvid, _vvmeta} -> raise %UnificationError{type0: t} 452 | # {@type_var_unbound, ^id, _vvint, _vvmeta} -> raise %UnificationError{type0: t} 453 | # {@type_var_unbound, vvid, vvint, vvmeta} when vvint>int -> 454 | # %{env | type_vars: Map.put(env.type_vars, vvid, {@type_var_unbound, vvid, int, vvmeta})} 455 | # {@type_var_unbound, vvid, _vvint, _vvmeta} -> env 456 | # end 457 | # end 458 | # 459 | # 460 | # defp unify_type_list(_env, [], t), do: raise %UnificationError{type0: nil, type1: t} 461 | # defp unify_type_list(_env, t, []), do: raise %UnificationError{type0: t, type1: nil} 462 | # defp unify_type_list(env, [t0|rest0], [t1|rest1]) do 463 | # env = unify(env, t0, t1) 464 | # unify_type_list(env, rest0, rest1) 465 | # end 466 | # 467 | # 468 | # defp unify(env, type0, type1) 469 | # defp unify(env, type0, type0), do: env 470 | # defp unify(env, {@type_const, name, _meta0}, {@type_const, name, _meta1}), do: env 471 | # defp unify(env, {@type_app, type0, type_args0, _meta0}, {@type_app, type1, type_args1, _meta1}) do 472 | # env = unify(env, type0, type1) 473 | # unify_type_list(env, type_args0, type_args1) 474 | # end 475 | # defp unify(env, {@type_func, type_args0, type0, _meta0}, {@type_func, type_args1, type1, _meta1}) do 476 | # env = unify_type_list(env, type_args0, type_args1) 477 | # unify(env, type0, type1) 478 | # end 479 | # defp unify(env, {@type_var, id0, _meta0}, {@type_var, id1, _meta1}) do 480 | # v0 = env.type_vars[id0] 481 | # v1 = env.type_vars[id1] 482 | # case {v0, v1} do 483 | # {{@type_var_link, type0, _meta0}, type1} -> unify(type0, type1) 484 | # {type0, {@type_var_link, type1, _meta1}} -> unify(type0, type1) 485 | # {{@type_var_unbound, id, _int0, _meta0}=t0, {@type_var_unbound, id, _int1, _meta1}=t1} -> raise %UnificationError{type0: t0, type1: t1} 486 | # {{@type_var_unbound, id, int, meta}, t} -> 487 | # env = unify_check_depth(env, id, int, t) 488 | # %{env | type_vars: Map.put(env.type_vars, id, {@type_var_link, type, meta})} 489 | # {t, {@type_var_unbound, id, int, meta}} -> 490 | # env = unify_check_depth(env, id, int, t) 491 | # %{env | type_vars: Map.put(env.type_vars, id, {@type_var_link, type, meta})} 492 | # {t0, t1} -> raise %UnificationError{type0: t0, type1: t1} 493 | # end 494 | # end 495 | 496 | 497 | # defp generalize(env, depth, type) 498 | # defp generalize(env, depth, {@type_const, _name, _meta0} = type), do: {env, type} 499 | # defp generalize(env, depth, {@type_app, type, type_args, meta}) do 500 | # {env, type} = generalize(env, depth, type) 501 | # {env, type_args} = Enum.reduce(type_args, {env, []}, fn(ty, {en, sf}) -> 502 | # {e, t} = generalize(en, depth, ty) 503 | # {e, sf++[t]} 504 | # end) 505 | # {env, {@type_app, type, type_args, meta}} 506 | # end 507 | # defp generalize(env, depth, {@type_func, type_args, type, meta}) do 508 | # {env, type_args} = Enum.reduce(type_args, {env, []}, fn(ty, {en, sf}) -> 509 | # {e, t} = generalize(en, depth, ty) 510 | # {e, sf++[t]} 511 | # end) 512 | # {env, type} = generalize(env, depth, type) 513 | # {env, {@type_func, type_args, type, meta}} 514 | # end 515 | # defp generalize(env, depth, {@type_var, id, meta}=t) do 516 | # case env.type_var[id] do 517 | # {@type_var_unbound, vid, vdepth, vmeta} when vdepth > depth -> 518 | # env = %{env | type_vars: Map.put(env.type_vars, id, {@type_var_generic, vid, vmeta})} 519 | # {env, t} 520 | # {@type_var_link, type, meta} -> 521 | # %{env | type_vars: Map.put(env.type_vars, id, generalize(env, depth, type))} 522 | # _ -> {env, t} 523 | # end 524 | # end 525 | # 526 | # 527 | # defp instantiate(env, depth, type) do 528 | # {env, _varmap, type} = instantiate(env, %{}, depth, type) 529 | # {env, type} 530 | # end 531 | # defp instantiate(env, varmap, _depth, {@type_const, _name, _meta0} = type), do: {env, varmap, type} 532 | # defp instantiate(env, varmap, depth, {@type_app, type, type_args, meta}) do 533 | # {env, varmap, type} = instantiate(env, varmap, depth, type) 534 | # {env, varmap, type_args} = Enum.reduce(type_args, {env, varmap, []}, fn(ty, {en, vm, sf}) -> 535 | # {e, v, t} = instantiate(en, vm, depth, ty) 536 | # {e, v, sf++[t]} 537 | # end) 538 | # {env, varmap, {@type_app, type, type_args, meta}} 539 | # end 540 | # defp instantiate(env, varmap, depth, {@type_func, type_args, type, meta}) do 541 | # {env, varmap, type} = instantiate(env, varmap, depth, type) 542 | # {env, varmap, type_args} = Enum.reduce(type_args, {env, varmap, []}, fn(ty, {en, vm, sf}) -> 543 | # {e, v, t} = instantiate(en, vm, depth, ty) 544 | # {e, v, sf++[t]} 545 | # end) 546 | # {env, varmap, {@type_app, type_args, type, meta}} 547 | # end 548 | # defp instantiate(env, varmap, depth, {@type_var, id, meta}=t) do 549 | # case env.type_var[id] do 550 | # {@type_var_unbound, vid, vdepth, vmeta} -> {env, varmap, t} 551 | # {@type_var_link, type, meta} -> instantiate(env, varmap, depth, type) 552 | # {@type_var_generic, vid, vmeta} -> 553 | # case varmap[vid] do 554 | # nil -> 555 | # {env, var} = new_var(env, depth) 556 | # varmap = Map.put(varmap, id, var) 557 | # {env, varmap, var} 558 | # var -> {env, varmap, var} 559 | # end 560 | # end 561 | # end 562 | # 563 | # 564 | # defp verify_fun_type(env, num_params, type) 565 | # defp verify_fun_type(env, num_params, {@type_func, type_args, type, meta}=t) do 566 | # if length(type_args) != num_params do 567 | # raise %UnificationError{type0: t} 568 | # else 569 | # {env, type_args, type} 570 | # end 571 | # end 572 | # defp verify_fun_type(env, num_params, {@type_var, id, meta}=t) do 573 | # case env.type_vars[id] do 574 | # {@type_var_link, type, _meta} -> verify_fun_type(env, num_params, type) 575 | # {@type_var_unbound, id, depth, meta} -> 576 | # {env, args} = Enum.reduce(0..num_params, {env, []}, fn(i, {env, acc}) -> 577 | # {env, var} = new_var(env, i) 578 | # {env, acc ++ var} 579 | # end) 580 | # {env, return} = new_var(env, depth) 581 | # %{env | type_vars: Map.put(env.type_vars, id, {@type_var_link, {@type_func, args, return, meta}, meta})} 582 | # {env, args, return} 583 | # end 584 | # end 585 | # defp verify_fun_type(env, num_params, t), do: raise %UnificationError{type0: t} 586 | # 587 | # 588 | # defp infer_ml_ast(env, depth \\ 0, ast) 589 | # 590 | # # Literals always have known types 591 | # defp infer_ml_ast(env, depth, {@tag_lit, _meta, _value} = ast), do: {env, ast} 592 | # 593 | # defp infer_ml_ast(env, depth, {@tag_var, meta, [_name, _scope]} = ast) do 594 | # try do 595 | # {env, type} = instantiate(env, depth, meta.type) 596 | # {env, ast} 597 | # catch 598 | # _,_ -> raise %UnificationError{type0: meta.type} 599 | # end 600 | # end 601 | # 602 | # defp infer_ml_ast(env, depth, {@tag_call, call_meta, [call_name | call_args]} = ast) do 603 | # {env, fn_ast} = infer_ml_ast(env, depth, ) 604 | # {env, args, return} = verify_fun_type(env, length(call_args), ) 605 | # end 606 | 607 | 608 | 609 | # Typing 610 | 611 | # defp type_is_fullfilled_by({type, _type_meta}, {constraint_type, _constraint_meta}) do 612 | # # TODO: Compare the meta's as necessary too perhaps? 613 | # case {type, constraint_type} do 614 | # {type, type} -> true 615 | # _ -> false 616 | # end 617 | # end 618 | 619 | defp type_of_expr(_env, {_, meta, _}), do: meta[:type] 620 | 621 | # defp type_call_does_match?([], []), do: true 622 | # defp type_call_does_match?([doesThis | doesRest], [intoThis | intoRest]) do 623 | # if type_is_fullfilled_by(doesThis, intoThis) do 624 | # type_call_does_match?(doesRest, intoRest) 625 | # else 626 | # false 627 | # end 628 | # end 629 | # 630 | # defp type_call(%{type_funs: funs}=env, name, args) do 631 | # case Map.get(funs, name) do 632 | # nil -> raise %UnknownCall{env: env, name: name, args: args} 633 | # heads when is_list(heads) -> 634 | # args_type = Enum.map(args, &type_of_expr(env, &1)) 635 | # IO.inspect {:FUNCY, env} 636 | # case Enum.find(heads, &type_call_does_match?(args_type, &1)) do 637 | # nil -> raise %UnknownCall{env: env, name: name, args: args} 638 | # {_argTypes, matchFn} -> matchFn.(args) 639 | # end 640 | # end 641 | # end 642 | 643 | defp type_binding(%{type_bindings: bindings}=env, name, scope) do 644 | case Map.get(bindings, name) do 645 | nil -> raise %UnknownVar{env: env, name: name, scope: scope} 646 | type -> type 647 | end 648 | end 649 | 650 | 651 | 652 | defp resolve_types_const_meta_values(_env, _type, fromValues, intoValues) 653 | defp resolve_types_const_meta_values(_env, _type, value, value), do: value 654 | defp resolve_types_const_meta_values(_env, _type, fromValues, []), do: fromValues # `into` is accepting any value 655 | defp resolve_types_const_meta_values(_env, _type, fromValues, intoValues) do 656 | if Enum.all?(fromValues, fn 657 | {:infinite, :infinite} -> Enum.all?(intoValues, fn 658 | {:infinite, :infinite} -> true 659 | :infinite -> true 660 | _ -> false 661 | end) 662 | {fromFirst, :infinite} -> Enum.all?(intoValues, fn 663 | {:infinite, :infinite} -> true 664 | {intoFirst, :infinite} -> fromFirst>=intoFirst 665 | :infinite -> true 666 | _ -> false 667 | end) 668 | {:infinite, fromLast} -> Enum.all?(intoValues, fn 669 | {:infinite, :infinite} -> true 670 | {:infinite, intoLast} -> fromLast<=intoLast 671 | :infinite -> true 672 | _ -> false 673 | end) 674 | {fromFirst, fromLast} -> Enum.all?(intoValues, fn 675 | {:infinite, :infinite} -> true 676 | {intoFirst, :infinite} -> fromFirst>=intoFirst 677 | {:infinite, intoLast} -> fromLast<=intoLast 678 | {intoFirst, intoLast} -> fromFirst>=intoFirst and fromLast<=intoLast 679 | :infinite -> true 680 | into -> into === fromFirst and into === fromLast 681 | end) 682 | :infinite -> Enum.all?(intoValues, fn 683 | {:infinite, :infinite} -> true 684 | :infinite -> true 685 | _ -> false 686 | end) 687 | from -> Enum.all?(intoValues, fn 688 | {:infinite, :infinite} -> true 689 | {intoFirst, :infinite} -> from>=intoFirst 690 | {:infinite, intoLast} -> from<=intoLast 691 | {intoFirst, intoLast} -> from>=intoFirst and from<=intoLast 692 | :infinite -> true 693 | into -> from === into 694 | end) 695 | end) do 696 | fromValues 697 | else 698 | nil 699 | end 700 | end 701 | 702 | 703 | def resolve_types!(env, fromType, intoType) do 704 | # inspect {:RESOLVERINATING, env, fromType, intoType} 705 | type = resolve_types(env, fromType, intoType) 706 | if Exception.exception?(type) do 707 | raise type 708 | else 709 | type 710 | end 711 | end 712 | # `intoType` needs to be able to encompass `fromType`, such as if fromType 713 | # has a specific value but intoType accepts a range of values that includes 714 | # that values 715 | def resolve_types(env, fromType, intoType) 716 | def resolve_types(_env, type, type), do: type 717 | def resolve_types(env, {@type_const, type, fromMeta}=fromType, {@type_const, type, intoMeta}=intoType) do 718 | case resolve_types_const_meta_values(env, type, List.wrap(fromMeta[:values]), List.wrap(intoMeta[:values])) do 719 | nil -> 720 | # TODO: Make a dedicated %ResolutionError{} or something, or something specifically for these values perhaps? 721 | %UnificationError{message: "Unable to resolve", env: env, type0: fromType, type1: intoType} 722 | _values -> fromType 723 | end 724 | end 725 | def resolve_types(env, {@type_const, _fromType, _fromMeta}=fromType, {@type_const, _intoType, _intoMeta}=intoType) do 726 | %UnificationError{message: "Unable to resolve mismatched types", env: env, type0: fromType, type1: intoType} 727 | end 728 | def resolve_types(env, fromType, {@type_ptr, ptr, _intoMeta}=intoType) do 729 | case env.type_vars[ptr] do 730 | nil -> %UnificationError{message: "Unable to resolve pointed to var due to not being set", env: env, type0: fromType, type1: intoType} 731 | t -> resolve_types(env, fromType, t) 732 | end 733 | end 734 | def resolve_types(env, {@type_ptr, ptr, _fromMeta}=fromType, intoType) do 735 | case env.type_vars[ptr] do 736 | nil -> %UnificationError{message: "Unable to resolve pointed to var due to not being set", env: env, type0: fromType, type1: intoType} 737 | t -> resolve_types(env, t, intoType) 738 | end 739 | end 740 | def resolve_types(_env, fromType, {@type_ptr_unbound, nil, _intoMeta}), do: fromType 741 | 742 | 743 | 744 | defp unify_types_const_meta_values(_env, type, values0, values1) 745 | defp unify_types_const_meta_values(_env, _type, [], []), do: [] 746 | defp unify_types_const_meta_values(_env, _type, [], values1), do: values1 747 | defp unify_types_const_meta_values(_env, _type, values0, []), do: values0 748 | defp unify_types_const_meta_values(_env, _type, values0, values1) do 749 | values0 ++ values1 # TODO: Make this smarter, like by combining ranges, right now just combine them all 750 | end 751 | 752 | defp unify_types_const_meta(env, type, meta0, meta1) do 753 | unified_values = unify_types_const_meta_values(env, type, List.wrap(meta0[:values]), List.wrap(meta1[:values])) 754 | if Exception.exception?(unified_values) do 755 | unified_values 756 | else 757 | [values: unified_values] ++ meta0 |> Enum.dedup() # TODO: Simplify and merge 758 | end 759 | end 760 | 761 | 762 | def unify_types!(env, t0, t1) do 763 | type = unify_types(env, t0, t1) 764 | if Exception.exception?(type) do 765 | raise type 766 | else 767 | type 768 | end 769 | end 770 | 771 | def unify_types(env, t0, t1) 772 | def unify_types(env, t, t), do: {env, t} 773 | def unify_types(env, {@type_const, type, meta0}, {@type_const, type, meta1}) do 774 | meta = unify_types_const_meta(env, type, meta0, meta1) 775 | {@type_const, type, meta} 776 | end 777 | def unify_types(env, {@type_ptr, ptr, _ptrMeta}=t0, t1) do 778 | case env.type_vars[ptr] do 779 | nil -> %UnificationError{message: "Pointed to type0 is unbound", env: env, type0: t0, type1: t1} 780 | t -> unify_types(env, t, t1) 781 | end 782 | end 783 | def unify_types(env, t0, {@type_ptr, ptr, _ptrMeta}=t1) do 784 | case env.type_vars[ptr] do 785 | nil -> %UnificationError{message: "Pointed to type1 is unbound", env: env, type0: t0, type1: t1} 786 | t -> unify_types(env, t0, t) 787 | end 788 | end 789 | # def unify_types(_env, {@type_ptr_generic, nil, _genMeta0}, t1), do: t1 790 | # def unify_types(_env, t0, {@type_ptr_generic, nil, _genMeta1}), do: t0 791 | def unify_types(_env, {@type_ptr_unbound, nil, _genMeta0}, t1), do: t1 792 | def unify_types(_env, t0, {@type_ptr_unbound, nil, _genMeta1}), do: t0 793 | def unify_types(_env, {tt, v, _m0}=t0, {tt, v, _m1}=_t1) do 794 | # TODO: Add in meta parsing 795 | t0 796 | end 797 | def unify_types(env, t0, t1) do 798 | %UnificationError{message: "Unable to unify types", env: env, type0: t0, type1: t1} 799 | end 800 | 801 | 802 | # Reification 803 | 804 | defp reify_ml_expr(env, ast, to \\ :elixir) 805 | defp reify_ml_expr(env, ast, :elixir), do: reify_ml_expr_elixir(env, ast ) 806 | 807 | # Literal value 808 | defp reify_ml_expr_elixir(_env, {@tag_lit, _lit_meta, lit_value}), do: lit_value 809 | 810 | # Function call 811 | defp reify_ml_expr_elixir(env, {@tag_call, call_meta, [call_name | call_args]}) do 812 | args = Enum.map(call_args, &reify_ml_expr_elixir(env, &1)) 813 | case call_name do 814 | {module, func} -> 815 | {{:., [], [{:__aliases__, [alias: false], [module]}, func]}, call_meta, args} 816 | end 817 | end 818 | 819 | # Let binding 820 | defp reify_ml_expr_elixir(env, {@tag_let, let_meta, [binding, exprInner, exprAfter]}) do 821 | {:__block__, let_meta, [ 822 | {:=, [], [reify_ml_expr_elixir(env, binding), reify_ml_expr_elixir(env, exprInner)]}, 823 | reify_ml_expr_elixir(env, exprAfter) 824 | ]} 825 | end 826 | 827 | # Variable binding 828 | defp reify_ml_expr_elixir(_env, {@tag_var, bind_meta, [name, scope]}) do 829 | {name, bind_meta, scope} 830 | end 831 | 832 | # Multi-binding 833 | defp reify_ml_expr_elixir(env, {@tag_mbind, bind_meta, bindings}) do 834 | List.foldr(bindings, nil, fn 835 | (binding, nil) -> reify_ml_expr_elixir(env, binding) 836 | (binding, ast) -> {:=, bind_meta, [reify_ml_expr_elixir(env, binding), ast]} 837 | end) 838 | end 839 | 840 | # Function 841 | defp reify_ml_expr_elixir(env, {@tag_func, meta, heads}) do 842 | {:fn, meta, Enum.map(heads, &reify_ml_expr_elixir(env, &1))} 843 | end 844 | 845 | # Function head 846 | defp reify_ml_expr_elixir(env, {@tag_func_head, meta, [args, body]}) do 847 | {:->, meta, [Enum.map(args, &reify_ml_expr_elixir(env, &1)), reify_ml_expr_elixir(env, body)]} 848 | end 849 | 850 | end 851 | -------------------------------------------------------------------------------- /lib/typed_elixir.ex: -------------------------------------------------------------------------------- 1 | defmodule TypedElixir do 2 | @moduledoc """ 3 | """ 4 | 5 | 6 | alias TypedElixir.HMEnv 7 | alias TypedElixir.Type 8 | 9 | 10 | 11 | 12 | # defmodule TypeGeneric ,do: defstruct [] 13 | # defmodule TypeConst ,do: defstruct type: :"$$NO$TYPE$$", meta: %{} 14 | # defmodule TypeCall ,do: defstruct args: nil, return: %TypeGeneric{} # blah(args) 15 | # defmodule TypeCallIndirect ,do: defstruct args: nil, return: %TypeGeneric{} # blah.(args) 16 | # defmodule TypeModule ,do: defstruct [] 17 | # defmodule TypePtr do 18 | # defstruct ptr: -1 19 | # def new(env, subtype \\ %TypeGeneric{}) do 20 | # {env, ptr} = HMEnv.new_counter(env, :ptr) 21 | # type = %TypePtr{ptr: ptr} 22 | # env = HMEnv.push_type_ptr(env, ptr, subtype) 23 | # {env, type} 24 | # end 25 | # end 26 | 27 | 28 | 29 | 30 | 31 | 32 | defmacro __using__(_opts) do 33 | quote bind_quoted: [] do 34 | import TypedElixir, only: [defmodulet: 2] 35 | end 36 | end 37 | 38 | 39 | defp debug(val, opts, section, prefix \\ nil) do 40 | debugOpts = opts[:debug] || [] 41 | isEnabled = true === debugOpts || section in debugOpts || :all in debugOpts 42 | if isEnabled do 43 | if prefix do 44 | IO.inspect({section, prefix, val}) 45 | val 46 | else 47 | IO.inspect({section, val}) 48 | val 49 | end 50 | else 51 | val 52 | end 53 | end 54 | 55 | 56 | defmacro defmodulet(alias_name, opts) when is_list(opts) do 57 | block_module = opts[:do] 58 | opts = Keyword.delete(opts, :do) 59 | opts = Keyword.put(opts, :environment, __CALLER__) 60 | # opts = [debug: true] ++ opts 61 | debug(:defmodulet, opts, :defmodulet) 62 | 63 | alias_name = Macro.expand(alias_name, __CALLER__) 64 | |> debug(opts, :ALIAS) 65 | 66 | block_module = block_module 67 | |> debug(opts, :AST) 68 | 69 | block_module = wrap_with_block_if_not(block_module) 70 | |> debug(opts, :BLOCK) 71 | 72 | block_module = Macro.expand(block_module, __CALLER__) 73 | |> debug(opts, :AST_EXPANDED) 74 | 75 | env = %HMEnv{} |> HMEnv.push_scope(:top) 76 | |> debug(opts, :ENV) 77 | 78 | {env, block_module} = typecheck_module(env, opts, alias_name, block_module) 79 | |> debug(opts, :TYPECHECKED) 80 | 81 | {_env, _scope} = env |> HMEnv.pop_scope(:top) 82 | |> debug(opts, :FINAL_ENV) 83 | 84 | quote do 85 | defmodule unquote(alias_name) do 86 | unquote(block_module) 87 | end 88 | end 89 | end 90 | 91 | 92 | 93 | defp wrap_with_block_if_not(nil), do: nil 94 | defp wrap_with_block_if_not({:__block__, _, _} = block), do: block 95 | defp wrap_with_block_if_not([{:__block__, _, _} = block]), do: block 96 | defp wrap_with_block_if_not(not_block), do: {:__block__, [], [not_block]} 97 | 98 | 99 | 100 | defp typecheck_module(env, opts, name, body) 101 | defp typecheck_module(env, _opts, name, nil) when is_atom(name), do: {env, nil} 102 | defp typecheck_module(env, opts, name, {:__block__, meta, body_asts}) when is_atom(name) do 103 | # environment = opts[:environment] 104 | env = HMEnv.push_scope(env, :module, name) 105 | {env, bodies} = Enum.reduce(body_asts, {env, []}, fn(module_entry_ast, {env, bodies}) -> 106 | # module_entry_ast = Macro.expand(module_entry_ast, environment) 107 | {env, body} = type_check_body(env, opts, module_entry_ast) 108 | {env, [body | bodies]} 109 | end) 110 | bodies = :lists.reverse(bodies) 111 | Enum.map(bodies, &debug(&1, opts, :MODULE_BODY, name)) 112 | # TODO: Parse out the module information to make a type-map in a callback function 113 | {env, _scope} = HMEnv.pop_scope(env, :module) 114 | {env, type} = Type.Module.new(env, %{}) 115 | ast = {:__block__, [type: type] ++ meta, bodies} 116 | {env, ast} 117 | end 118 | 119 | 120 | 121 | defp type_check_body(env, opts, module_entry_ast) 122 | defp type_check_body(env, opts, {:def, meta, [head_ast, body_ast]}) do 123 | environment = opts[:environment] 124 | head_ast = Macro.expand(head_ast, environment) 125 | body_ast = Macro.expand(body_ast, environment) 126 | {env, head} = type_check_def_head(env, opts, head_ast) # This will push scope with the name 127 | {env, body} = type_check_def_body(env, opts, body_ast) 128 | {env, func_type} = get_type_of(env, head) 129 | {env, return_type} = get_type_of(env, body) 130 | # {env, return_type} = unify_types!(env, func_type.return, return_type) 131 | # resolve_types!(env, return_type, func_type.return_type) # Can the return_type be returned by the func returns? 132 | {env, _scope} = HMEnv.pop_scope(env, :def) 133 | env = resolve_fun_return_type(env, opts, return_type, func_type.return_type) 134 | # func_type = %{func_type | return_type: return_type} 135 | ast = {:def, [type: func_type] ++ meta, [head, body]} 136 | {env, ast} 137 | end 138 | defp type_check_body(env, opts, {:@, attrMeta, [attr_ast]}) do 139 | {env, attr} = type_check_attribute(env, opts, attr_ast) 140 | ast = {:@, attrMeta, [attr]} 141 | {env, ast} 142 | end 143 | defp type_check_body(_env, _opts, module_entry_ast) do 144 | throw {:TODO_UNHANDLED_BODY_AST, module_entry_ast} 145 | end 146 | 147 | 148 | 149 | defp resolve_fun_return_type(env, opts, orig_return_type, orig_typed_return_type) do 150 | {env, return_type} = Type.get_type_or_ptr_type(env, orig_return_type) 151 | {env, typed_return_type} = Type.get_type_or_ptr_type(env, orig_typed_return_type) 152 | {env, type} = resolve_fun_return_type_(env, opts, return_type, typed_return_type) 153 | {env, subtype} = Type.Ptr.Link.new(env, type) 154 | env = case orig_typed_return_type do 155 | %Type.Ptr{} -> Type.Ptr.set(env, orig_typed_return_type, subtype) 156 | _ -> env 157 | end 158 | env 159 | end 160 | defp resolve_fun_return_type_(env, _opts, return_type, typed_return_type) 161 | # Uhh, unresolved return, so it is never set to anything but itself... 162 | defp resolve_fun_return_type_(env, _opts, %Type.Ptr.Unbound{id: id}, %Type.Ptr.Unbound{id: id}) do 163 | Type.Const.new(env, :no_return) 164 | end 165 | defp resolve_fun_return_type_(env, _opts, type, type), do: {env, type} 166 | # The typed return is not set yet, so set it to the body expression 167 | defp resolve_fun_return_type_(env, _opts, return_type, %Type.Ptr.Unbound{}) do 168 | {env, return_type} 169 | end 170 | defp resolve_fun_return_type_(env, _opts, return_type, %Type.Ptr.Generic{named: false}) do 171 | {env, return_type} 172 | end 173 | defp resolve_fun_return_type_(env, _opts, return_type, typed_return_type) do 174 | {env, type} = resolve_types!(env, return_type, typed_return_type) 175 | throw {:TODO_RESOLVE_FUN_RETURN_TYPE_UNKNOWN, type, :<, return_type, typed_return_type} 176 | {env, typed_return_type} 177 | end 178 | 179 | 180 | 181 | defp type_check_attribute(env, opts, attr_ast) 182 | defp type_check_attribute(env, opts, {:spec, specMeta, [spec_ast]}) do 183 | {env, type} = parse_type_from_specattr(env, opts, spec_ast) 184 | ast = {:spec, [type: type] ++ specMeta, [spec_ast]} 185 | {env, ast} 186 | end 187 | defp type_check_attribute(env, opts, {:type, specMeta, [spec_ast]}) do 188 | {env, type} = parse_type_from_typeattr(env, opts, spec_ast) 189 | ast = {:type, [type: type] ++ specMeta, [spec_ast]} 190 | {env, ast} 191 | end 192 | defp type_check_attribute(env, _opts, {:moduledoc, _, _}=attr_ast) do 193 | {env, attr_ast} 194 | end 195 | defp type_check_attribute(env, _opts, {:doc, _, _}=attr_ast) do 196 | {env, attr_ast} 197 | end 198 | defp type_check_attribute(env, _opts, attr_ast) do 199 | IO.inspect {:UNKNOWN_ATTRIBUTE, attr_ast} 200 | {env, attr_ast} 201 | end 202 | 203 | 204 | 205 | defp type_check_def_body(env, opts, body_ast) 206 | defp type_check_def_body(env, opts, [do: expression_or_block]) do 207 | environment = opts[:environment] 208 | expression_or_block = Macro.expand(expression_or_block, environment) 209 | {env, do_ast} = type_check_expression(env, opts, expression_or_block) 210 | ast = [do: do_ast] 211 | {env, ast} 212 | end 213 | defp type_check_def_body(_env, _opts, body_ast) do 214 | throw {:TODO_DEF_BODY, body_ast} 215 | end 216 | 217 | 218 | 219 | defp type_check_expression(env, opts, expression_or_block) 220 | # General constants 221 | defp type_check_expression(env, _opts, const) 222 | when is_atom(const) 223 | when is_integer(const) 224 | when is_float(const), do: {env, const} 225 | # Bindings 226 | defp type_check_expression(env, _opts, {name, meta, scope}) when is_atom(name) and is_atom(scope) do 227 | key = {:binding, name} 228 | case HMEnv.get_type(env, key) do 229 | nil -> throw {:BINDING_NOT_FOUND, name} 230 | type -> 231 | ast = {name, [type: type] ++ meta, scope} 232 | {env, ast} 233 | end 234 | end 235 | # Special constructs in Elixir 236 | defp type_check_expression(env, opts, {:=, bindMeta, [binding_ast, expr_ast]}) do 237 | environment = opts[:environment] 238 | binding_ast = Macro.expand(binding_ast, environment) 239 | expr_ast = Macro.expand(expr_ast, environment) 240 | {env, expr} = type_check_expression(env, opts, expr_ast) 241 | {env, exprType} = get_type_of(env, expr) 242 | {env, binding} = type_check_binding(env, opts, binding_ast, exprType) 243 | {env, bindingType} = get_type_of(env, binding) 244 | case resolve_types!(env, exprType, bindingType) do 245 | {_env, %Type.Const{const: :no_return}} -> # Do not allow :no_return as a return type, because it will never happen.. 246 | throw {:INVALID_ASSIGNMENT_NOT_ALLOWED, :no_return} 247 | {env, type} -> 248 | ast = {:=, [type: type] ++ bindMeta, [binding, expr]} 249 | {env, ast} 250 | end 251 | end 252 | # Direct function call 253 | defp type_check_expression(env, opts, {name, meta, args_ast}) when is_atom(name) and is_list(args_ast) do 254 | environment = opts[:environment] 255 | args_ast = Enum.map(args_ast, &Macro.expand(&1, environment)) 256 | arity = length(args_ast) 257 | key = {:function, name, arity} 258 | {env, args} = HMEnv.map_env(env, args_ast, &type_check_expression(&1, opts, &2)) 259 | {env, args_types} = HMEnv.map_env(env, args, &get_type_of/2) 260 | case HMEnv.get_type(env, key) do 261 | nil -> 262 | throw {:FUNCTION_NOT_FOUND, name, arity, env.types} 263 | %Type.Func{args_types: call_args, return_type: return_type} when length(call_args)===arity -> 264 | state = %{ 265 | return_type: return_type, 266 | ids: %{} 267 | } 268 | state = Enum.zip(args_types, call_args) |> Enum.reduce(state, fn({fromType, intoType}, state) -> 269 | {_, base_return_type} = Type.get_type_or_ptr_type(env, state.return_type) 270 | case Type.get_type_or_ptr_type(env, intoType) do 271 | {_env, %Type.Ptr.Generic{named: false}} -> # If putting into an unnamed generic, it always works 272 | state 273 | {_env, %Type.Ptr.Generic{id: id, named: true}=t} -> # Else if named generic, make sure same type as a prior used one if another 274 | state = 275 | if t === base_return_type do 276 | %{state | return_type: fromType} 277 | else 278 | state 279 | end 280 | case state[id] do 281 | nil -> 282 | ids = Map.put_new(state.ids, id, fromType) 283 | %{state | ids: ids} 284 | priorFromType -> 285 | unify_types!(env, priorFromType, fromType) 286 | state 287 | end 288 | {_env, _type} -> 289 | resolve_types!(env, fromType, intoType) 290 | state 291 | end 292 | # unify_types!(env, fromType, intoType) 293 | # resolve_types!(env, fromType, intoType) 294 | end) 295 | ast = {name, [type: state.return_type] ++ meta, args} 296 | {env, ast} 297 | invalidType -> throw {:DEF_HEAD_DEFNAME, "Invalid type for name", name, meta, args, invalidType, env} 298 | end 299 | end 300 | defp type_check_expression(_env, _opts, expression_or_block) do 301 | throw {:TODO_EXPRESSION, expression_or_block} 302 | end 303 | 304 | 305 | 306 | defp type_check_def_head(env, opts, head_ast) 307 | defp type_check_def_head(env, opts, {:when, whenMeta, [{name, _meta, _args_ast}=head_ast, when_ast]}) when is_atom(name) do 308 | {env, head} = type_check_def_head_when(env, opts, head_ast, when_ast) 309 | ast = {:when, whenMeta, [head, when_ast]} 310 | {env, ast} 311 | end 312 | defp type_check_def_head(env, opts, {name, _meta, _args_ast}=head_ast) when is_atom(name) do 313 | type_check_def_head_when(env, opts, head_ast, nil) 314 | end 315 | 316 | 317 | 318 | defp type_check_def_head_when(env, opts, head_ast, when_ast) 319 | defp type_check_def_head_when(env, opts, {name, meta, args_ast}, when_ast) do 320 | arg_count = length(args_ast) 321 | key = {:function, name, arg_count} 322 | case HMEnv.get_type(env, key) do 323 | nil -> # No pre-defined type for this fun-name, use the inferred value 324 | {env, args} = type_check_fun_bindings(env, opts, args_ast) 325 | {env, args} = type_check_fun_when(env, opts, args, when_ast) 326 | {env, args_types} = HMEnv.map_env(env, args, &get_type_of/2) 327 | {env, return_type} = Type.Ptr.Unbound.new_ptr(env) 328 | {env, type} = Type.Func.new(env, args_types, return_type, false, nil) 329 | # Put the head type into the scope for calling it recursively 330 | env = HMEnv.push_type(env, {:function, name, arg_count}, type) 331 | # Make this function available at the global module scope before pushing a new scope 332 | env = HMEnv.push_scope(env, :def, name) 333 | ast = {name, [type: type] ++ meta, args} 334 | {env, ast} 335 | %Type.Func{args_types: args_types} = type when length(args_types)===arg_count -> 336 | {env, args} = type_check_fun_bindings(env, opts, args_ast, args_types) 337 | {env, args} = type_check_fun_when(env, opts, args, when_ast) 338 | {env, args_types} = HMEnv.map_env(env, args, &get_type_of/2) 339 | {env, _} = HMEnv.zipmap_env(env, args_types, args_types, &resolve_types!/3) 340 | # Put the head type into the scope for calling it recursively 341 | env = HMEnv.push_type(env, {:function, name, arg_count}, type) 342 | # Make this function available at the global module scope before pushing a new scope 343 | env = HMEnv.push_scope(env, :def, name) 344 | ast = {name, [type: type] ++ meta, args} 345 | {env, ast} 346 | invalidType -> throw {:DEF_HEAD_DEFNAME, "Invalid type for name", name, meta, args_ast, invalidType, env} 347 | end 348 | end 349 | 350 | 351 | 352 | defp type_check_fun_when(env, opts, args, when_ast) 353 | # There is no :when condition 354 | defp type_check_fun_when(env, _opts, args, nil) do 355 | {env, args} 356 | end 357 | # There are no args, but there is a :when condition, need to handle that in case it is closed over 358 | defp type_check_fun_when(env, _opts, [], when_ast) do 359 | throw {:TODO_REFINE_FUNC_WHEN_NO_ARGS, when_ast, env} 360 | end 361 | # There is a when condition with args 362 | defp type_check_fun_when(env, _opts, args, when_ast) do 363 | throw {:TODO_REFINE_ARGS_WITH_WHEN, args, when_ast, env} 364 | end 365 | 366 | 367 | 368 | defp type_check_fun_bindings(env, opts, args_ast) 369 | defp type_check_fun_bindings(env, opts, args_ast) do 370 | {env, args} = Enum.reduce(args_ast, {env, []}, fn(arg_ast, {env, prior}) -> 371 | {env, type} = Type.Ptr.Generic.new_ptr(env, false) 372 | {env, arg} = type_check_binding(env, opts, arg_ast, type) 373 | {env, prior ++ [arg]} 374 | end) 375 | {env, args} 376 | end 377 | defp type_check_fun_bindings(env, opts, args_ast, argTypes) do 378 | {env, args} = Enum.zip(args_ast, argTypes) |> Enum.reduce({env, []}, fn({arg_ast, arg_type}, {env, prior}) -> 379 | {env, arg} = type_check_binding(env, opts, arg_ast, arg_type) 380 | {env, prior ++ [arg]} 381 | end) 382 | {env, args} 383 | end 384 | 385 | 386 | 387 | # defp type_check_binding(env, opts, binding, type \\ nil) 388 | defp type_check_binding(env, _opts, {name, meta, scope}, type) when is_atom(name) and is_atom(scope) do 389 | {env, type} = case type do 390 | nil -> 391 | Type.Ptr.Unbound.new_ptr(env) 392 | type -> 393 | Type.Ptr.Link.new_ptr(env, type) 394 | end 395 | key = {:binding, name} 396 | env = HMEnv.push_type(env, key, type) 397 | ast = {name, [type: type] ++ meta, scope} 398 | {env, ast} 399 | end 400 | defp type_check_binding(_env, _opts, binding, type) do 401 | throw {:TODO_BINDING, binding, type} 402 | end 403 | 404 | 405 | # defp infer_type_from_funhead 406 | 407 | 408 | 409 | 410 | 411 | defp parse_type_from_typespec(env, opts, typespec_ast) 412 | defp parse_type_from_typespec(env, _opts, {:any, _meta, []}) do 413 | {env, type} = Type.Ptr.Generic.new_ptr(env, true) 414 | {env, type} 415 | end 416 | 417 | 418 | 419 | defp parse_type_from_typeattr(env, opts, type_ast) 420 | # A typespec that does not take an argument to refine the type 421 | defp parse_type_from_typeattr(env, opts, {:::, _meta, [{typename, _nameMeta, scope}, typespec_ast]}) when is_atom(scope) do 422 | {env, type} = parse_type_from_typespec(env, opts, typespec_ast) 423 | # Name the type for later use 424 | key = {:typename, typename} 425 | env = HMEnv.push_type(env, key, type) 426 | {env, type} 427 | end 428 | 429 | 430 | 431 | defp parse_type_from_specattr(env, opts, spec_ast) 432 | defp parse_type_from_specattr(env, opts, {:::, _meta, [{name, _nameMeta, args_ast}, return_ast]}) when is_atom(name) do 433 | argCount = length(args_ast) 434 | {env, args_types} = HMEnv.map_env(env, args_ast, &parse_type_from(&1, opts, &2)) 435 | {env, returnType} = parse_type_from(env, opts, return_ast) 436 | {env, type} = Type.Func.new(env, args_types, returnType, false, nil) 437 | env = HMEnv.push_type(env, {:function, name, argCount}, type) 438 | {env, type} 439 | end 440 | 441 | 442 | 443 | 444 | # defp parse_type_from_specattr_arg(env, opts, arg_ast) 445 | # defp parse_type_from_specattr_arg(env, opts, {typename, _meta, scope}) when is_atom(typename) and is_atom(scope) do 446 | # case HMEnv.get_type(env, {:typename, typename}) do 447 | # nil -> throw {:TYPENAME_DOES_NOT_EXIST, typename} 448 | # type -> {env, type} 449 | # end 450 | # end 451 | # defp parse_type_from_specattr_arg(env, opts, arg_ast) do 452 | # # Use parse_type_from on each type? 453 | # throw {:TODO_PARSE_SPEC_ARG, arg_ast} 454 | # end 455 | 456 | defp parse_type_from(env, opts, type_ast) 457 | # Constants 458 | defp parse_type_from(env, _opts, atom) when is_atom(atom) ,do: Type.Const.new(env, :atom, values: [atom]) 459 | defp parse_type_from(env, _opts, int) when is_integer(int) ,do: Type.Const.new(env, :integer, values: [int]) 460 | defp parse_type_from(env, _opts, float) when is_float(float) ,do: Type.Const.new(env, :float, values: [float]) 461 | defp parse_type_from(env, _opts, {:atom, _meta, []}) ,do: Type.Const.new(env, :atom) 462 | defp parse_type_from(env, _opts, {:integer, _meta, []}) ,do: Type.Const.new(env, :integer) 463 | defp parse_type_from(env, _opts, {:float, _meta, []}) ,do: Type.Const.new(env, :float) 464 | defp parse_type_from(env, _opts, {:any, _meta, []}) ,do: Type.Ptr.Generic.new_ptr(env, false) 465 | # Variable name lookups 466 | defp parse_type_from(env, _opts, {typename, _meta, scope}) when is_atom(typename) and is_atom(scope) do 467 | case HMEnv.get_type(env, {:typename, typename}) do 468 | nil -> throw {:TYPENAME_DOES_NOT_EXIST, typename} 469 | type -> {env, type} 470 | end 471 | end 472 | defp parse_type_from(_env, _opts, type_ast) do 473 | throw {:TODO_PARSE_TYPE, type_ast} 474 | end 475 | 476 | 477 | 478 | 479 | # Get type of 480 | defp get_type_of(env, thing) 481 | defp get_type_of(env, atom) when is_atom(atom) ,do: Type.Const.new(env, :atom, values: [atom]) 482 | defp get_type_of(env, integer) when is_integer(integer) ,do: Type.Const.new(env, :integer, values: [integer]) 483 | defp get_type_of(env, float) when is_float(float) ,do: Type.Const.new(env, :float, values: [float]) 484 | defp get_type_of(env, [do: do_ast]) ,do: get_type_of(env, do_ast) 485 | defp get_type_of(env, {_, meta, _}) ,do: {env, Keyword.fetch!(meta, :type)} 486 | 487 | 488 | 489 | 490 | 491 | 492 | # Resolve a type to a type 493 | def resolve_types!(env, fromType, intoType) do 494 | inspect {:RESOLVERINATING, env, fromType, intoType} 495 | type = resolve_types(env, fromType, intoType) 496 | if Exception.exception?(type) do 497 | raise type 498 | else 499 | type 500 | end 501 | end 502 | 503 | 504 | def resolve_types(env, fromType, intoType) do 505 | {env, fromType} = Type.get_type_or_ptr_type(env, fromType) 506 | {env, intoType} = Type.get_type_or_ptr_type(env, intoType) 507 | resolve_types_nolinks(env, fromType, intoType) 508 | end 509 | 510 | 511 | defp resolve_types_nolinks(env, fromType, intoType) 512 | defp resolve_types_nolinks(env, type, type), do: {env, type} 513 | defp resolve_types_nolinks(env, fromType, %Type.Ptr.Unbound{}=_intoType), do: {env, fromType} # Everything goes in to an Unbound 514 | defp resolve_types_nolinks(env, fromType, %Type.Ptr.Generic{named: false}=_intoType), do: {env, fromType} # Everything goes out to an unnamed generic too (good luck recovering it) 515 | # Keep TypePtr handling last 516 | # def resolve_types_nolinks(env, %Type.Ptr{ptr: fromPtr}=from, %Type.Ptr{ptr: intoPtr}=into) do 517 | # intoType = HMEnv.get_type_ptr(env, intoPtr) 518 | # intoPath = resolve_types_nolinks(env, from, intoType) 519 | # if Exception.exception?(intoPath) do 520 | # fromType = HMEnv.get_type_ptr(env, fromPtr) 521 | # resolve_types_nolinks(env, fromType, into) 522 | # else 523 | # intoPath 524 | # end 525 | # end 526 | # defp resolve_types_nolinks(env, %Type.Ptr{}=fromType, %Type.Ptr{}=intoType) do 527 | # p0 = Type.Ptr.get(env, fromType) 528 | # p1 = Type.Ptr.get(env, intoType) 529 | # case {p0, p1} do 530 | # {p0, %Type.Ptr.Unbound{}} -> {env, p0} 531 | # # {%Type.Ptr.Unbound{id: id}, %Type.Ptr.Unbound{id: id}} -> p0 532 | # # {%Type.Ptr.Generic{id: id0}, %Type.Ptr.Unbound{id: id1}} -> p0 533 | # # {%Type.Ptr.Unbound{id: fromID}, %Type.Ptr.Unbound{id: intoID}} -> 534 | # _ -> throw {:TODO_RESOLVE_2_PTRS, p0, p1} 535 | # end 536 | # end 537 | # defp resolve_types_nolinks(env, fromType, %Type.Ptr{}=intoType) do 538 | # case Type.Ptr.get(env, intoType) do 539 | # %Type.Ptr.Unbound{} -> {env, fromType} 540 | # %Type.Ptr.Generic{} -> throw {:CANNOT_RESOLVE_SPECIFIC_TYPE_TO_GENERIC, fromType} 541 | # end 542 | # end 543 | # defp resolve_types_nolinks(env, fromType, %Type.Ptr{ptr: ptr}) do 544 | # # intoType = HMEnv.get_type_ptr(env, ptr) 545 | # # resolve_types_nolinks(env, fromType, intoType) 546 | # end 547 | # defp resolve_types_nolinks(env, %Type.Ptr{ptr: ptr}, intoType) do 548 | # # fromType = HMEnv.get_type_ptr(env, ptr) 549 | # # resolve_types_nolinks(env, fromType, intoType) 550 | # end 551 | # Catch-all 552 | defp resolve_types_nolinks(env, fromType, intoType) do 553 | {env, from} = Type.get_type_or_ptr_type(env, fromType) 554 | {_env, into} = Type.get_type_or_ptr_type(env, intoType) 555 | throw {:NO_TYPE_RESOLUTION, from, into} 556 | end 557 | 558 | 559 | 560 | # Unify two types 561 | def unify_types!(env, t0, t1) do 562 | type = unify_types(env, t0, t1) 563 | if Exception.exception?(type) do 564 | raise type 565 | else 566 | type 567 | end 568 | end 569 | 570 | 571 | def unify_types(env, t0, t1) do 572 | {env, t0} = Type.get_type_or_ptr_type(env, t0) 573 | {env, t1} = Type.get_type_or_ptr_type(env, t1) 574 | unify_types_nolinks(env, t0, t1) 575 | end 576 | 577 | 578 | def unify_types_nolinks(env, t0, t1) 579 | def unify_types_nolinks(env, t, t), do: {env, t} 580 | def unify_types_nolinks(_env, %Type.Ptr.Generic{id: id0, named: true}=t0, %Type.Ptr.Generic{id: id1, named: true}=t1) when id0 !== id1 do 581 | throw {:NO_TYPE_UNIFICATION, :GENERICS_DO_NOT_MATCH, t0, t1} 582 | end 583 | def unify_types_nolinks(_env, t0, %Type.Ptr.Generic{named: false}), do: t0 584 | def unify_types_nolinks(_env, %Type.Ptr.Generic{named: false}, t1), do: t1 585 | # Keep TypePtr handling last 586 | # def unify_types_nolinks(env, %Type.Ptr{}=t0, %Type.Ptr{}=t1) do 587 | # type0 = Type.Ptr.get(env, t1) 588 | # path0 = unify_types_nolinks(env, t1, type0) 589 | # if Exception.exception?(path0) do 590 | # type1 = Type.Ptr.get(env, t0) 591 | # unify_types_nolinks(env, type1, t0) 592 | # else 593 | # path0 594 | # end 595 | # end 596 | # def unify_types_nolinks(env, t0, %Type.Ptr{ptr: ptr}) do 597 | # # t1 = HMEnv.get_type_ptr(env, ptr) 598 | # # unify_types_nolinks(env, t0, t1) 599 | # end 600 | # def unify_types_nolinks(env, %Type.Ptr{ptr: ptr}, t1) do 601 | # # t0 = HMEnv.get_type_ptr(env, ptr) 602 | # # unify_types_nolinks(env, t0, t1) 603 | # end 604 | # Catch-all 605 | def unify_types_nolinks(env, t0, t1) do 606 | {env, t0} = Type.get_type_or_ptr_type(env, t0) 607 | {_env, t1} = Type.get_type_or_ptr_type(env, t1) 608 | throw {:NO_TYPE_UNIFICATION, :NO_PATH, t0, t1} 609 | end 610 | 611 | 612 | 613 | 614 | 615 | # TODO: Make sure the return value of a function that ends in `?` only 616 | # returns a boolean() 617 | 618 | # TODO: Walk the call path of the function and see what it returns and make 619 | # sure it matches the spec 620 | 621 | 622 | 623 | 624 | # The below is too convoluted, doing the typing in-place, though that means 625 | # that all constructs must be known before used instead of just building it 626 | # up, I'm okay with that. :-) 627 | 628 | 629 | # @fun_types [:def, :defp, :defmacro, :defmacrop] 630 | # 631 | # 632 | # defmacro __using__(_opts) do 633 | # quote bind_quoted: [] do 634 | # import TypedElixir, only: [defmodulet: 2] 635 | # end 636 | # end 637 | # 638 | # defmacro defmodulet(alias_name, do: block_module) do 639 | # quote do 640 | # defmodule unquote(alias_name) do 641 | # @after_compile TypedElixir 642 | # 643 | # unquote(clear_unneeded_specs(block_module)) 644 | # 645 | # TypedElixir.type_check( 646 | # unquote(alias_name), 647 | # unquote(Macro.escape(block_module)), 648 | # __ENV__ 649 | # ) 650 | # end 651 | # end 652 | # end 653 | 654 | # # defmodulet helpers 655 | # 656 | # defp clear_unneeded_specs(block_module) 657 | # defp clear_unneeded_specs({:__block__, blockattrs, blockbody}) do 658 | # {:__block__, blockattrs, clear_unneeded_specs_module(blockbody)} 659 | # end 660 | # 661 | # defp clear_unneeded_specs_module(blockbody) 662 | # defp clear_unneeded_specs_module([]), do: [] 663 | # defp clear_unneeded_specs_module([{funtype, funattrs, funbody}|blockbody]) when funtype in @fun_types do 664 | # [{funtype, funattrs, clean_unneeded_specs_fun(funbody)} | clear_unneeded_specs_module(blockbody)] 665 | # end 666 | # defp clear_unneeded_specs_module([block|blockbody]) do 667 | # [block | clear_unneeded_specs_module(blockbody)] 668 | # end 669 | # 670 | # defp clean_unneeded_specs_fun(block_fun) 671 | # defp clean_unneeded_specs_fun(block_fun) do 672 | # Macro.prewalk(block_fun, fn 673 | # {:@, _, [{:spec, _, _}]} -> nil 674 | # {:@, _, [{:spec, _, _}]} -> nil 675 | # ast -> ast 676 | # end) 677 | # end 678 | # 679 | # # Type checking 680 | # 681 | # def type_check(module_name, module_body, module_env) 682 | # def type_check(module_name, body, env) do 683 | # body = Macro.expand(body, env) 684 | # IO.puts "Type Checking: #{inspect module_name}" 685 | # # IO.inspect {module_name, body}#, env} 686 | # # empty_context = %{ 687 | # # types: %{}, 688 | # # specs: %{} 689 | # # } 690 | # # type_check_module(module_name, body, env, empty_context) 691 | # 692 | # types = type_check_gather_types(module_name, body, env) 693 | # IO.puts "\nTypes:" 694 | # IO.inspect types 695 | # 696 | # specs = type_check_gather_specs(module_name, body, env) 697 | # IO.puts "\nSpecs:" 698 | # IO.inspect specs 699 | # 700 | # funs = type_check_gather_funs(module_name, body, env) 701 | # IO.puts "\nFuns:" 702 | # IO.inspect funs 703 | # 704 | # # check_fun_follows_spec_ = check_fun_follows_spec(module_name, body, env) 705 | # # IO.puts "\ncheck_fun_follows_spec:" 706 | # # IO.inspect check_fun_follows_spec_ 707 | # 708 | # {type_fun_warnings, type_fun_errors} = check_types(module_name, body, env) 709 | # 710 | # IO.puts "\n\n\n====== Actual Output ======\n" 711 | # 712 | # if funs !== %{} do 713 | # funs 714 | # |> Enum.filter(&(!Map.has_key?(specs, elem(&1,0)))) 715 | # |> Enum.map(fn {head, fun} -> "Fun missing specs: #{inspect head} -> #{inspect fun}" end) 716 | # |> Enum.join("\n") 717 | # |> IO.puts 718 | # end 719 | # 720 | # # IO.puts "\n" 721 | # # 722 | # # check_fun_follows_spec_ 723 | # # |> Enum.join("\n") 724 | # # |> IO.puts 725 | # 726 | # IO.puts "\n" 727 | # 728 | # # check_spec_fun_heads() 729 | # 730 | # nil 731 | # end 732 | # 733 | # 734 | # def type_check_gather_types(_module_name, {:__block__, _attrs, expressions}, _env) do 735 | # for {:@, _, [{:type, _typeattrs, body_ast}]} <- expressions, 736 | # [{:::, _, [{name_ast, _, nil}, type_ast]}] = body_ast, 737 | # into: %{} do 738 | # {name_ast, type_ast} 739 | # end 740 | # end 741 | # 742 | # 743 | # def type_check_gather_specs(_module_name, {:__block__, _attrs, expressions}, _env) do 744 | # for {:@, _, [{:spec, _specattrs, body_ast}]} <- expressions, 745 | # [{:::, _, [{name_ast, _, func_def_ast}, func_ret_ast]}] = body_ast, 746 | # name_arity = length(func_def_ast), 747 | # into: %{} do 748 | # {{name_ast, name_arity}, {func_def_ast, func_ret_ast}} 749 | # end 750 | # end 751 | # 752 | # 753 | # def type_check_gather_funs(_module_name, {:__block__, _attrs, expressions}, _env) do 754 | # funs = for expression <- expressions, 755 | # {type, attrs, [fun_head_ast, fun_do_ast]} <- [expression], 756 | # type in [:def, :defp, :defmacro, :defmacrop], 757 | # name_ast = get_name_ast_from_head(fun_head_ast), 758 | # {name, _, args_ast} = name_ast, 759 | # name_arity = length(args_ast), 760 | # defaults = Enum.filter_map(args_ast, fn 761 | # {:'\\\\', _, _} -> true 762 | # _ -> false 763 | # end, fn {_, _, [arg_name, arg_default]} -> 764 | # arg_default 765 | # end), 766 | # count_defaults = length(defaults), 767 | # name_arity_min = name_arity - count_defaults 768 | # # into: %{}, 769 | # do 770 | # if count_defaults === 0 do 771 | # {{name, name_arity}, expression} 772 | # else 773 | # [{{name, name_arity}, expression} 774 | # | build_fun_defaults(type, attrs, name, :lists.reverse(args_ast), []) 775 | # # for arity <- ((name_arity - 1) .. name_arity_min) 776 | # # do 777 | # # {{name, arity}, {type, attrs, []} 778 | # # } 779 | # # end 780 | # ] 781 | # end 782 | # end 783 | # funs |> List.flatten |> Enum.into(%{}) 784 | # end 785 | # 786 | # 787 | # defp build_fun_defaults(type, attrs, name, head_ast_reversed, req_head) 788 | # defp build_fun_defaults(type, attrs, name, [], req_head), do: [] 789 | # defp build_fun_defaults(type, attrs, name, [{:'\\\\', attr, default}|rest], req_head) do 790 | # [{{name, length(rest) + length(req_head)}, {type, attr, [:delegate_default]}} 791 | # | build_fun_defaults(type, attrs, name, rest, req_head) 792 | # ] 793 | # end 794 | # defp build_fun_defaults(type, attrs, name, [req|rest], req_head) do 795 | # build_fun_defaults(type, attrs, name, rest, [req|req_head]) 796 | # end 797 | # 798 | # 799 | # def get_name_ast_from_head({:when, _, [name_ast|_]}), do: name_ast 800 | # def get_name_ast_from_head(name_ast), do: name_ast 801 | # 802 | # def get_when_ast_from_head({:when, _, [_|when_ast]}), do: when_ast 803 | # def get_when_ast_from_head(_), do: [] 804 | # 805 | # 806 | # def check_types(module_name, body, env) 807 | # def check_types(module_name, body, env) do 808 | # IO.inspect {:check_types, module_name, body, env} 809 | # # IO.inspect Keyword.keys(env.functions) 810 | # # env.functions |> Enum.map(&IO.inspect/1) 811 | # {[], []} 812 | # end 813 | # 814 | # 815 | # # Get type of: 816 | # 817 | # # Should cache this... badly... 818 | # defp get_type_of() do 819 | # end 820 | # 821 | # def __after_compile__(env, objbin) do 822 | # {:ok, {_modname, [abstract_code: abscode]}} = :beam_lib.chunks(objbin, [:abstract_code]) 823 | # {:raw_abstract_v1, code} = abscode 824 | # code 825 | # |> Enum.reduce(%{opaques: [], types: [], specs: []}, fn 826 | # {:attribute, _line, :spec, {raw_fun, [raw_first_clause | _rest]} = newspec}, %{specs: spec} = acc -> 827 | # # IO.inspect {"Found spec", raw_fun, raw_first_clause, _rest} 828 | # %{acc | specs: [newspec|spec]} 829 | # {:attribute, _line, :type, {name, type_form, var_form} = newtype}, %{types: type} = acc -> 830 | # # IO.inspect {"Found type", name, type_form, var_form} 831 | # %{acc | types: [newtype|type]} 832 | # {:attribute, _line, :opaque, {name, type_form, var_form} = newopaque}, %{opaques: opaque} = acc -> 833 | # # IO.inspect {"Found opaque", name, type_form, var_form} 834 | # %{acc | opaques: [newopaque|opaque]} 835 | # _bc, acc -> 836 | # # IO.inspect {:blah, _bc} 837 | # acc 838 | # end) 839 | # |> IO.inspect 840 | # end 841 | # 842 | # @spec get_module_types_funspecs(atom()) :: %{} 843 | # def get_module_types_funspecs(module) do 844 | # {^module, objbin, _filename} = :code.get_object_code(module) 845 | # {:ok, {^module, [abstract_code: abscode, exports: exports]}} = :beam_lib.chunks(objbin, [:abstract_code, :exports]) 846 | # {:raw_abstract_v1, code} = abscode 847 | # code 848 | # |> Enum.reduce(%{opaques: [], types: [], specs: []}, fn 849 | # {:attribute, _line, :spec, {raw_fun, [raw_first_clause | _rest]} = newspec}, %{specs: spec} = acc -> 850 | # # IO.inspect {"Found spec", raw_fun, raw_first_clause, _rest} 851 | # %{acc | specs: [newspec|spec]} 852 | # {:attribute, _line, :type, {name, type_form, var_form} = newtype}, %{types: type} = acc -> 853 | # # IO.inspect {"Found type", name, type_form, var_form} 854 | # %{acc | types: [newtype|type]} 855 | # {:attribute, _line, :opaque, {name, type_form, var_form} = newopaque}, %{opaques: opaque} = acc -> 856 | # # IO.inspect {"Found opaque", name, type_form, var_form} 857 | # %{acc | opaques: [newopaque|opaque]} 858 | # _, acc -> acc 859 | # end) 860 | # end 861 | # 862 | # 863 | # # def check_fun_follows_spec(module_name, {:__block__, _attrs, expressions}, _env) do 864 | # # do_check_fun_follows_spec(module_name, expressions) 865 | # # end 866 | # # 867 | # # defp do_check_fun_follows_spec(module_name, expressions, spec \\ nil, funspec \\ nil) 868 | # # defp do_check_fun_follows_spec(mn, [{fun_type, _, _} = fun | expressions], nil, nil) when fun_type in @fun_types do 869 | # # IO.inspect "Blah1 #{inspect fun}" 870 | # # [msg(mn, fun, "Fun did not have a spec immediately before it") | 871 | # # do_check_fun_follows_spec(mn, expressions, nil, nil)] 872 | # # end 873 | # # defp do_check_fun_follows_spec(mn, [{fun_type, _, _} = fun | exprs], nil, funspec) when fun_type in @fun_types do 874 | # # IO.inspect "Blah2 #{inspect fun}" 875 | # # case does_spec_match_fun_name(funspec, fun) do 876 | # # nil -> do_check_fun_follows_spec(mn, exprs, nil, funspec) 877 | # # err -> 878 | # # [msg(mn, fun, "Fun does not match the prior listed spec with error of \"#{err}\"", funspec) | 879 | # # do_check_fun_follows_spec(mn, exprs, nil, nil)] 880 | # # end 881 | # # end 882 | # # defp do_check_fun_follows_spec(mn, [{fun_type, _, _} = fun | _exprs] = exprs, spec, _f) when fun_type in @fun_types do 883 | # # IO.inspect "Blah3 #{inspect fun}" 884 | # # do_check_fun_follows_spec(mn, exprs, nil, spec) 885 | # # end 886 | # # defp do_check_fun_follows_spec(mn, [{:@, _, [{:spec, _specattrs, _body_ast}]} = newspec | expressions], nil, _fs) do 887 | # # IO.inspect "Blah4 #{inspect newspec}" 888 | # # do_check_fun_follows_spec(mn, expressions, newspec, nil) 889 | # # end 890 | # # defp do_check_fun_follows_spec(mn, [{:@, _, [{:spec, _specattrs, _body_ast}]} = newspec | expressions], spec, nil) do 891 | # # IO.inspect "Blah5 #{inspect newspec}" 892 | # # [msg(mn, spec, "Spec found when prior spec already stated without function", newspec) | 893 | # # do_check_fun_follows_spec(mn, expressions, newspec, nil)] 894 | # # end 895 | # # defp do_check_fun_follows_spec(mn, [_e | expressions], nil, _fs) do 896 | # # IO.inspect "Blah6 #{inspect _e}" 897 | # # do_check_fun_follows_spec(mn, expressions, nil, nil) 898 | # # end 899 | # # defp do_check_fun_follows_spec(mn, [_e | expressions], spec, _fs) do 900 | # # IO.inspect "Blah7 #{inspect _e}" 901 | # # [msg(mn, spec, "Spec does not immediately preceed its function") | 902 | # # do_check_fun_follows_spec(mn, expressions, nil, nil)] 903 | # # end 904 | # # defp do_check_fun_follows_spec(_mn, [], nil, _fs) do 905 | # # IO.inspect "Blah8" 906 | # # [] 907 | # # end 908 | # # defp do_check_fun_follows_spec(mn, [], spec, _fs) do 909 | # # IO.inspect "Blah9" 910 | # # [msg(mn, spec, "Spec does not immediately preceed any function")] 911 | # # end 912 | # 913 | # 914 | # @opaque spec_ast :: nil 915 | # @opaque fun_ast :: nil 916 | # 917 | # @spec does_spec_match_fun_name(spec_ast, fun_ast) :: boolean() | nil 918 | # defp does_spec_match_fun_name(spec, fun) 919 | # defp does_spec_match_fun_name( 920 | # {:@, _, [{:spec, spec_attrs, [{:::, _spec_attrs, [spec_head | spec_body]}]}]}, 921 | # {fun_type, fun_attrs, [fun_head | fun_body]}) 922 | # when fun_type in @fun_types do 923 | # {spec_name, _spec_name_attr, spec_name_args} = get_name_ast_from_head(spec_head) 924 | # {fun_name, _fun_name_attr, fun_name_args} = get_name_ast_from_head(fun_head) 925 | # IO.puts "blorp\n#{inspect spec_name}\n#{inspect fun_name}" 926 | # if spec_name !== fun_name do 927 | # "Names do not match of spec #{inspect spec_name} and fun #{inspect fun_name}" 928 | # else 929 | # # TODO: Test arity here 930 | # IO.puts "TODO: arity:\n#{inspect spec_name_args}\n#{inspect fun_name_args}" 931 | # nil 932 | # end 933 | # end 934 | # defp does_spec_match_fun_name(non_spec, non_fun) do 935 | # "ERROR: Not spec or fun:\nNonSpec: #{inspect non_spec}\nNonFun: #{inspect non_fun}" 936 | # end 937 | # 938 | # 939 | # def msg(module_name, issue_obj, msg, rel_issue_obj \\ nil) 940 | # def msg(module_name, {_type, attr, _body} = issue_obj, msg, nil) do 941 | # "#{msg(module_name, attr, msg)}\t#{inspect issue_obj}\n" 942 | # end 943 | # def msg(module_name, {_type, attr, _body} = issue_obj, msg, rel_issue_obj) do 944 | # "#{msg(module_name, attr, msg)}\t#{inspect issue_obj}\n#{msg(module_name, rel_issue_obj, "Related")}" 945 | # end 946 | # def msg(module_name, [line: line], msg, _) do 947 | # "#{module_name}:#{to_string line}: #{msg}\n" 948 | # end 949 | # def msg(module_name, nil, msg, _) do 950 | # "#{module_name}: #{msg}\n" 951 | # end 952 | # def msg(module_name, issue_obj, msg, _) do 953 | # "#{module_name}: #{msg}\n\tIssue Object: #{inspect issue_obj}\n" 954 | # end 955 | 956 | end 957 | -------------------------------------------------------------------------------- /lib/typed_elixir/hm_env.ex: -------------------------------------------------------------------------------- 1 | defmodule TypedElixir.HMEnv do 2 | defstruct counters: %{}, types: %{}, scopes: [], type_ptrs: %{}, user: [] 3 | 4 | 5 | 6 | def debug?(opts, section) 7 | def debug?(%{user: opts}, section), do: debug?(opts, section) 8 | def debug?(bool, _section) when is_boolean(bool), do: bool 9 | def debug?(opts, section) do 10 | debugOpts = opts[:debug] || opts 11 | true === debugOpts || section in debugOpts || :all in debugOpts 12 | end 13 | 14 | def debug(val, opts, section, prefix \\ nil) 15 | def debug(val, %{user: opts}, section, prefix), do: debug(val, opts, section, prefix) 16 | def debug(val, opts, section, prefix) do 17 | # debugOpts = opts[:debug] || [] 18 | isEnabled = debug?(opts, section) # true === debugOpts || section in debugOpts || :all in debugOpts 19 | colors = 20 | case debug?(opts, :color) do 21 | true -> 22 | [ 23 | number: :cyan, 24 | atom: :blue, 25 | tuple: :green, 26 | map: :magenta, 27 | regex: :red, 28 | list: :yellow, 29 | boolean: :yellow, 30 | reset: :white, 31 | ] 32 | _ -> [] 33 | end 34 | if isEnabled do 35 | if prefix do 36 | IO.inspect(val, label: "#{section}:#{prefix}", syntax_colors: colors, width: 110, pretty: true) 37 | Process.sleep(5) 38 | val 39 | else 40 | IO.inspect(val, label: "#{section}", syntax_colors: colors, width: 110, pretty: true) 41 | Process.sleep(5) 42 | val 43 | end 44 | else 45 | val 46 | end 47 | end 48 | 49 | 50 | def new_counter(env, key) do 51 | {value, counters} = Map.get_and_update(env.counters, key, fn 52 | nil -> {0, 1} 53 | value -> {value, value+1} 54 | end) 55 | env = %{env | counters: counters} 56 | {env, value} 57 | end 58 | 59 | 60 | # Type helpers 61 | 62 | def push_type(env, key, type) do 63 | types = Map.update(env.types, key, [type], fn prior -> [type | prior] end) 64 | env = %{env | types: types} 65 | env = add_to_scope(env, :type, key) 66 | env 67 | end 68 | 69 | def get_type(env, key) do 70 | case env.types[key] do 71 | nil -> nil 72 | [] -> nil 73 | [type | _] -> type 74 | end 75 | end 76 | 77 | defp pop_type(env, key) do 78 | types = case env.types[key] do 79 | nil -> throw {:POP_TYPE, key, "does not exist"} 80 | [] -> throw {:POP_TYPE, key, "does not exist"} 81 | [_] -> Map.delete(env.types, key) 82 | [_ | rest] -> Map.put(env.types, key, rest) 83 | end 84 | %{env | types: types} 85 | end 86 | 87 | 88 | # Type vars 89 | 90 | def push_type_ptr(env, ptr, type) do 91 | type_ptrs = Map.update(env.type_ptrs, ptr, [type], fn prior -> [type | prior] end) 92 | env = %{env | type_ptrs: type_ptrs} 93 | # env = add_to_scope(env, :type_ptr, ptr) # Unique ID for an entire module, plus might be used elsewhere... 94 | env 95 | end 96 | 97 | def get_type_ptr(env, ptr) do 98 | case env.type_ptrs[ptr] do 99 | nil -> throw {:GET_TYPE_PTR, ptr, "does not exist", env.type_ptrs} 100 | [] -> throw {:GET_TYPE_PTR, ptr, "does not exist", env.type_ptrs} 101 | [type_ptrs | _] -> type_ptrs 102 | end 103 | end 104 | 105 | # defp pop_type_ptr(env, ptr) do 106 | # type_ptrs = case env.type_ptrs[ptr] do 107 | # nil -> throw {:POP_type_ptr, ptr, "does not exist"} 108 | # [] -> throw {:POP_type_ptr, ptr, "does not exist"} 109 | # [_] -> Map.delete(env.type_ptrs, ptr) 110 | # [_ | rest] -> Map.put(env.type_ptrs, ptr, rest) 111 | # end 112 | # %{env | type_ptrs: type_ptrs} 113 | # end 114 | 115 | def update_type_ptr(env, ptr, type) do 116 | type_ptrs = Map.update!(env.type_ptrs, ptr, fn [_ | prior] -> [type | prior] end) 117 | env = %{env | type_ptrs: type_ptrs} 118 | env 119 | end 120 | 121 | 122 | # Scope helpers 123 | 124 | def push_scope(env, id, doc \\ nil) do 125 | scopes = [{id, doc, []} | env.scopes] 126 | %{env | scopes: scopes} 127 | end 128 | 129 | def pop_scope(env, id) do 130 | [{^id, _, scope} | scopes] = env.scopes 131 | env = %{env | scopes: scopes} 132 | env = Enum.reduce(scope, env, fn ({key, value}, env) -> removed_from_scope(env, key, value) end) 133 | {env, scope} 134 | end 135 | 136 | def get_scope(env) do 137 | [scope | _scopes] = env.scopes 138 | scope 139 | end 140 | 141 | defp add_to_scope(env, key, val) do 142 | [{id, doc, scope} | restScopes] = env.scopes 143 | scopes = [{id, doc, [{key, val} | scope]} | restScopes] 144 | %{env | scopes: scopes} 145 | end 146 | 147 | defp removed_from_scope(env, key, val) 148 | defp removed_from_scope(env, :type, name), do: pop_type(env, name) 149 | # defp removed_from_scope(env, :type_ptr, id), do: pop_type_ptr(env, id) 150 | 151 | 152 | # Generic helpers 153 | 154 | def map_env(env, enumerable, func), do: map_env(env, enumerable, func, []) 155 | 156 | defp map_env(env, [], _func, reversed_results) do 157 | {env, :lists.reverse(reversed_results)} 158 | end 159 | defp map_env(env, [value | rest], func, reversed_results) do 160 | {env, result} = func.(env, value) 161 | reversed_results = [result | reversed_results] 162 | map_env(env, rest, func, reversed_results) 163 | end 164 | 165 | 166 | def zipmap_env(env, enumerableLeft, enumerableRight, func) when length(enumerableLeft) === length(enumerableRight) do 167 | zipmap_env(env, enumerableLeft, enumerableRight, func, []) 168 | end 169 | 170 | defp zipmap_env(env, [], [], _func, reversed_results) do 171 | {env, :lists.reverse(reversed_results)} 172 | end 173 | defp zipmap_env(env, [left | restLeft], [right | restRight], func, reversed_results) do 174 | {env, result} = func.(env, left, right) 175 | reversed_results = [result | reversed_results] 176 | zipmap_env(env, restLeft, restRight, func, reversed_results) 177 | end 178 | 179 | end 180 | -------------------------------------------------------------------------------- /lib/typed_elixir/type.ex: -------------------------------------------------------------------------------- 1 | defmodule TypedElixir.Type do 2 | # Oh if only Elixir already had a static type system... 3 | 4 | alias TypedElixir.HMEnv 5 | 6 | # Yes monotonic is slow, but the ordering is useful for compiling and I do not care since it is not called at run-time 7 | def unique_value, do: :erlang.unique_integer([:positive, :monotonic]) 8 | 9 | 10 | # Types 11 | defmodule Const do 12 | defstruct [:const, :meta] 13 | def new(env, const, meta \\ []) when is_atom(const) and is_list(meta) do 14 | meta = Enum.into(meta, %{}) 15 | type = %Const{const: const, meta: meta} 16 | {env, type} 17 | end 18 | end 19 | 20 | 21 | 22 | 23 | defmodule Ptr do 24 | defstruct [:ptr] 25 | 26 | defmodule Link do 27 | defstruct [:type] 28 | def new(env, type), do: {env, %Link{type: type}} 29 | def new_ptr(env, type) do 30 | {env, ptrType} = new(env, type) 31 | Ptr.new(env, ptrType) 32 | end 33 | end 34 | 35 | defmodule Unbound do 36 | defstruct [:id, :depth] 37 | def new(env, depth \\ 0) do 38 | # {env, id} = HMEnv.new_counter(env, :tval) 39 | id = TypedElixir.Type.unique_value() 40 | {env, %Unbound{id: id, depth: depth}} 41 | end 42 | def new_ptr(env, depth \\ 0) do 43 | {env, ptrType} = new(env, depth) 44 | Ptr.new(env, ptrType) 45 | end 46 | end 47 | 48 | defmodule Generic do 49 | defstruct [:id, :named, :meta] 50 | def new(env, named, meta \\ []) when is_boolean(named) and is_list(meta) do 51 | # {env, id} = HMEnv.new_counter(env, :tval) 52 | id = TypedElixir.Type.unique_value() 53 | {env, %Generic{id: id, named: named, meta: meta}} 54 | end 55 | def new_ptr(env, named, meta \\ []) do 56 | {env, ptrType} = new(env, named, meta) 57 | Ptr.new(env, ptrType) 58 | end 59 | end 60 | 61 | 62 | 63 | def new(env, %Unbound{}=ptype) ,do: new_withtype(env, ptype) 64 | def new(env, %Generic{}=ptype) ,do: new_withtype(env, ptype) 65 | def new(env, %Link{}=ptype) ,do: new_withtype(env, ptype) 66 | defp new_withtype(env, ptype) do 67 | # {env, ptr} = HMEnv.new_counter(env, :ptr) 68 | ptr = TypedElixir.Type.unique_value() 69 | type = %Ptr{ptr: ptr} 70 | env = HMEnv.push_type_ptr(env, ptr, ptype) 71 | {env, type} 72 | end 73 | 74 | def get(env, ptr) 75 | def get(env, %Ptr{ptr: ptr}) do 76 | HMEnv.get_type_ptr(env, ptr) 77 | end 78 | 79 | def set(env, ptr, value) 80 | def set(env, %Ptr{ptr: ptr}, %Unbound{}=value) ,do: HMEnv.update_type_ptr(env, ptr, value) 81 | def set(env, %Ptr{ptr: ptr}, %Generic{}=value) ,do: HMEnv.update_type_ptr(env, ptr, value) 82 | def set(env, %Ptr{ptr: ptr}, %Link{}=value) ,do: HMEnv.update_type_ptr(env, ptr, value) 83 | def set(env, ptr, to_link) do 84 | {env, link} = Link.new(env, to_link) 85 | set(env, ptr, link) 86 | end 87 | 88 | def set_deep(env, ptr, value), do: set_deep(env, ptr, value, []) 89 | def set_deep(env, %Ptr{}=ptr, value, cache) do 90 | if ptr in cache do 91 | set(env, ptr, value) 92 | else 93 | case get(env, ptr) do 94 | %Ptr.Link{type: %Ptr{}=deep} -> set_deep(env, deep, value, [ptr | cache]) 95 | _ -> set(env, ptr, value) 96 | end 97 | end 98 | end 99 | 100 | end 101 | 102 | 103 | 104 | 105 | defmodule App do 106 | defstruct [:type, :args_types, :meta] 107 | # args_types is [{:name, type}] 108 | def new(env, inner_type, args_types, meta \\ []) when is_list(args_types) and is_list(meta) do 109 | meta = Enum.into(meta, %{}) 110 | type = %App{type: inner_type, args_types: args_types, meta: meta} 111 | {env, type} 112 | end 113 | 114 | def get_type(env, %App{type: type}), do: get_type(env, type) 115 | def get_type(env, type) do 116 | {env, resolved} = TypedElixir.Type.get_resolved_type(env, type) 117 | case resolved do 118 | %App{} = app -> get_type(env, app) 119 | t -> {env, t} 120 | end 121 | end 122 | 123 | def refine(env, app, overrides), do: refine(env, app, overrides, 0) 124 | # defp refine(env, %App{}=app, [], _pos_index), do: {env, app} 125 | defp refine(env, %App{type: type, args_types: args_types} = app, [], _pos_index) do 126 | Enum.any?(args_types, fn{_, type} -> 127 | {_env, resolved} = TypedElixir.Type.get_type_or_ptr_type(env, type) 128 | case resolved do 129 | %TypedElixir.Type.Ptr.Unbound{} -> true 130 | _ -> false 131 | end 132 | end) 133 | |> case do 134 | false -> {env, type} # Fully applied, return the final type 135 | true -> {env, app} # We are still being applied... 136 | end 137 | end 138 | # By name 139 | defp refine(env, %App{}=app, [{name, new_type} | overrides], pos_index) when is_atom(name) do 140 | args_types = app.args_types 141 | type = app.type 142 | case List.keyfind(args_types, name, 0, nil) do 143 | nil -> throw {:TYPE_NAME_NOT_FOUND_ON_APP_TYPE, name, new_type, app} 144 | {^name, old_type} -> 145 | {env, ptr} = Ptr.Link.new_ptr(env, new_type) 146 | args_types = List.keyreplace(args_types, name, 0, {name, ptr}) 147 | {env, type} = 148 | TypedElixir.Type.map_types(env, type, fn 149 | (env, ^old_type) -> {env, ptr} 150 | (env, t) -> {env, t} 151 | end) 152 | app = %{app | args_types: args_types, type: type} 153 | refine(env, app, overrides, pos_index) 154 | end 155 | end 156 | # By position 157 | defp refine(env, %App{}=app, [new_type | overrides], pos_index) when is_map(new_type) do # is_struct 158 | args_types = app.args_types 159 | type = app.type 160 | case Enum.at(args_types, pos_index, nil) do 161 | nil -> throw {:TYPE_POST_NOT_FOUND_ON_APP_TYPE, pos_index, new_type, app} 162 | {name, old_type} -> 163 | {env, ptr} = Ptr.Link.new_ptr(env, new_type) 164 | args_types = List.replace_at(args_types, pos_index, {name, ptr}) 165 | {env, type} = 166 | TypedElixir.Type.map_types(env, type, fn 167 | (env, ^old_type) -> {env, ptr} 168 | (env, t) -> {env, t} 169 | end) 170 | app = %{app | args_types: args_types, type: type} 171 | refine(env, app, overrides, pos_index) 172 | end 173 | end 174 | end 175 | 176 | 177 | 178 | 179 | defmodule Func do 180 | # call is {modules, func_name, args_count} when is_list_of_atoms(modules) and is_atom(func_name) and is_integer(args_count) 181 | defstruct [:args_types, :return_type, :is_indirect, :call, :meta] 182 | def new(env, args_types, return_type, is_indirect, call, meta \\ []) 183 | def new(env, args_types, return_type, is_indirect, {modules, func_name, args_count} = call, meta) 184 | when is_list(args_types) and is_map(return_type) and is_boolean(is_indirect) and is_list(modules) 185 | and is_atom(func_name) and is_integer(args_count) and is_list(meta) do 186 | meta = Enum.into(meta, %{}) 187 | type = %Func{args_types: args_types, return_type: return_type, is_indirect: is_indirect, call: call, meta: meta} 188 | {env, type} 189 | end 190 | def new(env, args_types, return_type, is_indirect, nil = call, meta) 191 | when is_list(args_types) and is_map(return_type) and is_boolean(is_indirect) and is_list(meta) do 192 | meta = Enum.into(meta, %{}) 193 | type = %Func{args_types: args_types, return_type: return_type, is_indirect: is_indirect, call: call, meta: meta} 194 | {env, type} 195 | end 196 | 197 | def get_func_return_list(env, %Func{return_type: return_type} = func) do 198 | {env, return_type} = TypedElixir.Type.get_resolved_type(env, return_type) 199 | {env, return} = get_func_return_list(env, return_type) 200 | {env, [func | return]} 201 | end 202 | def get_func_return_list(env, _type), do: {env, []} 203 | 204 | def unbind_generics_arg(env, arg_type) 205 | def unbind_generics_arg(env, %Ptr{} = ptr) do 206 | case Ptr.get(env, ptr) do 207 | %Ptr.Generic{id: id, named: false} -> 208 | key = {:unbind_generics_arg, id} 209 | case HMEnv.get_type(env, key) do 210 | nil -> 211 | {env, ptr} = Ptr.Unbound.new_ptr(env) 212 | env = HMEnv.push_type(env, key, ptr) 213 | {env, ptr |> HMEnv.debug(env, :func_unbind_generic, :unset_generic)} 214 | ptr -> {env, ptr |> HMEnv.debug(env, :func_unbind_generic, :set_generic)} 215 | end 216 | %Ptr.Link{type: link} -> TypedElixir.Type.map_types(env, link, &unbind_generics_arg/2) 217 | _ -> {env, ptr |> HMEnv.debug(env, :func_unbind_generic, :link)} 218 | end 219 | end 220 | def unbind_generics_arg(env, arg_type), do: {env, arg_type |> HMEnv.debug(env, :func_unbind_generic, :default)} 221 | def unbind_generics(env, %Func{args_types: args_types, return_type: return_type} = type) do 222 | type |> HMEnv.debug(env, :func_unbind_generic, :start) 223 | env = HMEnv.push_scope(env, :Func_unbind_generics) 224 | {env, args_types} = TypedElixir.Type.map_types(env, args_types, &unbind_generics_arg/2) 225 | {env, return_type} = unbind_generics_arg(env, return_type) 226 | {env, _scope_data} = HMEnv.pop_scope(env, :Func_unbind_generics) 227 | type = %{type | args_types: args_types, return_type: return_type} 228 | {env, type} 229 | end 230 | 231 | # def unboundify(env, %Func{args_types: args_types, return_type: return_type} = type) do 232 | # {env, args_types_mappings} = 233 | # HMEnv.map_env(env, args_types, fn(env, arg_type) -> 234 | # {env, arg_type} = TypedElixir.Type.map_types(env, arg_type, [:ptr_recurse], fn 235 | # (env, %Ptr.Generic{id: id}) -> 236 | # {env, unbound} = Ptr.Unbound.new(env) 237 | # {env, id} 238 | # end) 239 | # # case TypedElixir.Type.get_type_or_ptr_type(env, arg_type) do 240 | # # {env, %Ptr.Generic{id: id}}-> 241 | # # {env, ptr} = Ptr.Unbound.new(env) 242 | # # {env, {id, ptr}} 243 | # # {env, arg} -> {env, {nil, arg}} 244 | # # end 245 | # end) 246 | # args_types = Enum.map(args_types_mappings, &elem(&1, 1)) 247 | # {env, return_type} = 248 | # case TypedElixir.Type.get_type_or_ptr_type(env, return_type) do 249 | # {env, %Ptr.Generic{id: id}}-> 250 | # case :lists.keyfind(id, 1, args_types_mappings) do 251 | # {^id, return_type} -> {env, return_type} 252 | # _ -> {env, return_type} 253 | # end 254 | # {env, _type} -> {env, return_type} 255 | # end 256 | # type = %{type | args_types: args_types, return_type: return_type} 257 | # {env, type} 258 | # end 259 | end 260 | 261 | 262 | 263 | 264 | defmodule Module do 265 | defstruct [:types, :meta] 266 | def new(env, types, meta \\ []) when is_map(types) and is_list(meta) do 267 | meta = Enum.into(meta, %{}) 268 | type = %Module{types: types, meta: meta} 269 | {env, type} 270 | end 271 | end 272 | 273 | 274 | 275 | 276 | defmodule GADT do 277 | # :heads should be [{atom_HeadName, type} || atom_HeadName] 278 | defstruct [:heads, :meta] 279 | def new(env, heads, meta \\ []) when is_list(heads) and is_list(meta) do 280 | meta = Enum.into(meta, %{}) 281 | type = %GADT{heads: heads, meta: meta} 282 | {env, type} 283 | end 284 | end 285 | 286 | 287 | 288 | defmodule Tuple do 289 | # :elements should be [types] 290 | defstruct [:elements, :meta] 291 | def new(env, elements, meta \\ []) when is_list(elements) and is_list(meta) do 292 | meta = Enum.into(meta, %{}) 293 | type = %Tuple{elements: elements, meta: meta} 294 | {env, type} 295 | end 296 | end 297 | 298 | 299 | 300 | 301 | defmodule Record do 302 | # :labels should be [{atom_Label, type}] 303 | defstruct [:labels, :meta] 304 | def new(env, labels, meta \\ []) when is_list(labels) and is_list(meta) do 305 | labels_no_dups = Enum.uniq_by(labels, &elem(&1, 0)) 306 | if(length(labels_no_dups) != length(labels), do: throw {:RECORD_DUPLICATE_LABELS, Keyword.keys(labels -- labels_no_dups)}) 307 | meta = Enum.into(meta, %{}) 308 | type = %Record{labels: labels, meta: meta} 309 | {env, type} 310 | end 311 | 312 | defmodule Unresolved do 313 | defstruct [:labels, :meta] 314 | def new(env, labels, meta \\ []) when is_list(labels) and is_list(meta) do 315 | meta = Enum.into(meta, %{}) 316 | type = %Record{labels: labels, meta: meta} 317 | {env, type} 318 | end 319 | 320 | def resolve(env, %Unresolved{labels: labels, meta: meta}) do 321 | {resolved_labels, resolved_meta} = 322 | labels 323 | |> Enum.reduce({%{}, %{}}, fn 324 | ({:+, type}, {l, m}) -> 325 | case TypedElixir.Type.get_resolved_type(env, type) do 326 | {_env, %Record{labels: addLabels, meta: addMeta}} -> 327 | newLabels = Map.merge(l, addLabels, &throw({:RECORD_DUPLICATE_LABEL_RESOLVE, elem(&1, 0), elem(&1, 1)})) 328 | newMeta = Map.merge(m, addMeta) 329 | {newLabels, newMeta} 330 | end 331 | end) 332 | resolved_labels = Enum.into(resolved_labels, []) 333 | resolved_meta = Map.merge(meta, resolved_meta) |> Enum.into([]) 334 | Record.new(env, resolved_labels, resolved_meta) 335 | end 336 | end 337 | 338 | def extend(env, record, new_labels) 339 | def extend(env, %Record{} = record, []), do: {env, record} 340 | def extend(env, %Record{labels: labels, meta: meta}, [{nl, _nt} = new_label | rest]) do 341 | case Enum.find(labels, nil, &(elem(&1, 0)==nl)) do 342 | nil -> 343 | new_labels = labels ++ [new_label] # Keep the ordering... 344 | type = %Record{labels: new_labels, meta: meta} 345 | extend(env, type, rest) 346 | label -> throw {:RECORD_EXTENDED_DUPLICATE_LABEL, label} 347 | end 348 | end 349 | end 350 | 351 | 352 | 353 | 354 | # Helpers 355 | 356 | def get_resolved_type(env, type) 357 | def get_resolved_type(env, %Ptr{}=tvar) do 358 | case Ptr.get(env, tvar) do 359 | %Ptr.Link{type: type} -> get_resolved_type(env, type) 360 | _ -> {env, tvar} 361 | end 362 | end 363 | def get_resolved_type(env, type), do: {env, type} 364 | 365 | 366 | def get_type_or_ptr_type(env, type) 367 | def get_type_or_ptr_type(env, %Ptr{}=tvar) do 368 | case Ptr.get(env, tvar) do 369 | %Ptr.Link{type: type} -> get_type_or_ptr_type(env, type) 370 | ptrType -> {env, ptrType} 371 | end 372 | end 373 | def get_type_or_ptr_type(env, type), do: {env, type} 374 | 375 | 376 | def simple_description_of_type(type) 377 | def simple_description_of_type(%Const{const: const}), do: to_string(const) 378 | def simple_description_of_type(type), do: inspect(type) 379 | 380 | 381 | def debug_get_recursive_type_or_ptr_type(env, type) 382 | def debug_get_recursive_type_or_ptr_type(type, %HMEnv{}=env), do: debug_get_recursive_type_or_ptr_type(env, type) 383 | def debug_get_recursive_type_or_ptr_type(%HMEnv{}=env, types) when is_list(types) do 384 | Enum.map(types, &debug_get_recursive_type_or_ptr_type(env, &1, 3)) 385 | end 386 | def debug_get_recursive_type_or_ptr_type(%HMEnv{}=env, type), do: debug_get_recursive_type_or_ptr_type(env, type, 3) 387 | def debug_get_recursive_type_or_ptr_type(env, type, max_depth) 388 | def debug_get_recursive_type_or_ptr_type(_env, type, 0), do: type 389 | def debug_get_recursive_type_or_ptr_type(env, type, max_depth) do 390 | max_depth = max_depth - 1 391 | {env, type} = get_type_or_ptr_type(env, type) 392 | case type do 393 | %Ptr.Generic{} -> type 394 | %Ptr.Unbound{} -> type 395 | %Const{} -> type 396 | %Func{args_types: args_types, return_type: return_type} -> 397 | args_types = Enum.map(args_types, &debug_get_recursive_type_or_ptr_type(env, &1, max_depth)) 398 | return_type = debug_get_recursive_type_or_ptr_type(env, return_type, max_depth) 399 | %{type | args_types: args_types, return_type: return_type} 400 | %Tuple{elements: elements} -> 401 | elements = Enum.map(elements, &debug_get_recursive_type_or_ptr_type(env, &1, max_depth)) 402 | %{type | elements: elements} 403 | end 404 | end 405 | 406 | 407 | # Depth-first pass 408 | def map_types(env, type, opts \\ [], callback) 409 | def map_types(env, types, opts, callback) when is_list(types) do 410 | HMEnv.map_env(env, types, &map_types(&1, &2, opts, callback)) 411 | end 412 | def map_types(env, %Const{} = type, _opts, callback), do: callback.(env, type) 413 | def map_types(env, %App{type: inner_type} = type, _opts, callback) do 414 | {env, new_type} = callback.(env, inner_type) 415 | type = %{type | type: new_type} 416 | callback.(env, type) 417 | end 418 | def map_types(env, %Func{args_types: args_types, return_type: return_type} = type, opts, callback) do 419 | {env, args_types} = map_types(env, args_types, opts, callback) 420 | {env, return_type} = map_types(env, return_type, opts, callback) 421 | type = %{type | args_types: args_types, return_type: return_type} 422 | callback.(env, type) 423 | end 424 | def map_types(env, %Module{types: types} = type, opts, callback) do 425 | {env, types} = 426 | HMEnv.map_env(env, Enum.into(types, []), fn (env, {name, type}) -> 427 | {env, t} = map_types(env, type, opts, callback) 428 | {env, {name, t}} 429 | end) 430 | types = Enum.into(types, %{}) 431 | type = %{type | types: types} 432 | callback.(env, type) 433 | end 434 | def map_types(env, %Record{labels: labels} = type, opts, callback) do 435 | {env, typed_labels} = 436 | HMEnv.map_env(env, labels, fn (env, {label, type}) -> 437 | {env, t} = map_types(env, type, opts, callback) 438 | {env, {label, t}} 439 | end) 440 | type = %{type | labels: typed_labels} 441 | callback.(env, type) 442 | end 443 | def map_types(env, %Tuple{elements: elements} = type, opts, callback) do 444 | {env, typed_elements} = map_types(env, elements, opts, callback) 445 | type = %{type | elements: typed_elements} 446 | callback.(env, type) 447 | end 448 | def map_types(env, %Ptr{ptr: ptr} = type, opts, callback) do 449 | ptr = 450 | if :ptr_recurse in opts do 451 | throw {:TODO, :map_types_recurse_not_implemented_yet} 452 | else 453 | ptr 454 | end 455 | type = %{type | ptr: ptr} 456 | callback.(env, type) 457 | end 458 | def map_types(_env, types, _opts, _callback) do 459 | throw {:TODO, :unhandled_type, types} 460 | end 461 | 462 | 463 | 464 | def generify_unbound(env, type) 465 | def generify_unbound(env, types) when is_list(types) do 466 | HMEnv.map_env(env, types, &generify_unbound/2) 467 | end 468 | def generify_unbound(env, %Ptr{} = ptr) do 469 | case Ptr.get(env, ptr) do 470 | %Ptr.Unbound{} -> 471 | {env, generic} = Ptr.Generic.new(env, false) 472 | env = Ptr.set(env, ptr, generic) 473 | {env, ptr} 474 | %Ptr.Link{type: linked} = link -> 475 | case generify_unbound(env, linked) do 476 | {env, ^linked} -> {env, ptr} 477 | {env, linked} -> 478 | link = %{link | type: linked} 479 | env = Ptr.set(env, ptr, link) 480 | {env, ptr} 481 | end 482 | %Ptr.Generic{named: _is_named} -> {env, ptr} 483 | end 484 | end 485 | def generify_unbound(env, %Func{args_types: args_types, return_type: return_type} = type) do 486 | {env, args_types} = HMEnv.map_env(env, args_types, &generify_unbound/2) 487 | {env, return_type} = generify_unbound(env, return_type) 488 | type = %{type | args_types: args_types, return_type: return_type} 489 | {env, type} 490 | end 491 | def generify_unbound(env, %Record{labels: labels} = type) do 492 | {env, labels} = 493 | HMEnv.map_env(env, labels, fn(env, {_name, type}) -> 494 | generify_unbound(env, type) 495 | end) 496 | type = %{type | labels: labels} 497 | {env, type} 498 | end 499 | def generify_unbound(env, %Tuple{elements: elements} = type) do 500 | {env, elements} = HMEnv.map_env(env, elements, &generify_unbound/2) 501 | type = %{type | elements: elements} 502 | {env, type} 503 | end 504 | def generify_unbound(env, %Const{} = type), do: {env, type} 505 | def generify_unbound(_env, type) do 506 | throw {:TODO, :generify_unbound_unhandled, type} 507 | end 508 | 509 | 510 | 511 | 512 | # def unboundify_generic(env, type) 513 | # def unboundify_generic(env, %Ptr{} = ptr) do 514 | # case Ptr.get(env, ptr) do 515 | # %Ptr.Unbound{} -> {env, ptr} 516 | # %Ptr.Generic{named: false} -> Ptr.Unbound.new_ptr(env, false) 517 | # %Ptr.Generic{named: true} -> Ptr.Generic.new_ptr(env, true) 518 | # %Ptr.Link{type: linked} -> 519 | # {env, new_linked} = unboundify_generic(env, linked) 520 | # if linked === new_linked do 521 | # {env, ptr} 522 | # else 523 | # Ptr.Link.new_ptr(env, new_linked) 524 | # end 525 | # end 526 | # end 527 | # def unboundify_generic(env, %Const{} = type), do: {env, type} 528 | # def unboundify_generic(env, %Record{labels: labels} = type) do 529 | # {env, labels} = 530 | # HMEnv.map_env(env, labels, fn(env, {_name, type}) -> 531 | # generify_unbound(env, type) 532 | # end) 533 | # type = %{type | labels: labels} 534 | # {env, type} 535 | # end 536 | # def unboundify_generic(env, %Tuple{elements: elements} = type) do 537 | # {env, elements} = HMEnv.map_env(env, elements, &unboundify_generic/2) 538 | # type = %{type | elements: elements} 539 | # {env, type} 540 | # end 541 | # def unboundify_generic(env, %Func{args_types: args_types, return_type: return_type} = type) do 542 | # {env, args_types} = HMEnv.map_env(env, args_types, &unboundify_generic/2) 543 | # {env, return_type} = unboundify_generic(env, return_type) 544 | # type = %{type | args_types: args_types, return_type: return_type} 545 | # {env, type} 546 | # end 547 | # def unboundify_generic(_env, type) do 548 | # throw {:TODO, :unboundify_generic_unhandled, type} 549 | # end 550 | 551 | 552 | 553 | 554 | # Resolve a type to a type 555 | def resolve_types!(env, fromType, intoType) do 556 | # inspect {:RESOLVERINATING, env, fromType, intoType} 557 | HMEnv.debug(:initialize, env, :resolving) 558 | type = resolve_types(env, fromType, intoType) 559 | if Exception.exception?(type) do 560 | raise type 561 | else 562 | type 563 | end 564 | end 565 | 566 | 567 | def resolve_types(env, from, into) do 568 | {from, into} |> HMEnv.debug(env, :resolving, :start) 569 | {env, fromType} = get_type_or_ptr_type(env, from) 570 | {env, intoType} = get_type_or_ptr_type(env, into) 571 | {fromType, intoType} |> HMEnv.debug(env, :resolving, :pre) 572 | {env, type} = resolve_types_nolinks(env, from, fromType, into, intoType) 573 | type |> HMEnv.debug(env, :resolving, :end) 574 | {env, type} 575 | end 576 | 577 | 578 | defp resolve_types(env, _baseFrom, %Ptr{}=from, baseInto, into) do 579 | {env, fromType} = get_type_or_ptr_type(env, from) 580 | {env, intoType} = get_type_or_ptr_type(env, into) 581 | {env, type} = resolve_types_nolinks(env, from, fromType, baseInto, intoType) 582 | {env, type} 583 | end 584 | defp resolve_types(env, baseFrom, from, baseInto, into) do 585 | {env, fromType} = get_type_or_ptr_type(env, from) 586 | {env, intoType} = get_type_or_ptr_type(env, into) 587 | {env, type} = resolve_types_nolinks(env, baseFrom, fromType, baseInto, intoType) 588 | {env, type} 589 | end 590 | 591 | 592 | defp resolve_types_nolinks(env, from, fromType, into, intoType) 593 | defp resolve_types_nolinks(env, _from, type, _into, type), do: {env, type} 594 | defp resolve_types_nolinks(env, _from, %Const{const: const_type} = type, _into, %Const{const: const_type}) do 595 | # TODO: Verify the metas too... 596 | {env, type} 597 | end 598 | defp resolve_types_nolinks(env, %Ptr{}=ptr, %Ptr.Unbound{}=un, into, intoType) do # Can also refine an unbound too 599 | {ptr, un, into, intoType} |> HMEnv.debug(env, :resolving, :unbound_set) 600 | env = Ptr.set_deep(env, ptr, into) 601 | {env, ptr} 602 | end 603 | defp resolve_types_nolinks(env, from, _fromType, _into, %Ptr.Unbound{}=_intoType), do: {env, from} # Everything goes in to an Unbound 604 | defp resolve_types_nolinks(env, from, fromType, into, %Ptr.Generic{id: id, named: _named}=_intoType) do # Everything goes in to a generic 605 | # key = {:generic_id, id} 606 | # IO.inspect(id, label: :bleep) 607 | # case HMEnv.get_type(env, key) do 608 | # nil -> 609 | # env = HMEnv.push_type(env, key, fromType) 610 | # IO.inspect(id, label: :bloop) 611 | # {env, fromType} 612 | # type -> 613 | # resolve_types(env, from, fromType, type) 614 | # end 615 | key = {:generic_id, id} 616 | case HMEnv.get_type(env, key) do 617 | nil -> 618 | # {env, intoType} # Don't return the generic, return the ptr if anything... 619 | {env, fromType} 620 | intoType -> 621 | resolve_types(env, from, intoType, into, fromType) 622 | end 623 | end 624 | defp resolve_types_nolinks(env, from, %Ptr.Generic{id: id, named: _named}=_fromType, into, intoType) do # Refine generic to actual thing 625 | # key = {:generic_id, id} 626 | # case HMEnv.get_type(env, key) do 627 | # nil -> 628 | # # throw {:BLARGH, id, intoType, Map.keys(env.types)} 629 | # IO.inspect(id, label: :blorpo) 630 | # {env, nil} 631 | # fromType -> 632 | # resolve_types(env, from, fromType, intoType) 633 | # end 634 | key = {:generic_id, id} 635 | case HMEnv.get_type(env, key) do 636 | nil -> 637 | env = HMEnv.push_type(env, key, intoType) 638 | {env, intoType} 639 | type -> 640 | resolve_types(env, from, type, into, intoType) 641 | end 642 | end 643 | # defp resolve_types_nolinks(env, _from, fromType, %Ptr.Generic{named: false}=_intoType), do: {env, fromType} # Everything goes out to an unnamed generic too (good luck recovering it) 644 | # defp resolve_types_nolinks(env, _from, fromType, %Ptr.Generic{named: true}=_intoType), do: {env, fromType} # TODO: Everything also goes in to a named generic, buuuut..... 645 | # Checking if an applied type matches the other type 646 | defp resolve_types_nolinks(env, _from, %App{type: type}, _into, %App{type: type}), do: {env, type} 647 | defp resolve_types_nolinks(env, from, %App{type: fromType}, into, intoType) do 648 | resolve_types(env, from, fromType, into, intoType) 649 | # {env, fromType} = get_type_or_ptr_type(env, fromType) 650 | # resolve_types_nolinks(env, fromType, intoType) 651 | end 652 | defp resolve_types_nolinks(env, from, fromType, into, %App{type: intoType}) do 653 | resolve_types(env, from, fromType, into, intoType) 654 | # {env, intoType} = get_type_or_ptr_type(env, intoType) 655 | # resolve_types_nolinks(env, fromType, intoType) 656 | end 657 | defp resolve_types_nolinks(env, _from, %Func{is_indirect: is_indirect, args_types: from_args_types, return_type: from_return_type, call: call} = type, _into, %Func{is_indirect: is_indirect, args_types: to_args_types, return_type: to_return_type, call: call}) when length(from_args_types) === length(to_args_types) do 658 | {env, args_types} = HMEnv.zipmap_env(env, from_args_types, to_args_types, &resolve_types/3) 659 | {env, return_type} = resolve_types(env, from_return_type, to_return_type) 660 | type = %{type | args_types: args_types, return_type: return_type} 661 | {env, type} 662 | end 663 | defp resolve_types_nolinks(env, _from, %Record{labels: fromLabels} = type, _into, %Record{labels: toLabels}) do 664 | fromLabels = Enum.sort(fromLabels) 665 | toLabels = Enum.sort(toLabels) 666 | if length(fromLabels) != length(toLabels), do: throw {:NO_TYPE_RESOLUTION, :RECORD_LENGTH_DOES_NOT_MATCH, Keyword.keys(fromLabels), Keyword.keys(toLabels)} 667 | {env, _labels} = 668 | HMEnv.zipmap_env(env, fromLabels, toLabels, fn 669 | (env, {label, ltype}, {label, ltype}) -> {env, ltype} 670 | (env, {label, fromType}, {label, toType}) -> resolve_types(env, fromType, toType) 671 | (_env, {fromLabel, _fromType}, {toLabel, _toType}) -> throw {:NO_TYPE_RESOLUTION, :RECORD_LABEL_MISMATCH, if(fromLabel throw {:NO_TYPE_RESOLUTION, :RECORD_FROM, label} 673 | # (_env, _, {label, _}) -> throw {:NO_TYPE_RESOLUTION, :RECORD_TO, label} 674 | end) 675 | {env, type} 676 | end 677 | defp resolve_types_nolinks(env, _from, %Tuple{elements: fromElements} = type, _into, %Tuple{elements: toElements}) when length(fromElements) === length(toElements) do 678 | {env, elements} = HMEnv.zipmap_env(env, fromElements, toElements, &resolve_types/3) 679 | {env, %{type | elements: elements}} 680 | end 681 | # Keep TypePtr handling last 682 | # def resolve_types_nolinks(env, from, %Ptr{ptr: fromPtr}=from, %Ptr{ptr: intoPtr}=into) do 683 | # intoType = HMEnv.get_type_ptr(env, intoPtr) 684 | # intoPath = resolve_types_nolinks(env, from, intoType) 685 | # if Exception.exception?(intoPath) do 686 | # fromType = HMEnv.get_type_ptr(env, fromPtr) 687 | # resolve_types_nolinks(env, fromType, into) 688 | # else 689 | # intoPath 690 | # end 691 | # end 692 | # defp resolve_types_nolinks(env, from, %Ptr{}=fromType, %Ptr{}=intoType) do 693 | # p0 = Ptr.get(env, fromType) 694 | # p1 = Ptr.get(env, intoType) 695 | # case {p0, p1} do 696 | # {p0, %Ptr.Unbound{}} -> {env, p0} 697 | # # {%Ptr.Unbound{id: id}, %Ptr.Unbound{id: id}} -> p0 698 | # # {%Ptr.Generic{id: id0}, %Ptr.Unbound{id: id1}} -> p0 699 | # # {%Ptr.Unbound{id: fromID}, %Ptr.Unbound{id: intoID}} -> 700 | # _ -> throw {:TODO_RESOLVE_2_PTRS, p0, p1} 701 | # end 702 | # end 703 | # defp resolve_types_nolinks(env, from, fromType, %Ptr{}=intoType) do 704 | # case Ptr.get(env, intoType) do 705 | # %Ptr.Unbound{} -> {env, fromType} 706 | # %Ptr.Generic{} -> throw {:CANNOT_RESOLVE_SPECIFIC_TYPE_TO_GENERIC, fromType} 707 | # end 708 | # end 709 | # defp resolve_types_nolinks(env, from, fromType, %Ptr{ptr: ptr}) do 710 | # # intoType = HMEnv.get_type_ptr(env, ptr) 711 | # # resolve_types_nolinks(env, fromType, intoType) 712 | # end 713 | # defp resolve_types_nolinks(env, from, %Ptr{ptr: ptr}, intoType) do 714 | # # fromType = HMEnv.get_type_ptr(env, ptr) 715 | # # resolve_types_nolinks(env, fromType, intoType) 716 | # end 717 | # Catch-all 718 | defp resolve_types_nolinks(_env, from, fromType, into, intoType) do 719 | # {env, from} = get_type_or_ptr_type(env, fromType) 720 | # {_env, into} = get_type_or_ptr_type(env, intoType) 721 | throw {:NO_TYPE_RESOLUTION, from, fromType, into, intoType} 722 | end 723 | 724 | 725 | 726 | def get_generic_bound(env, %Ptr.Generic{id: id}=gen) do 727 | key = {:generic_id, id} 728 | case HMEnv.get_type(env, key) do 729 | nil -> gen 730 | result -> result 731 | end 732 | end 733 | 734 | 735 | 736 | 737 | 738 | # Unify two types 739 | def unify_types!(env, t0, t1) do 740 | type = unify_types(env, t0, t1) 741 | if Exception.exception?(type) do 742 | raise type 743 | else 744 | type 745 | end 746 | end 747 | 748 | 749 | def unify_types(env, t0, t1) do 750 | {env, t0} = get_type_or_ptr_type(env, t0) 751 | {env, t1} = get_type_or_ptr_type(env, t1) 752 | unify_types_nolinks(env, t0, t1) 753 | end 754 | 755 | 756 | def unify_types_nolinks(env, t0, t1) 757 | def unify_types_nolinks(env, t, t), do: {env, t} 758 | def unify_types_nolinks(_env, %Ptr.Generic{id: id0, named: true}=t0, %Ptr.Generic{id: id1, named: true}=t1) when id0 !== id1 do 759 | throw {:NO_TYPE_UNIFICATION, :GENERICS_DO_NOT_MATCH, t0, t1} 760 | end 761 | def unify_types_nolinks(_env, t0, %Ptr.Generic{named: false}), do: t0 762 | def unify_types_nolinks(_env, %Ptr.Generic{named: false}, t1), do: t1 763 | # Keep TypePtr handling last 764 | # def unify_types_nolinks(env, %Ptr{}=t0, %Ptr{}=t1) do 765 | # type0 = Ptr.get(env, t1) 766 | # path0 = unify_types_nolinks(env, t1, type0) 767 | # if Exception.exception?(path0) do 768 | # type1 = Ptr.get(env, t0) 769 | # unify_types_nolinks(env, type1, t0) 770 | # else 771 | # path0 772 | # end 773 | # end 774 | # def unify_types_nolinks(env, t0, %Ptr{ptr: ptr}) do 775 | # # t1 = HMEnv.get_type_ptr(env, ptr) 776 | # # unify_types_nolinks(env, t0, t1) 777 | # end 778 | # def unify_types_nolinks(env, %Ptr{ptr: ptr}, t1) do 779 | # # t0 = HMEnv.get_type_ptr(env, ptr) 780 | # # unify_types_nolinks(env, t0, t1) 781 | # end 782 | # Catch-all 783 | def unify_types_nolinks(env, t0, t1) do 784 | {env, t0} = get_type_or_ptr_type(env, t0) 785 | {_env, t1} = get_type_or_ptr_type(env, t1) 786 | throw {:NO_TYPE_UNIFICATION, :NO_PATH, t0, t1} 787 | end 788 | end 789 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule TypedElixir.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :typed_elixir, 6 | version: "0.1.0", 7 | elixir: "~> 1.3", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | # Dependencies can be Hex packages: 21 | # 22 | # {:mydep, "~> 0.3.0"} 23 | # 24 | # Or git/path repositories: 25 | # 26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 27 | # 28 | # Type "mix help deps" for more examples and options 29 | defp deps do 30 | [ 31 | {:mix_test_watch, "~> 0.3", only: :dev, runtime: false} 32 | ] 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"fs": {:hex, :fs, "2.12.0", "ad631efacc9a5683c8eaa1b274e24fa64a1b8eb30747e9595b93bec7e492e25e", [:rebar3], []}, 2 | "mix_test_watch": {:hex, :mix_test_watch, "0.3.3", "70859889a8d1d43d1b75d69d87258a301f43209a17787cdb2bd9cab42adf271d", [:mix], [{:fs, "~> 2.12", [hex: :fs, optional: false]}]}} 3 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | quote do @spec div(int, (d int if d != 0)) :: int end 2 | # quote do @spec testing(x: int if x >= 2) :: (r: int if r >= (x-2)) end 3 | quote do @spec testing(x: int if x >= 2) :: (if int >= x-2) end 4 | quote do @spec testing(x: int if x >= 2) :: if int >= x-2 end 5 | quote do @spec testing(x int when x >= 2) :: x int when x >= x-2 end 6 | quote do @spec div(number, (d is number if d != 0)) :: number end 7 | -------------------------------------------------------------------------------- /scratchpad.ex: -------------------------------------------------------------------------------- 1 | {:@, [line: 18], 2 | [{:spec, [line: 18], 3 | [{:::, [line: 18], 4 | [{:fun_default, [line: 18], [{:any, [line: 18], []}]}, nil]}]}]} 5 | 6 | {:@, [line: 20], 7 | [{:spec, [line: 20], 8 | [{:::, [line: 20], [{:simple, [line: 20], []}, nil]}]}]} 9 | 10 | 11 | {{:trim_trailing, 2}, 12 | {:type, 813, :fun, 13 | [{:type, 813, :product, 14 | [{:user_type, 813, :t, []}, {:user_type, 813, :t, []}]}, 15 | {:user_type, 813, :t, []}]}, []} 16 | 17 | {{:replace, 4}, 18 | {:type, 1028, :fun, 19 | [{:type, 1028, :product, 20 | [{:user_type, 1028, :t, []}, 21 | {:type, 1028, :union, 22 | [{:user_type, 1028, :pattern, []}, 23 | {:remote_type, 1028, [{:atom, 0, Regex}, {:atom, 0, :t}, []]}]}, 24 | {:user_type, 1028, :t, []}, 25 | {:remote_type, 1028, [{:atom, 0, Keyword}, {:atom, 0, :t}, []]}]}, 26 | {:user_type, 1028, :t, []}]}, []} 27 | 28 | [{:replace, [line: 40], 29 | [{:t, [line: 40], nil}, 30 | {:|, [line: 40], 31 | [{:pattern, [line: 40], nil}, 32 | {{:., [line: 40], 33 | [{:__aliases__, [counter: 0, line: 40], [:Regex]}, :t]}, 34 | [line: 40], []}]}, {:t, [line: 40], nil}, 35 | {{:., [line: 40], 36 | [{:__aliases__, [counter: 0, line: 40], [:Keyword]}, :t]}, 37 | [line: 40], []}]}, {:t, [line: 40], nil}]}]}]} 38 | 39 | 40 | {:@, [line: 40], 41 | [{:spec, [line: 40], 42 | [{:::, [line: 40], 43 | [{:replace, [line: 40], 44 | [{:t, [line: 40], nil}, 45 | {:|, [line: 40], 46 | [{:pattern, [line: 40], nil}, 47 | {{:., [line: 40], 48 | [{:__aliases__, [counter: 0, line: 40], [:Regex]}, :t]}, 49 | [line: 40], []}]}, {:t, [line: 40], nil}, 50 | {{:., [line: 40], 51 | [{:__aliases__, [counter: 0, line: 40], [:Keyword]}, :t]}, 52 | [line: 40], []}]}, {:t, [line: 40], nil}]}]}]} 53 | 54 | %{opaques: [{:t, {:atom, 0, nil}, []}], 55 | specs: [{{:__info__, 1}, 56 | [{:type, 10, :fun, 57 | [{:type, 10, :product, 58 | [{:type, 10, :union, 59 | [{:atom, 10, :attributes}, {:atom, 10, :compile}, 60 | {:atom, 10, :exports}, {:atom, 10, :functions}, {:atom, 10, :macros}, 61 | {:atom, 10, :md5}, {:atom, 10, :module}, 62 | {:atom, 10, :native_addresses}]}]}, 63 | {:type, 10, :union, 64 | [{:type, 10, :atom, []}, 65 | {:type, 10, :list, 66 | [{:type, 10, :union, 67 | [{:type, 10, :tuple, 68 | [{:type, 10, :atom, []}, {:type, 10, :any, []}]}, 69 | {:type, 10, :tuple, 70 | [{:type, 10, :atom, []}, {:type, 10, :byte, []}, 71 | {:type, 10, :integer, []}]}]}]}]}]}]}, 72 | {{:replace, 4}, 73 | [{:type, 40, :fun, 74 | [{:type, 40, :product, 75 | [{:user_type, 40, :t, []}, 76 | {:type, 40, :union, 77 | [{:user_type, 40, :pattern, []}, 78 | {:remote_type, 40, [{:atom, 0, Regex}, {:atom, 0, :t}, []]}]}, 79 | {:user_type, 40, :t, []}, 80 | {:remote_type, 40, [{:atom, 0, Keyword}, {:atom, 0, :t}, []]}]}, 81 | {:user_type, 40, :t, []}]}]}, 82 | {{:fun_default, 1}, 83 | [{:type, 18, :fun, 84 | [{:type, 18, :product, [{:type, 18, :any, []}]}, {:atom, 0, nil}]}]}, 85 | {{:fun_default, 0}, 86 | [{:type, 17, :fun, [{:type, 17, :product, []}, {:atom, 0, nil}]}]}, 87 | {{:hello, 1}, 88 | [{:type, 23, :fun, 89 | [{:type, 23, :product, [{:user_type, 23, :test_type, []}]}, 90 | {:type, 23, :union, 91 | [{:user_type, 23, :test_type, []}, 92 | {:remote_type, 23, [{:atom, 0, Map}, {:atom, 0, :t}, []]}]}]}]}, 93 | {{:simple, 0}, 94 | [{:type, 20, :fun, [{:type, 20, :product, []}, {:atom, 0, nil}]}]}], 95 | types: [{:pattern, {:atom, 0, nil}, []}, 96 | {:test_type, {:remote_type, 15, [{:atom, 0, String}, {:atom, 0, :t}, []]}, 97 | []}]} 98 | -------------------------------------------------------------------------------- /test/ml_elixir/ml_module_test.exs: -------------------------------------------------------------------------------- 1 | 2 | 3 | import MLElixir 4 | defmlmodule MLModuleTest do 5 | 6 | type type_declaration 7 | 8 | type type_definition = integer 9 | 10 | type record_emb_0 = %{a: %{b: %{c: type_definition}}} 11 | 12 | type testering_enum 13 | | none 14 | | one 15 | | integer integer 16 | | two 17 | | float float 18 | | float2 float 19 | 20 | def test_int_untyped = 42 21 | def test_int_typed | integer = 42 22 | def test_float_untyped = 6.28 23 | def test_float_typed | float = 6.28 24 | def test_defined | type_definition = 42 25 | def identity_untyped(a) = a 26 | def identity_typed(b | !id_type) | !id_type = b 27 | def identity_int(c | integer, f | float) | integer = c 28 | def identity_float(x | integer, f | float) | float = f 29 | def test_block0 do 42 end 30 | def test_blockN0() do 42 end 31 | def test_blockT0() | integer do 42 end 32 | def test_blockN1(x) do x end 33 | def test_blockT1(x | !id_type) | !id_type do x end 34 | def test_noblockT1(x | !id_type) | !id_type = x 35 | def test_record | record_emb_0 = %{a: %{b: %{c: 42}}} 36 | def test_record_sub(r | record_emb_0) = r.a 37 | 38 | end 39 | 40 | 41 | defmlmodule MLModuleTest_Generalized do 42 | type t 43 | 44 | def blah(t | t) | t, do: t 45 | def bloo(t | t) | t = t 46 | end 47 | -------------------------------------------------------------------------------- /test/ml_elixir_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MLElixirTest do 2 | @moduledoc false 3 | 4 | use ExUnit.Case 5 | # import CompileTimeAssertions 6 | 7 | doctest MLElixir 8 | import MLElixir 9 | 10 | defmlmodule MLModuleTest_Specific, debug: [:_resolving, :module_pretty_output] do 11 | type t = MLModuleTest.type_definition 12 | type Specific = MLModuleTest_Generalized.(t: float) 13 | type st = Specific.t 14 | type a(t) = t 15 | type b(c) = c 16 | type ra = a(integer) 17 | type rb = b(integer) 18 | 19 | type testering_enum 20 | | none 21 | | one 22 | | integer integer 23 | | two 24 | | float float 25 | | float2 float 26 | # | t integer # Single value? 27 | # | t_f0 integer, float # multiple value? 28 | # | i_f1(integer, float) # Hmm, what for... 29 | # | t_f2{integer, float} # Hmm... default tuple? 30 | # | t_f3[i: integer, f: float] # Keyword list version maybe? Or just make a normal list? 31 | # | %t_f4{t: integer, f: float} # Map version? 32 | | tuple_enum0{} 33 | | tuple_enum1{integer} 34 | | tuple_enum2{integer, float} 35 | | tuple_recurception{{integer, float}} 36 | # | record_enum %{integer: integer} 37 | # | map_enum %{integer => float} 38 | # | gadt_enum = (integer | float) 39 | 40 | def testering0 | t = 42 41 | def testering1 | st = 6.28 42 | def testering2 | ra = 42 43 | def testering3 | rb = 42 44 | def testering4 | a(integer) = 42 45 | def testering5 | a(float) = 6.28 46 | def testering6 | testering_enum = none() # Just testing that it works with 0-args too, elixir ast oddness reasons 47 | def testering7 | testering_enum = one 48 | def testering8 | testering_enum = two 49 | def testering9 | testering_enum = integer # Curried! 50 | def testering9x(x) | testering_enum = integer x # Not-Curried! 51 | def testering10 | testering_enum = integer 42 52 | def testering11(x) | testering_enum = integer x 53 | def testering12 = MLModuleTest.test_int_untyped 54 | def testering13 = MLModuleTest.test_int_typed 55 | def testering14 = MLModuleTest.test_record.a.b.c 56 | def testering15(r) = MLModuleTest.test_record_sub(r).b.c 57 | # def testering16 = MLModuleTest.testering_enum # Should fail 58 | def testering17 = MLModuleTest.testering_enum.one 59 | def testering18 = MLModuleTest.testering_enum.integer # auto-curry gadt head test 60 | def testering19(i) = MLModuleTest.testering_enum.integer i 61 | def testering20 = tuple_enum0 62 | def testering21 = tuple_enum1 42 63 | def testering22 = tuple_enum2 64 | def testering23 = tuple_enum2 42 65 | def testering24(f) = tuple_enum2 42, f 66 | def testering25 = tuple_enum2 42, 6.28 67 | def testering26 = testering0 68 | def testering27 = tuple_recurception {42, 6.28} 69 | 70 | # Functions 71 | def testering_func0 = 42 72 | def testering_func1(a, b, c) | {integer, integer, integer} = {a, b, c} 73 | def testering_func2(a | integer, b | integer, c | integer) = {a, b, c} 74 | def testering_func3(a | integer, b | integer, c | integer) | {integer, integer, integer} = {a, b, c} 75 | def testering_func4 = testering_func0 76 | def testering_func5 = testering_func1 1, 2, 3 77 | def testering_func6(a, b, c) = testering_func1 a, b, c 78 | def testering_func7(a) = testering_func1 1, a, 3 79 | def testering_func8 = testering_func1 80 | def testering_func9(a) = testering_func1 a 81 | def testering_func10(a) = testering_func1 1, a 82 | def testering_func11(a) = testering_func1 a, 2 83 | def testering_func12(a, b) = testering_func1 a, b 84 | def testering_func13(a) = testering_func1 a, _, _ 85 | def testering_func14(a) = testering_func1 _, a, _ 86 | def testering_func15(a) = testering_func1 _, _, a 87 | def testering_func16(a) = testering_func1 _, a 88 | def testering_func17(a) = testering_func1 a, _0, _1 89 | def testering_func18(a) = testering_func1 a, _1, _0 90 | def testering_func19(a) = testering_func1 a, _1 91 | def testering_func20 = testering_func8 1, 2, 3 92 | def testering_func21 = testering_func9 1, 2, 3 93 | def testering_func22 = testering_func12 1, 2, 3 94 | def testering_func23 = testering_func9 1 95 | def testering_func24 = testering_func9 1, _ 96 | def testering_func25 = testering_func9 _, 2 97 | def testering_func26a(a, b, c, d, e) = {a, b, c, d, e} 98 | def testering_func26b(b) = testering_func26a 1, b 99 | def testering_func26c(c) = testering_func26b 2, c 100 | def testering_func26d(d) = testering_func26c 3, d 101 | def testering_func26(e) = testering_func26d 4, e 102 | def testering_func27(e) = testering_func26a _, _, _, _, e 103 | def testering_func28(e) = testering_func26b _, _, _, e 104 | def testering_func29(e) = testering_func26a _3, _2, _1, _0, e 105 | def testering_func30(f) = f(2) 106 | def testering_func31(value, f) = f(value) 107 | def testering_func32(value | integer, f) = f(value) 108 | def testering_func33 = testering_func31(3, testering_func1(1, 2)) 109 | 110 | # Bindings 111 | def testering_binding0(i) = i 112 | def testering_binding1(i | integer) = i 113 | def testering_binding2(42) = 42 114 | def testering_binding3(42 | integer) = 42 115 | def testering_binding4(42 = i) = i 116 | def testering_binding5(i = 42) = i 117 | def testering_binding6({i}) = i 118 | def testering_binding7({42}) = 42 119 | def testering_binding8({i | integer}) = i 120 | def testering_binding9({i | integer} | {integer}) = i 121 | def testering_binding10({i} | {integer}) = i 122 | def testering_binding11({i}=t) = {i, t} 123 | def testering_binding12(%{a: i | integer}) = i 124 | def testering_binding13(%{a: i | integer}=r) = %{b: r} 125 | 126 | # Records (I.E. Erlang/Elixir atom() keyed maps, like structs) 127 | type record0 = %{} # Empty record 128 | type record1 = %{ 129 | x: integer, 130 | y: float, 131 | } 132 | type record2(t) = %{t: t} 133 | type record_ex_0 = %{+: record0, z: integer} 134 | type record_ex_1 = %{+: record1, +: record0, z: integer} 135 | type record_ex_2(t) = %{+: record2(t), z: integer} 136 | type record_ex_2_float = %{+: record2(float), z: integer} 137 | # type record_ex_sub(t) = %{+: t, z: integer} # Need to support unbound's perhaps? # Unsure if I want to support this... 138 | type record_rem_0 = %{+: record1, -: x} 139 | type record_emb_0 = %{a: %{b: %{c: integer}}} 140 | 141 | def testering_record0 | record0 = %{} 142 | def testering_record1 | record1 = %{x: 42, y: 6.28} 143 | def testering_record2(i) | record1 = %{x: i, y: 6.28} 144 | def testering_record3(t | !t) | record2(!t) = %{t: t} 145 | def testering_record4 | record_ex_0 = %{z: 42} 146 | def testering_record5 | record_ex_1 = %{x: 42, y: 6.28, z: 42} 147 | def testering_record6 | record_ex_2(integer) = %{t: 42, z: 42} 148 | def testering_record7(t | !t) | record_ex_2(!t) = %{t: t, z: 42} 149 | def testering_record8 | record_ex_2_float = %{t: 6.28, z: 42} 150 | # def testering_record9 | record_ex_sub(record2(float)) = %{t: 6.28, z: 42} # Unsure if I want to support this... 151 | def testering_record10 | record_rem_0 = %{y: 6.28} 152 | def testering_record11(r | record_emb_0) = r.a.b.c 153 | 154 | # Tuples 155 | type tuple0 = {} 156 | type tuple1 = {integer} 157 | type tuple2 = {integer, float} 158 | type tuple3 = {integer, {float, integer}} 159 | 160 | def testering_tuple0 | tuple0 = {} 161 | def testering_tuple1(t) | tuple0 = t 162 | def testering_tuple2 = {} 163 | def testering_tuple3 = {42} 164 | def testering_tuple4 | tuple1 = {42} 165 | def testering_tuple5(i) | tuple1 = {i} 166 | def testering_tuple6(t) | tuple1 = t 167 | def testering_tuple7 | tuple1 = testering_tuple3 168 | def testering_tuple8 = {42, 6.28} 169 | def testering_tuple9(i|!t) = {i, 6.28} 170 | def testering_tuple10(i) | tuple2 = testering_tuple9 i 171 | def testering_tuple11 | tuple2 = testering_tuple9 42 172 | def testering_tuple12 | tuple3 = {42, {6.28, 42}} 173 | 174 | # FFI 175 | external addi(integer, integer) | integer = Kernel.+ 176 | def testering_ffi_addi_0 = addi(1, 2) 177 | def testering_ffi_addi_1(i) = addi(1, i) 178 | def testering_ffi_addi_2(a, b) = addi(a, b) 179 | 180 | def value |> fun = fun(value) 181 | # let value |> fun = fun(value) # Both work as always 182 | # def testering_op_pipe0 = 42 |> testering_tuple5 183 | def identity(i) = i 184 | def testering_op_pipe1(i) = 185 | 42 186 | |> identity 187 | |> testering_func1(1, _, i) 188 | # def testering_op_pipe2(i) = 189 | # 42 190 | # |> identity 191 | # |> testering_func1(1) # partial currying not yet implemented, see: :make_function_wrapper_of_proper_length 192 | # def testering_op_pipe2() = 193 | # 42 194 | # |> identity 195 | # |> testering_func1 196 | end 197 | 198 | # defmlmodule MLModuleTest_Specific_Module | MLModuleTest.(t: int) do 199 | # end 200 | 201 | test "MLModuleTest's" do 202 | assert MLModuleTest.test_int_untyped() === 42 203 | assert MLModuleTest.test_int_typed() === 42 204 | assert MLModuleTest.test_float_untyped() === 6.28 205 | assert MLModuleTest.test_float_typed() === 6.28 206 | assert MLModuleTest.test_defined() === 42 207 | assert MLModuleTest.identity_untyped(42) === 42 208 | assert MLModuleTest.identity_untyped(6.28) === 6.28 209 | assert MLModuleTest.identity_typed(42) === 42 210 | assert MLModuleTest.identity_typed(6.28) === 6.28 211 | assert MLModuleTest.identity_int(42, 6.28) === 42 212 | assert MLModuleTest.identity_float(42, 6.28) === 6.28 213 | assert MLModuleTest.test_block0() === 42 214 | assert MLModuleTest.test_blockN0() === 42 215 | assert MLModuleTest.test_blockT0() === 42 216 | assert MLModuleTest.test_blockN1(42) === 42 217 | assert MLModuleTest.test_blockN1(6.28) === 6.28 218 | assert MLModuleTest.test_blockT1(42) === 42 219 | assert MLModuleTest.test_blockT1(6.28) === 6.28 220 | 221 | assert MLModuleTest_Generalized.blah(42) === 42 222 | assert MLModuleTest_Generalized.bloo(42) === 42 223 | 224 | assert MLModuleTest_Specific.testering0() === 42 225 | assert MLModuleTest_Specific.testering1() === 6.28 226 | assert MLModuleTest_Specific.testering2() === 42 227 | assert MLModuleTest_Specific.testering3() === 42 228 | assert MLModuleTest_Specific.testering4() === 42 229 | assert MLModuleTest_Specific.testering5() === 6.28 230 | assert MLModuleTest_Specific.testering6() === :none 231 | assert MLModuleTest_Specific.testering7() === :one 232 | assert MLModuleTest_Specific.testering8() === :two 233 | assert MLModuleTest_Specific.testering9().(42) === {:integer, 42} # Ooo, can auto-curry enum heads! Guarded too 234 | assert MLModuleTest_Specific.testering9x(42) === {:integer, 42} 235 | assert MLModuleTest_Specific.testering10() === {:integer, 42} 236 | assert MLModuleTest_Specific.testering11(42) === {:integer, 42} 237 | assert MLModuleTest_Specific.testering12 === 42 238 | assert MLModuleTest_Specific.testering13 === 42 239 | assert MLModuleTest_Specific.testering14 === 42 240 | assert MLModuleTest_Specific.testering15(%{a: %{b: %{c: 42}}}) === 42 241 | # assert MLModuleTest_Specific.testering16 === 42 # Should fail 242 | assert MLModuleTest_Specific.testering17 === :one 243 | assert MLModuleTest_Specific.testering18.(42) === {:integer, 42} 244 | assert MLModuleTest_Specific.testering19(42) === {:integer, 42} 245 | assert MLModuleTest_Specific.testering20 === {:tuple_enum0} 246 | assert MLModuleTest_Specific.testering21 === {:tuple_enum1, 42} 247 | assert MLModuleTest_Specific.testering22.(42, 6.28) === {:tuple_enum2, 42, 6.28} 248 | assert MLModuleTest_Specific.testering23.(6.28) === {:tuple_enum2, 42, 6.28} 249 | assert MLModuleTest_Specific.testering24(6.28) === {:tuple_enum2, 42, 6.28} 250 | assert MLModuleTest_Specific.testering25 === {:tuple_enum2, 42, 6.28} 251 | assert MLModuleTest_Specific.testering26 === 42 252 | assert MLModuleTest_Specific.testering27 === {:tuple_recurception, {42, 6.28}} 253 | 254 | # Functions 255 | assert MLModuleTest_Specific.testering_func0() === 42 256 | assert MLModuleTest_Specific.testering_func1(1, 2, 3) === {1, 2, 3} 257 | assert MLModuleTest_Specific.testering_func2(1, 2, 3) === {1, 2, 3} 258 | assert MLModuleTest_Specific.testering_func3(1, 2, 3) === {1, 2, 3} 259 | assert MLModuleTest_Specific.testering_func4() === 42 260 | assert MLModuleTest_Specific.testering_func5() === {1, 2, 3} 261 | assert MLModuleTest_Specific.testering_func6(1, 2, 3) === {1, 2, 3} 262 | assert MLModuleTest_Specific.testering_func7(2) === {1, 2, 3} 263 | assert MLModuleTest_Specific.testering_func8().(1, 2, 3) === {1, 2, 3} 264 | assert MLModuleTest_Specific.testering_func9(1).(2, 3) === {1, 2, 3} 265 | assert MLModuleTest_Specific.testering_func10(2).(3) === {1, 2, 3} 266 | assert MLModuleTest_Specific.testering_func11(1).(3) === {1, 2, 3} 267 | assert MLModuleTest_Specific.testering_func12(1, 2).(3) === {1, 2, 3} 268 | assert MLModuleTest_Specific.testering_func13(1).(2, 3) === {1, 2, 3} 269 | assert MLModuleTest_Specific.testering_func14(2).(1, 3) === {1, 2, 3} 270 | assert MLModuleTest_Specific.testering_func15(3).(1, 2) === {1, 2, 3} 271 | assert MLModuleTest_Specific.testering_func16(2).(1, 3) === {1, 2, 3} 272 | assert MLModuleTest_Specific.testering_func17(1).(2, 3) === {1, 2, 3} 273 | assert MLModuleTest_Specific.testering_func18(1).(3, 2) === {1, 2, 3} 274 | assert MLModuleTest_Specific.testering_func19(1).(:unused, 2, 3) === {1, 2, 3} 275 | assert MLModuleTest_Specific.testering_func20() === {1, 2, 3} 276 | assert MLModuleTest_Specific.testering_func21() === {1, 2, 3} 277 | assert MLModuleTest_Specific.testering_func22() === {1, 2, 3} 278 | assert MLModuleTest_Specific.testering_func23().(2, 3) === {1, 2, 3} 279 | assert MLModuleTest_Specific.testering_func24().(2, 3) === {1, 2, 3} 280 | assert MLModuleTest_Specific.testering_func25().(1, 3) === {1, 2, 3} 281 | assert MLModuleTest_Specific.testering_func26(5) === {1, 2, 3, 4, 5} 282 | assert MLModuleTest_Specific.testering_func27(5).(1, 2, 3, 4) === {1, 2, 3, 4, 5} 283 | assert MLModuleTest_Specific.testering_func28(5).(2, 3, 4) === {1, 2, 3, 4, 5} 284 | assert MLModuleTest_Specific.testering_func29(5).(4, 3, 2, 1) === {1, 2, 3, 4, 5} 285 | assert MLModuleTest_Specific.testering_func30(fn x -> x + 1 end) === 3 286 | assert MLModuleTest_Specific.testering_func31(21, fn x -> x * 2 end) === 42 287 | assert MLModuleTest_Specific.testering_func32(21, fn x -> x * 2 end) === 42 288 | 289 | # Bindings 290 | assert MLModuleTest_Specific.testering_binding0(42) == 42 291 | assert MLModuleTest_Specific.testering_binding1(42) == 42 292 | assert MLModuleTest_Specific.testering_binding2(42) == 42 293 | assert MLModuleTest_Specific.testering_binding3(42) == 42 294 | assert MLModuleTest_Specific.testering_binding4(42) == 42 295 | assert MLModuleTest_Specific.testering_binding5(42) == 42 296 | assert MLModuleTest_Specific.testering_binding6({42}) == 42 297 | assert MLModuleTest_Specific.testering_binding7({42}) == 42 298 | assert MLModuleTest_Specific.testering_binding8({42}) == 42 299 | assert MLModuleTest_Specific.testering_binding9({42}) == 42 300 | assert MLModuleTest_Specific.testering_binding10({42}) == 42 301 | assert MLModuleTest_Specific.testering_binding11({42}) == {42, {42}} 302 | assert MLModuleTest_Specific.testering_binding12(%{a: 42}) == 42 303 | assert MLModuleTest_Specific.testering_binding13(%{a: 42}) == %{b: %{a: 42}} 304 | 305 | # Records 306 | assert MLModuleTest_Specific.testering_record0() === %{} 307 | assert MLModuleTest_Specific.testering_record1() === %{x: 42, y: 6.28} 308 | assert MLModuleTest_Specific.testering_record2(42) === %{x: 42, y: 6.28} 309 | assert MLModuleTest_Specific.testering_record3(42) === %{t: 42} 310 | assert MLModuleTest_Specific.testering_record4() === %{z: 42} 311 | assert MLModuleTest_Specific.testering_record5() === %{x: 42, y: 6.28, z: 42} 312 | assert MLModuleTest_Specific.testering_record6() === %{t: 42, z: 42} 313 | assert MLModuleTest_Specific.testering_record7(42) === %{t: 42, z: 42} 314 | assert MLModuleTest_Specific.testering_record8() === %{t: 6.28, z: 42} 315 | assert MLModuleTest_Specific.testering_record10() === %{y: 6.28} 316 | assert MLModuleTest_Specific.testering_record11(%{a: %{b: %{c: 42}}}) === 42 317 | 318 | # Tuples 319 | assert MLModuleTest_Specific.testering_tuple0() === {} 320 | assert MLModuleTest_Specific.testering_tuple1({}) === {} 321 | assert MLModuleTest_Specific.testering_tuple2() === {} 322 | assert MLModuleTest_Specific.testering_tuple3() === {42} 323 | assert MLModuleTest_Specific.testering_tuple4() === {42} 324 | assert MLModuleTest_Specific.testering_tuple5(42) === {42} 325 | assert MLModuleTest_Specific.testering_tuple6({42}) === {42} 326 | assert MLModuleTest_Specific.testering_tuple7() === {42} 327 | assert MLModuleTest_Specific.testering_tuple8() === {42, 6.28} 328 | assert MLModuleTest_Specific.testering_tuple9(42) === {42, 6.28} 329 | assert MLModuleTest_Specific.testering_tuple10(42) === {42, 6.28} 330 | assert MLModuleTest_Specific.testering_tuple11() === {42, 6.28} 331 | assert MLModuleTest_Specific.testering_tuple12() === {42, {6.28, 42}} 332 | 333 | # FFI 334 | assert MLModuleTest_Specific.addi(1, 2) === 3 # Yep, the external is exposed directly as a function for non-typed code 335 | assert MLModuleTest_Specific.testering_ffi_addi_0() === 3 336 | assert MLModuleTest_Specific.testering_ffi_addi_1(3) === 4 337 | assert MLModuleTest_Specific.testering_ffi_addi_2(1, 2) === 3 338 | 339 | # Operators 340 | (fn -> 341 | import Kernel, except: [|>: 2] 342 | import MLModuleTest_Specific, only: [|>: 2] 343 | assert (21 |> (&(&1*2))) === 42 344 | assert MLModuleTest_Specific.testering_op_pipe1(3) === {1, 42, 3} 345 | # assert MLModuleTest_Specific.testering_op_pipe2(3) === {1, 42, 3} 346 | end).() 347 | end 348 | 349 | 350 | 351 | test "MLElixir Javascript output" do 352 | defmlmodule :testing_js, output_format: :js do 353 | type t = MLModuleTest.type_definition 354 | type Specific = MLModuleTest_Generalized.(t: float) 355 | type st = Specific.t 356 | type a(t) = t 357 | type b(c) = c 358 | type ra = a(integer) 359 | type rb = b(integer) 360 | 361 | type testering_enum 362 | | none 363 | | one 364 | | integer integer 365 | | two 366 | | float float 367 | | float2 float 368 | 369 | def testering0 | t = 42 370 | def testering1 | st = 6.28 371 | def testering2 | ra = 42 372 | def testering3 | rb = 42 373 | def testering4 | a(integer) = 42 374 | def testering5 | a(float) = 6.28 375 | def testering6 | testering_enum = none() # Just testing that it works with 0-args too, elixir ast oddness reasons 376 | def testering7 | testering_enum = one 377 | def testering8 | testering_enum = two 378 | def testering9 | testering_enum = integer # Curried! 379 | def testering9x(x) | testering_enum = integer x # Not-Curried! 380 | def testering10 | testering_enum = integer 42 381 | def testering11(x) | testering_enum = integer x 382 | 383 | type record0 = %{} 384 | type record1 = %{ 385 | x: integer, 386 | y: float, 387 | } 388 | type record2(t) = %{t: t} 389 | type record_ex_0 = %{+: record0, z: integer} 390 | type record_ex_1 = %{+: record1, +: record0, z: integer} 391 | type record_ex_2(t) = %{+: record2(t), z: integer} 392 | type record_ex_2_float = %{+: record2(float), z: integer} 393 | type record_rem_0 = %{+: record1, -: x} 394 | 395 | def testering_record0 | record0 = %{} 396 | def testering_record1 | record1 = %{x: 42, y: 6.28} 397 | def testering_record2(i) | record1 = %{x: i, y: 6.28} 398 | def testering_record3(t | !t) | record2(!t) = %{t: t} 399 | def testering_record4 | record_ex_0 = %{z: 42} 400 | def testering_record5 | record_ex_1 = %{x: 42, y: 6.28, z: 42} 401 | def testering_record6 | record_ex_2(integer) = %{t: 42, z: 42} 402 | def testering_record7(t | !t) | record_ex_2(!t) = %{t: t, z: 42} 403 | def testering_record8 | record_ex_2_float = %{t: 6.28, z: 42} 404 | def testering_record10 | record_rem_0 = %{y: 6.28} 405 | 406 | # FFI 407 | external addi(integer, integer) | integer = Kernel.add 408 | def testering_ffi_addi_0 = addi(1, 2) 409 | def testering_ffi_addi_1(i) = addi(1, i) 410 | def testering_ffi_addi_2(a, b) = addi(a, b) 411 | end 412 | # |> (fn js -> 413 | # IO.puts("Javascript:") 414 | # IO.puts(js) 415 | # end).() 416 | end 417 | 418 | 419 | 420 | test "MLElixir macro calls" do 421 | # defmlmodule MLElixir.Tests.MacroTest do 422 | # # def¯o test_macro0 = 42 423 | # # let¯o test_macro0 | MLElixir.MLMacro.ast = 42 424 | # # def¯o test_macro0 | MLElixir.MLMacro.ast, do: MLElixir.MLMacro.ast.one 425 | # # let¯o test_macro0, do: 42 426 | # end 427 | end 428 | 429 | 430 | 431 | 432 | # test "literals" do 433 | # assert 1 == defml 1 434 | # assert 1.2 == defml 1.2 435 | # assert :ok == defml :ok 436 | # end 437 | # 438 | # test "let - binding single untyped variable" do 439 | # assert 1 == defml let _a = 2 in 1 440 | # assert 1 == defml let a = 1 in a 441 | # assert 1 == defml let a = 1 in let b = a in b 442 | # end 443 | # 444 | # test "let - named binding single untyped variable" do 445 | # assert 1 == defml let _a: _b = 2 in 1 446 | # # assert 1 == defml let a: b = 1 in a # These make elixir warn on unused vars, use the below two instead 447 | # # assert 1 == defml let a: b = 1 in b # These make elixir warn on unused vars, use the below two instead 448 | # assert 1 == defml let a: _b = 1 in a 449 | # assert 1 == defml let _a: b = 1 in b 450 | # end 451 | # 452 | # test "let - binding single typed unconstrained variable" do 453 | # assert 1 == defml let ![_a: int] = 2 in 1 454 | # assert 1 == defml let ![a: int] = 1 in a 455 | # assert 1 == defml let a = 1 in let ![b: int] = a in b 456 | # assert_compile_time_raise MLElixir.UnificationError, "Unable to resolve mismatched types", fn -> 457 | # import MLElixir 458 | # defml let ![a: int] = 6.28 in a 459 | # end 460 | # end 461 | # 462 | # test "let - binding single typed constrained variable" do 463 | # assert 1 == defml let ![a: int a=1] = 1 in a 464 | # assert 1 == defml let ![a: int a<=2] = 1 in a 465 | # assert_compile_time_raise MLElixir.UnificationError, "Unable to resolve", fn -> 466 | # import MLElixir 467 | # defml let ![a: int a=2] = 1 in a 468 | # end 469 | # assert_compile_time_raise MLElixir.UnificationError, "Unable to resolve", fn -> 470 | # import MLElixir 471 | # defml let ![a: int a>=2] = 1 in a 472 | # end 473 | # end 474 | # 475 | # test "let - matching single literal" do 476 | # assert 1 == defml let 1 = 1 in 1 477 | # assert_compile_time_raise MLElixir.UnificationError, "Unable to resolve", fn -> 478 | # import MLElixir 479 | # defml let 1 = 2 in 1 480 | # end 481 | # end 482 | # 483 | # test "let - multiple" do 484 | # assert 1 == defml let a = 1 in let 2 = 2 in a 485 | # assert 1 == defml let a = 1 in let b = a in b 486 | # assert_compile_time_raise MLElixir.UnificationError, "Unable to resolve", fn -> 487 | # import MLElixir 488 | # defml let a = 1 in let 2 = a in a 489 | # end 490 | # end 491 | # 492 | # test "let - open" do 493 | # assert 1 == defml let open MLElixir.Core in 1 494 | # assert 3 == defml let open MLElixir.Core in 1 + 2 495 | # assert_compile_time_raise MLElixir.InvalidCall, "No such function found", fn -> 496 | # import MLElixir 497 | # defml no_default_opens: true, do: 1 + 2 498 | # end 499 | # assert 3 == defml no_default_opens: true, do: let open MLElixir.Core in 1 + 2 500 | # end 501 | # 502 | # test "fun - define" do 503 | # # So sorry for this `end`, just an elixir syntax stupidity, not even elixir should have an end with a fn without an explicit block, blah... 504 | # # Stupid trailing `end`s, elixir has such an annoying syntax at times... 505 | # # defml fn blah -> blah end 506 | # # defml fn ![blah: int] -> blah end 507 | # # assert 1 == (defml fn -> 1 end).() 508 | # # assert 1 == (defml fn x -> x end).(1) 509 | # # assert 6.28 == (defml fn x -> x end).(6.28) 510 | # # assert 2 == (defml fn ![x: int x>=1] -> x end).(2) 511 | # # assert 6.28 == (defml fn ![x: int x>=1] -> x end).(6.28) # When called from 'outside' it is not typed 512 | # end 513 | # 514 | # test "fun - store" do 515 | # # quote do 516 | # # fn x -> x 517 | # # end 518 | # # defml let id = fn x -> x end in id 1 519 | # # defml let ![id: int] = fn x -> x end in 1 520 | # # assert 1 == defml let id = fn x -> x end in id 1 521 | # end 522 | # 523 | # # test "call" do 524 | # # assert 1 == defml 1+2 525 | # # end 526 | 527 | 528 | end 529 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | ExUnit.configure(colors: [enabled: true]) 3 | 4 | defmodule CompileTimeAssertions do 5 | defmodule DidNotRaise, do: defstruct(message: nil) 6 | 7 | defmacro assert_compile_time_raise(expected_exception, expected_message, fun) do 8 | actual_exception = 9 | try do 10 | Code.eval_quoted(fun) 11 | %DidNotRaise{} 12 | rescue 13 | e -> e 14 | end 15 | 16 | quote do 17 | assert unquote(actual_exception.__struct__) === unquote(expected_exception) 18 | assert unquote(actual_exception.message) === unquote(expected_message) 19 | end 20 | end 21 | 22 | defmacro assert_compile_time_throw(expected_error, fun) do 23 | actual_exception = 24 | try do 25 | Code.eval_quoted(fun) 26 | :DID_NOT_THROW 27 | catch 28 | e -> e 29 | end 30 | 31 | quote do 32 | assert unquote(expected_error) === unquote(Macro.escape(actual_exception)) 33 | end 34 | end 35 | 36 | defmacro match_compile_time_raise(expected_exception, fun) do 37 | actual_exception = 38 | try do 39 | Code.eval_quoted(fun) 40 | %DidNotRaise{} 41 | rescue 42 | e -> e 43 | end 44 | 45 | quote do 46 | unquote(expected_exception) = unquote(actual_exception) 47 | end 48 | end 49 | 50 | defmacro match_compile_time_throw(expected_error, fun) do 51 | actual_exception = 52 | try do 53 | Code.eval_quoted(fun) 54 | :DID_NOT_THROW 55 | catch 56 | e -> e 57 | end 58 | 59 | quote do 60 | unquote(expected_error) = unquote(Macro.escape(actual_exception)) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/typed_elixir_test.exs: -------------------------------------------------------------------------------- 1 | defmodule TypedElixirTest do 2 | @moduledoc false 3 | 4 | use ExUnit.Case 5 | import CompileTimeAssertions 6 | 7 | doctest TypedElixir 8 | use TypedElixir 9 | 10 | 11 | test "Empty" do 12 | defmodulet TypedTest_Empty do 13 | end 14 | end 15 | 16 | test "Typed - 0-arg" do 17 | defmodulet TypedTest_Typed_Simple do 18 | @spec simple() :: nil 19 | def simple(), do: nil 20 | end 21 | assert nil === TypedTest_Typed_Simple.simple() 22 | 23 | assert_compile_time_throw {:NO_TYPE_RESOLUTION, %TypedElixir.Type.Const{const: :integer, meta: %{values: [1]}}, %TypedElixir.Type.Const{const: :atom, meta: %{values: [nil]}}}, fn -> 24 | use TypedElixir 25 | defmodulet TypedTest_Typed_Simple do 26 | @spec simple() :: nil 27 | def simple(), do: 1 28 | end 29 | end 30 | end 31 | 32 | 33 | test "Untyped - 0-arg" do 34 | defmodulet TypedTest_Untyped_Simple do 35 | def simple(), do: nil 36 | end 37 | assert nil === TypedTest_Untyped_Simple.simple() 38 | end 39 | 40 | 41 | test "Untyped - 0-arg - recursive - no-return" do 42 | assert_compile_time_throw {:INVALID_ASSIGNMENT_NOT_ALLOWED, :no_return}, fn -> 43 | use TypedElixir 44 | defmodulet TypedTest_Untyped_Recursive_Simple_BAD_NoSet do 45 | def simple(), do: simple() 46 | def willFail() do 47 | x = simple() 48 | end 49 | end 50 | end 51 | end 52 | 53 | 54 | test "Typed - 1-arg - identity" do 55 | # The extra type is to give a name to the type in simple, so the input and output become the same type. 56 | # If the spec was `simple(any()) :: any()` then you could not state that the output type is based on the input type. 57 | defmodulet TypedTest_Typed_Identity do 58 | @type identity_type :: any() 59 | @spec identity(identity_type) :: identity_type 60 | def identity(x), do: x 61 | end 62 | assert 42 === TypedTest_Typed_Identity.identity(42) 63 | 64 | match_compile_time_throw {:NO_TYPE_RESOLUTION, %TypedElixir.Type.Const{const: :atom, meta: %{values: [nil]}}, %TypedElixir.Type.Ptr.Generic{id: _, named: true, meta: []}}, fn -> 65 | use TypedElixir 66 | defmodulet TypedTest_Typed_Identity_badtype do 67 | @type identity_type :: any() 68 | @spec identity(identity_type) :: identity_type 69 | def identity(_x), do: nil 70 | end 71 | end 72 | end 73 | 74 | 75 | test "Untyped - 1-arg - identity" do 76 | defmodulet TypedTest_Untyped_Identity do 77 | def identity(x), do: x 78 | end 79 | assert 42 === TypedTest_Untyped_Identity.identity(42) 80 | end 81 | 82 | 83 | test "Typed - 1-arg - returns nil" do 84 | defmodulet TypedTest_Typed_Identity_AnyReturn do 85 | @spec identity(any()) :: any() 86 | def identity(_x), do: nil 87 | end 88 | assert nil === TypedTest_Typed_Identity_AnyReturn.identity(42) 89 | end 90 | 91 | 92 | test "Untyped - 1-arg - returns nil" do 93 | defmodulet TypedTest_Untyped_Identity_AnyReturn do 94 | def identity(_x), do: nil 95 | end 96 | assert nil === TypedTest_Untyped_Identity_AnyReturn.identity(42) 97 | end 98 | 99 | 100 | test "Typed - 1-arg - recursive" do 101 | defmodulet TypedTest_Typed_Recursive_Counter do 102 | @spec counter(integer()) :: integer() 103 | def counter(x), do: counter(x) 104 | end 105 | 106 | assert_compile_time_throw {:NO_TYPE_RESOLUTION, %TypedElixir.Type.Const{const: :float, meta: %{values: [6.28]}}, %TypedElixir.Type.Const{const: :integer, meta: %{}}}, fn -> 107 | use TypedElixir 108 | defmodulet TypedTest_Typed_Recursive_Counter_Bad do 109 | @spec counter(integer()) :: integer() 110 | def counter(x), do: counter(6.28) 111 | end 112 | end 113 | end 114 | 115 | 116 | test "Untyped - 1-arg - recursive" do 117 | defmodulet TypedTest_Untyped_Recursive_Counter do 118 | def counter(x), do: counter(x) 119 | end 120 | 121 | defmodulet TypedTest_Untyped_Recursive_Counter_RecallingDifferentType do 122 | def counter(_x), do: counter(6.28) 123 | end 124 | end 125 | 126 | 127 | test "Typed - 0-arg/1-arg" do 128 | defmodulet TypedTest_Typed_MultiFunc0 do 129 | @spec simple() :: nil 130 | def simple(), do: nil 131 | 132 | @type identity_type :: any() 133 | @spec identity(identity_type) :: identity_type 134 | def identity(x), do: x 135 | 136 | @spec call_simple(any()) :: any() 137 | def call_simple(_x), do: simple() 138 | 139 | @spec call_simple_constrain_to_nil(any()) :: nil 140 | def call_simple_constrain_to_nil(_x), do: simple() 141 | 142 | @spec call_simple_through_identity(any()) :: nil 143 | def call_simple_through_identity(_x), do: simple() |> identity() 144 | end 145 | end 146 | 147 | 148 | test "rest" do 149 | # defmodulet TypedTest1 do 150 | # @moduledoc false 151 | # 152 | # @spec simple() :: nil 153 | # def simple(), do: nil 154 | # 155 | # @type identity_type :: any() 156 | # @spec identity(identity_type) :: identity_type 157 | # def identity(x), do: x 158 | # end 159 | # 160 | # defmodulet TypedTest2 do 161 | # @moduledoc false 162 | # 163 | # @spec simple(s) :: nil 164 | # def simple(s), do: String.capitalize(s) 165 | # end 166 | # 167 | # defmodulet TypedTest3 do 168 | # @moduledoc false 169 | # 170 | # alias String, as: S 171 | # 172 | # @spec simple(s) :: nil 173 | # def simple(s), do: S.capitalize(s) 174 | # end 175 | # 176 | # defmodulet TypedTest3 do 177 | # @moduledoc false 178 | # 179 | # import String 180 | # 181 | # @spec simple(s) :: nil 182 | # def simple(s), do: capitalize(s) 183 | # end 184 | 185 | # defmodulet TypedTest do 186 | # @moduledoc false 187 | # 188 | # import String 189 | # 190 | # @type test_type :: String.t 191 | # 192 | # @spec fun_default() :: nil 193 | # @spec fun_default(any()) :: nil 194 | # 195 | # @spec simple() :: nil 196 | # def simple(), do: nil 197 | # 198 | # @spec hello(test_type) :: test_type | Map.t 199 | # def hello(str) when is_binary(str) do 200 | # @spec ret :: String.t 201 | # ret = str |> trim 202 | # ret = ret <> " world" 203 | # ret 204 | # end 205 | # def hello(obj) do 206 | # "unknown hello: #{inspect obj}" 207 | # end 208 | # 209 | # def fun_default(_t \\ 42), do: nil 210 | # 211 | # def fun_no_spec(_t \\ 42), do: nil 212 | # 213 | # @type pattern :: nil 214 | # @opaque t :: nil 215 | # @spec replace(t, pattern | Regex.t, t, Keyword.t) :: t 216 | # def replace(_,_,_,_), do: nil 217 | # end 218 | 219 | # IO.inspect TypedElixir.get_module_types_funspecs(String) 220 | 221 | # [{_modulename, objbin}] = Code.compile_quoted(quote do 222 | # defmodule Testering do 223 | # @type pattern :: nil 224 | # @opaque t :: nil 225 | # @spec replace(t, pattern | Regex.t, t, Keyword.t) :: t 226 | # def replace(_,_,_,_), do: nil 227 | # end 228 | # end) 229 | # {:ok, {_modname, [abstract_code: abscode]}} = :beam_lib.chunks(objbin, [:abstract_code]) 230 | # {:raw_abstract_v1, code} = abscode 231 | # code 232 | # |> Enum.reduce(%{opaques: [], types: [], specs: []}, fn 233 | # {:attribute, _line, :spec, {raw_fun, [raw_first_clause | _rest]} = newspec}, %{specs: spec} = acc -> 234 | # # IO.inspect {"Found spec", raw_fun, raw_first_clause, _rest} 235 | # %{acc | specs: [newspec|spec]} 236 | # {:attribute, _line, :type, {name, type_form, var_form} = newtype}, %{types: type} = acc -> 237 | # # IO.inspect {"Found type", name, type_form, var_form} 238 | # %{acc | types: [newtype|type]} 239 | # {:attribute, _line, :opaque, {name, type_form, var_form} = newopaque}, %{opaques: opaque} = acc -> 240 | # # IO.inspect {"Found opaque", name, type_form, var_form} 241 | # %{acc | opaques: [newopaque|opaque]} 242 | # _, acc -> acc 243 | # end) 244 | # # |> IO.inspect 245 | 246 | # assert "Hello world" == IO.inspect(TypedTest.hello("Hello")) 247 | end 248 | 249 | 250 | end 251 | --------------------------------------------------------------------------------