Skip to content

Commit 0043b1e

Browse files
committed
Use dynamic(type) on pretty printing
1 parent 18ba88c commit 0043b1e

File tree

5 files changed

+41
-39
lines changed

5 files changed

+41
-39
lines changed

lib/elixir/lib/module/types/descr.ex

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ defmodule Module.Types.Descr do
8989

9090
## Set operations
9191

92+
def term_type?(@term), do: true
9293
def term_type?(descr), do: subtype_static(@term, Map.delete(descr, :dynamic))
94+
9395
def gradual?(descr), do: is_map_key(descr, :dynamic)
9496

9597
@doc """
@@ -212,14 +214,14 @@ defmodule Module.Types.Descr do
212214
@doc """
213215
Converts a descr to its quoted representation.
214216
"""
215-
def to_quoted(@term) do
216-
{:term, [], []}
217-
end
218-
219217
def to_quoted(%{} = descr) do
220-
case Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value) end) do
221-
[] -> {:none, [], []}
222-
unions -> unions |> Enum.sort() |> Enum.reduce(&{:or, [], [&2, &1]})
218+
if term_type?(descr) do
219+
{:term, [], []}
220+
else
221+
case Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value) end) do
222+
[] -> {:none, [], []}
223+
unions -> unions |> Enum.sort() |> Enum.reduce(&{:or, [], [&2, &1]})
224+
end
223225
end
224226
end
225227

@@ -591,7 +593,7 @@ defmodule Module.Types.Descr do
591593
cond do
592594
term_type?(descr) -> [{:dynamic, [], []}]
593595
single = indivisible_bitmap(descr) -> [single]
594-
true -> [{:and, [], [{:dynamic, [], []}, to_quoted(descr)]}]
596+
true -> [{:dynamic, [], [to_quoted(descr)]}]
595597
end
596598
end
597599

lib/elixir/pages/references/gradual-set-theoretic-types.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ This function also has the type `(boolean() -> boolean())`, because it receives
3737

3838
At this point, some may ask, why not a union? As a real-world example, take a t-shirt with green and yellow stripes. We can say the t-shirt belongs to the set of "t-shirts with green color". We can also say the t-shirt belongs to the set of "t-shirts with yellow color". Let's see the difference between unions and intersections:
3939

40-
* `(t_shirts_with_green() or t_shirts_with_yellow())` - contains t-shirts with either green or yellow, such as green, green and red, green and blue, yellow, yellow and red, etc.
40+
* `(t_shirts_with_green() or t_shirts_with_yellow())` - contains t-shirts with either green or yellow, such as green, green and red, green and yellow, yellow, yellow and red, etc.
4141

4242
* `(t_shirts_with_green() and t_shirts_with_yellow())` - contains t-shirts with both green and yellow (and also other colors)
4343

@@ -62,15 +62,15 @@ The simplest way to reason about `dynamic()` in Elixir is that it is a range of
6262

6363
However, by intersecting any type with `dynamic()`, we make the type gradual and therefore only a subset of the type needs to be valid. For instance, if you call `Integer.to_string(var)`, and `var` has type `dynamic() and (atom() or integer())`, the type system will not emit a warning, because `Integer.to_string/1` works with at least one of the types. This flexibility makes `dynamic()` excellent for typing existing code, because it will only emit warnings once it is certain the code will fail. For convenience, most programs will write `dynamic(atom() or integer())` instead of the intersection. They have the same behaviour.
6464

65-
Once Elixir introduces a type language into its API, Elixir programs will behave as a statically typed language, unless the `dynamic` type is used. This brings us to one last remark about dynamic types in Elixir: dyamic types are always at the root. For example, when you write a tuple of type `{:ok, dynamic()}`, Elixir will rewrite it to `dynamic({:ok, term()})`. While this has the downside that you cannot make part of a tuple/map/list gradual, only the whole tuple/map/list, it comes with the upside that dynamic is always explicitly at the root, making it harder to accidentally sneak `dynamic()` in a statically typed program.
65+
Once Elixir introduces typed function signatures, typed Elixir programs will behave as a statically typed code, unless the `dynamic()` type is used. This brings us to one last remark about dynamic types in Elixir: dyamic types are always at the root. For example, when you write a tuple of type `{:ok, dynamic()}`, Elixir will rewrite it to `dynamic({:ok, term()})`. While this has the downside that you cannot make part of a tuple/map/list gradual, only the whole tuple/map/list, it comes with the upside that dynamic is always explicitly at the root, making it harder to accidentally sneak `dynamic()` in a statically typed program.
6666

6767
## Roadmap
6868

6969
The current milestone is to implement type inference and type checking of Elixir programs without changes to the Elixir language. At this stage, we want to collect feedback on the quality of error messages and performance, and therefore the type system has no user facing API.
7070

7171
If the results are satisfactory, the next milestone will include a mechanism for defining typed structs. Elixir programs frequently pattern match on structs, which reveals information about the struct fields, but it knows nothing about their respective types. By propagating types from structs and their fields throughout the program, we will increase the type system’s ability to find errors while further straining our type system implementation. Proposals including the required changes to the language surface will be sent to the community once we reach this stage.
7272

73-
The third milestone is to introduce the type annotations for functions. Once we conclude this stage, the existing typespecs system will be phased out of the language and moved into a separate library.
73+
The third milestone is to introduce set-theoretic type signatures for functions. Unfortunately, the existing typespecs are not precise enough for set-theoretic types and they will be phased out of the language and moved into a separate library once this stage concludes.
7474

7575
## Acknowledgements
7676

lib/elixir/test/elixir/module/types/descr_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,15 +374,15 @@ defmodule Module.Types.DescrTest do
374374
assert intersection(binary(), dynamic()) |> to_quoted_string() == "binary()"
375375

376376
assert intersection(union(binary(), pid()), dynamic()) |> to_quoted_string() ==
377-
"dynamic() and (binary() or pid())"
377+
"dynamic(binary() or pid())"
378378

379-
assert intersection(atom(), dynamic()) |> to_quoted_string() == "dynamic() and atom()"
379+
assert intersection(atom(), dynamic()) |> to_quoted_string() == "dynamic(atom())"
380380

381381
assert union(atom([:foo, :bar]), dynamic()) |> to_quoted_string() ==
382382
"dynamic() or (:bar or :foo)"
383383

384384
assert intersection(dynamic(), closed_map(a: integer())) |> to_quoted_string() ==
385-
"dynamic() and %{a: integer()}"
385+
"dynamic(%{a: integer()})"
386386
end
387387

388388
test "map" do

lib/elixir/test/elixir/module/types/expr_test.exs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ defmodule Module.Types.ExprTest do
229229
230230
expected type:
231231
232-
dynamic() and %Point{x: term(), y: term(), z: term()}
232+
dynamic(%Point{x: term(), y: term(), z: term()})
233233
234234
but got type:
235235
@@ -279,7 +279,7 @@ defmodule Module.Types.ExprTest do
279279
280280
the given type does not have the given key:
281281
282-
dynamic() and %Point{x: nil, y: nil, z: integer()}
282+
dynamic(%Point{x: nil, y: nil, z: integer()})
283283
284284
typing violation found at:\
285285
"""}
@@ -295,17 +295,16 @@ defmodule Module.Types.ExprTest do
295295
296296
where "x" was given the type:
297297
298-
# type: dynamic() and
299-
%URI{
300-
authority: term(),
301-
fragment: term(),
302-
host: term(),
303-
path: term(),
304-
port: term(),
305-
query: term(),
306-
scheme: term(),
307-
userinfo: term()
308-
}
298+
# type: dynamic(%URI{
299+
authority: term(),
300+
fragment: term(),
301+
host: term(),
302+
path: term(),
303+
port: term(),
304+
query: term(),
305+
scheme: term(),
306+
userinfo: term()
307+
})
309308
# from: types_test.ex:LINE-2
310309
x = %URI{}
311310
@@ -369,7 +368,7 @@ defmodule Module.Types.ExprTest do
369368
370369
where "y" was given the type:
371370
372-
# type: dynamic() and %Point{x: term(), y: term(), z: term()}
371+
# type: dynamic(%Point{x: term(), y: term(), z: term()})
373372
# from: types_test.ex:LINE-2
374373
y = %Point{}
375374
@@ -421,16 +420,17 @@ defmodule Module.Types.ExprTest do
421420
422421
where "e" was given the type:
423422
424-
# type: dynamic() and
425-
(%RuntimeError{__exception__: true, message: term()} or
426-
%SyntaxError{
427-
__exception__: true,
428-
column: term(),
429-
description: term(),
430-
file: term(),
431-
line: term(),
432-
snippet: term()
433-
})
423+
# type: dynamic(
424+
%RuntimeError{__exception__: true, message: term()} or
425+
%SyntaxError{
426+
__exception__: true,
427+
column: term(),
428+
description: term(),
429+
file: term(),
430+
line: term(),
431+
snippet: term()
432+
}
433+
)
434434
# from: types_test.ex:LINE-5
435435
e in [SyntaxError, RuntimeError]
436436

lib/elixir/test/elixir/module/types/pattern_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ defmodule Module.Types.PatternTest do
7171
7272
where "x" was given the type:
7373
74-
# type: dynamic() and %Point{x: term(), y: term(), z: term()}
74+
# type: dynamic(%Point{x: term(), y: term(), z: term()})
7575
# from: types_test.ex:LINE-2
7676
x = %Point{}
7777

0 commit comments

Comments
 (0)