Skip to content

Commit faffec0

Browse files
committed
Type checking of :into
1 parent efb50de commit faffec0

File tree

5 files changed

+77
-13
lines changed

5 files changed

+77
-13
lines changed

lib/elixir/lib/calendar/date.ex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,8 @@ defmodule Date do
188188
end
189189

190190
def utc_today(calendar) do
191-
calendar
192-
|> DateTime.utc_now()
193-
|> DateTime.to_date()
191+
%{year: year, month: month, day: day} = DateTime.utc_now(calendar)
192+
%Date{year: year, month: month, day: day, calendar: calendar}
194193
end
195194

196195
@doc """

lib/elixir/lib/module/types/apply.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,15 @@ defmodule Module.Types.Apply do
838838
it has type:
839839
""", [:generator], collect_traces(arg, context)}
840840

841+
:into ->
842+
{"""
843+
incompatible value given to :into option in for-comprehension:
844+
845+
into: #{expr_to_string(expr) |> indent(4)}
846+
847+
it has type:
848+
""", [:into], collect_traces(expr, context)}
849+
841850
_ ->
842851
mfa_or_fa = if mod, do: Exception.format_mfa(mod, fun, arity), else: "#{fun}/#{arity}"
843852

lib/elixir/lib/module/types/expr.ex

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,10 @@ defmodule Module.Types.Expr do
338338
end
339339

340340
# TODO: for pat <- expr do expr end
341-
def of_expr({:for, _meta, [_ | _] = args}, stack, context) do
341+
def of_expr({:for, meta, [_ | _] = args}, stack, context) do
342342
{clauses, [[{:do, block} | opts]]} = Enum.split(args, -1)
343343
context = Enum.reduce(clauses, context, &for_clause(&1, stack, &2))
344-
context = Enum.reduce(opts, context, &for_option(&1, stack, &2))
344+
context = Enum.reduce(opts, context, &for_option(&1, meta, stack, &2))
345345

346346
if Keyword.has_key?(opts, :reduce) do
347347
{_, context} = of_clauses(block, [dynamic()], :for_reduce, stack, {none(), context})
@@ -504,17 +504,29 @@ defmodule Module.Types.Expr do
504504
context
505505
end
506506

507-
defp for_option({:into, expr}, stack, context) do
508-
{_type, context} = of_expr(expr, stack, context)
507+
defp for_option({:into, expr}, meta, stack, context) do
508+
{type, context} = of_expr(expr, stack, context)
509+
510+
meta =
511+
case expr do
512+
{_, meta, _} -> meta
513+
_ -> meta
514+
end
515+
516+
wrapped_expr = {:__block__, [type_check: :into] ++ meta, [expr]}
517+
518+
{_type, context} =
519+
Apply.remote(Collectable, :into, [expr], [type], wrapped_expr, stack, context)
520+
509521
context
510522
end
511523

512-
defp for_option({:reduce, expr}, stack, context) do
524+
defp for_option({:reduce, expr}, _meta, stack, context) do
513525
{_type, context} = of_expr(expr, stack, context)
514526
context
515527
end
516528

517-
defp for_option({:uniq, _}, _stack, context) do
529+
defp for_option({:uniq, _}, _meta, _stack, context) do
518530
# This option is verified to be a boolean at compile-time
519531
context
520532
end

lib/elixir/lib/module/types/helpers.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,26 @@ defmodule Module.Types.Helpers do
8585
:interpolation ->
8686
"""
8787
88-
#{hint()} string interpolation in Elixir uses the String.Chars protocol to \
88+
#{hint()} string interpolation uses the String.Chars protocol to \
8989
convert a data structure into a string. Either convert the data type into a \
9090
string upfront or implement the protocol accordingly
9191
"""
9292

9393
:generator ->
9494
"""
9595
96-
#{hint()} for-comprehensions in Elixir use the Enumerable protocol to traverse \
96+
#{hint()} for-comprehensions use the Enumerable protocol to traverse \
9797
data structures. Either convert the data type into a list (or another Enumerable) \
9898
or implement the protocol accordingly
9999
"""
100100

101+
:into ->
102+
"""
103+
104+
#{hint()} the :into option in for-comprehensions use the Enumerable protocol to \
105+
build its result. Either pass a valid data type or implement the protocol accordingly
106+
"""
107+
101108
:anonymous_rescue ->
102109
"""
103110

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

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ defmodule Module.Types.IntegrationTest do
405405
# from: a.ex:3:24
406406
_.._//_ = data
407407
408-
hint: string interpolation in Elixir uses the String.Chars protocol to convert a data structure into a string. Either convert the data type into a string upfront or implement the protocol accordingly
408+
hint: string interpolation uses the String.Chars protocol to convert a data structure into a string. Either convert the data type into a string upfront or implement the protocol accordingly
409409
""",
410410
"""
411411
warning: incompatible types given to String.Chars.to_string/1:
@@ -437,6 +437,8 @@ defmodule Module.Types.IntegrationTest do
437437
"a.ex" => """
438438
defmodule FooBar do
439439
def example1(%Date{} = date), do: for(x <- date, do: x)
440+
def example2(), do: for(i <- [1, 2, 3], into: Date.utc_today(), do: i * 2)
441+
def example3(), do: for(i <- [1, 2, 3], into: 456, do: i * 2)
440442
end
441443
"""
442444
}
@@ -462,7 +464,42 @@ defmodule Module.Types.IntegrationTest do
462464
# from: a.ex:2:24
463465
%Date{} = date
464466
465-
hint: for-comprehensions in Elixir use the Enumerable protocol to traverse data structures. Either convert the data type into a list (or another Enumerable) or implement the protocol accordingly
467+
hint: for-comprehensions use the Enumerable protocol to traverse data structures. Either convert the data type into a list (or another Enumerable) or implement the protocol accordingly
468+
""",
469+
"""
470+
warning: incompatible value given to :into option in for-comprehension:
471+
472+
into: Date.utc_today()
473+
474+
it has type:
475+
476+
-dynamic(
477+
%Date{year: term(), month: integer(), day: float() or integer(), calendar: Calendar.ISO} or
478+
%Date{year: term(), month: term(), day: term(), calendar: term()}
479+
)-
480+
481+
but expected a type that implements the Collectable protocol, it must be one of:
482+
483+
%File.Stream{} or %HashDict{} or %HashSet{} or %IO.Stream{} or %MapSet{} or binary() or
484+
list(term()) or non_struct_map()
485+
486+
hint: the :into option in for-comprehensions use the Enumerable protocol to build its result. Either pass a valid data type or implement the protocol accordingly
487+
""",
488+
"""
489+
warning: incompatible value given to :into option in for-comprehension:
490+
491+
into: 456
492+
493+
it has type:
494+
495+
-integer()-
496+
497+
but expected a type that implements the Collectable protocol, it must be one of:
498+
499+
%File.Stream{} or %HashDict{} or %HashSet{} or %IO.Stream{} or %MapSet{} or binary() or
500+
list(term()) or non_struct_map()
501+
502+
hint: the :into option in for-comprehensions use the Enumerable protocol to build its result. Either pass a valid data type or implement the protocol accordingly
466503
"""
467504
]
468505

0 commit comments

Comments
 (0)