Skip to content

Commit 3efd1a8

Browse files
ruippeixotogfacebook-github-bot
authored andcommitted
Add custom eqWAlizer type checking for maps:to_list
Summary: `maps:to_list` is currently typed as `to_list(#{K => V}) -> [{K, V}]`, which loses type information for shape-like maps. On this diff we're special-handling it in eqWAlizer such that `to_list(#{Ka => Va, Kb => Vb, ...}) -> [{Ka, Va} | {Kb, Vb} | ...]`. Reviewed By: ilya-klyuchnikov Differential Revision: D59587052 fbshipit-source-id: 072340ea3a0b406281b52dcd0a91739b72e3958b
1 parent c0a4f9a commit 3efd1a8

File tree

4 files changed

+72
-5
lines changed

4 files changed

+72
-5
lines changed

eqwalizer/src/main/scala/com/whatsapp/eqwalizer/tc/ElabApplyCustom.scala

+19-2
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,17 @@ class ElabApplyCustom(pipelineContext: PipelineContext) {
3838
RemoteId("lists", "keysort", 2),
3939
RemoteId("lists", "keystore", 4),
4040
RemoteId("maps", "filter", 2),
41+
RemoteId("maps", "filtermap", 2),
4142
RemoteId("maps", "find", 2),
4243
RemoteId("maps", "fold", 3),
4344
RemoteId("maps", "get", 2),
4445
RemoteId("maps", "get", 3),
4546
RemoteId("maps", "map", 2),
46-
RemoteId("maps", "remove", 2),
4747
RemoteId("maps", "put", 3),
48+
RemoteId("maps", "remove", 2),
49+
RemoteId("maps", "to_list", 1),
4850
RemoteId("maps", "with", 2),
4951
RemoteId("maps", "without", 2),
50-
RemoteId("maps", "filtermap", 2),
5152
RemoteId(CompilerMacro.fake_module, "record_info", 2),
5253
)
5354

@@ -432,6 +433,22 @@ class ElabApplyCustom(pipelineContext: PipelineContext) {
432433
val ty = remove(narrow.asMapType(mapCoercedTy))
433434
(ty, env1)
434435

436+
case RemoteId("maps", "to_list", 1) =>
437+
def mapToList(ty: Type): Type = ty match {
438+
case ShapeMap(props) =>
439+
ListType(UnionType(props.map { prop => TupleType(List(AtomLitType(prop.key), prop.tp)) }.toSet))
440+
case DictMap(keysTy, valuesTy) =>
441+
ListType(TupleType(List(keysTy, valuesTy)))
442+
case UnionType(tys) =>
443+
subtype.join(tys.map(mapToList))
444+
case NoneType =>
445+
NoneType
446+
case unexpected =>
447+
throw new IllegalStateException(s"unexpected non-map $unexpected")
448+
}
449+
val pairTys = narrow.asMapType(coerce(args.head, argTys.head, anyMapTy))
450+
(mapToList(pairTys), env1)
451+
435452
case RemoteId("maps", "with", 2) =>
436453
@tailrec
437454
def toKey(ty: Type)(implicit pos: Pos): Option[String] = ty match {

eqwalizer/test_projects/_cli/otp_funs.cli

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ ets 16
1414
filename 21
1515
gb_sets 26
1616
proplists 51
17-
maps 151
17+
maps 158
1818
lists 174
1919
erlang 410
2020
Per app stats:
2121
kernel 21
2222
erts 410
23-
stdlib 478
23+
stdlib 485

eqwalizer/test_projects/check/src/custom.erl

+25-1
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,30 @@ maps_fold_4_3_neg(M) ->
605605
M
606606
).
607607

608+
-spec maps_to_list_1(#{a => string(), b => number(), c => atom()}) ->
609+
[{a, string()} | {b, number()} | {c, atom()}].
610+
maps_to_list_1(M) -> maps:to_list(M).
611+
612+
-spec maps_to_list_2(#{number() => atom()}) -> [{number(), atom()}].
613+
maps_to_list_2(M) -> maps:to_list(M).
614+
615+
-spec maps_to_list_3(dynamic()) -> dynamic().
616+
maps_to_list_3(M) -> maps:to_list(M).
617+
618+
-spec maps_to_list_4(#{a => string(), b => [number()]} | #{b => [atom()], c => atom()}) ->
619+
[{a, string()} | {b, [number()] | [atom()]} | {c, atom()}].
620+
maps_to_list_4(M) -> maps:to_list(M).
621+
622+
-spec maps_to_list_5(#{a => string()} | #{atom() => map()}) ->
623+
[{a, string()} | {atom(), map()}].
624+
maps_to_list_5(M) -> maps:to_list(M).
625+
626+
-spec maps_to_list_6(map()) -> [dynamic()].
627+
maps_to_list_6(M) -> maps:to_list(M).
628+
629+
-spec maps_to_list_7_neg(number()) -> dynamic().
630+
maps_to_list_7_neg(Num) -> maps:to_list(Num).
631+
608632
-spec lists_filtermap_1() -> [number()].
609633
lists_filtermap_1() ->
610634
lists:filtermap(
@@ -2294,4 +2318,4 @@ maps_fold_keys(M) ->
22942318
).
22952319

22962320
-spec process_key(a | b) -> ok.
2297-
process_key(_K) -> ok.
2321+
process_key(_K) -> ok.

eqwalizer/test_projects/check/src/custom.erl.check

+26
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,32 @@ maps_fold_4_3_neg(M) -> | ERROR |
739739
M | |
740740
). | |
741741
| |
742+
-spec maps_to_list_1(#{a => string(), b =>…… |
743+
[{a, string()} | {b, number()} | {c, a…… |
744+
maps_to_list_1(M) -> maps:to_list(M). | OK |
745+
| |
746+
-spec maps_to_list_2(#{number() => atom()}…… |
747+
maps_to_list_2(M) -> maps:to_list(M). | OK |
748+
| |
749+
-spec maps_to_list_3(dynamic()) -> dynamic…… |
750+
maps_to_list_3(M) -> maps:to_list(M). | OK |
751+
| |
752+
-spec maps_to_list_4(#{a => string(), b =>…… |
753+
[{a, string()} | {b, [number()] | [ato…… |
754+
maps_to_list_4(M) -> maps:to_list(M). | OK |
755+
| |
756+
-spec maps_to_list_5(#{a => string()} | #{…… |
757+
[{a, string()} | {atom(), map()}]. | |
758+
maps_to_list_5(M) -> maps:to_list(M). | OK |
759+
| |
760+
-spec maps_to_list_6(map()) -> [dynamic()]…… |
761+
maps_to_list_6(M) -> maps:to_list(M). | OK |
762+
| |
763+
-spec maps_to_list_7_neg(number()) -> dyna…… |
764+
maps_to_list_7_neg(Num) -> maps:to_list(Nu…… ERROR | Num.
765+
| | Expression has type: number()
766+
| | Context expected type: #D{term() => term()}
767+
| |
742768
-spec lists_filtermap_1() -> [number()]. | |
743769
lists_filtermap_1() -> | OK |
744770
lists:filtermap( | |

0 commit comments

Comments
 (0)