Skip to content

Commit 3ff553c

Browse files
ruippeixotogfacebook-github-bot
authored andcommitted
Improve typing of maps:map when the input is a shape
Summary: Given `F :: fun((AnyKey, AnyVal) -> Ret)`: - Previously, `maps:map(F, #{Ka => Va, Kb => Vb, ...}) -> #{Ka | Kb | ... => Ret}`. - After this diff, `maps:map(F, #{Ka => Va, Kb => Vb, ...}) -> #{Ka => Ret, Kb => Ret, ...}`. Reviewed By: VLanvin Differential Revision: D59867075 fbshipit-source-id: 5850c1515afc5ba27ec79d40239262e8021360b6
1 parent 1a2d1cf commit 3ff553c

File tree

3 files changed

+49
-3
lines changed

3 files changed

+49
-3
lines changed

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,16 @@ class ElabApplyCustom(pipelineContext: PipelineContext) {
363363
val vTys = narrow.asFunType(funArgCoercedTy, 2).get.map(_.resTy)
364364
subtype.join(vTys)
365365
}
366-
(DictMap(keyTy, resValTy), env1)
366+
def mapValueType(argMapTy: Type, argValTy: Type): Type = argMapTy match {
367+
case ShapeMap(props) =>
368+
ShapeMap(props.map {
369+
case ReqProp(key, ty) => ReqProp(key, argValTy)
370+
case OptProp(key, ty) => OptProp(key, argValTy)
371+
})
372+
case UnionType(tys) => subtype.join(tys.map(mapValueType(_, argValTy)))
373+
case _ => DictMap(keyTy, resValTy)
374+
}
375+
(mapValueType(mapType, resValTy), env1)
367376

368377
case RemoteId("maps", "filtermap", 2) =>
369378
val List(funArg, map) = args

eqwalizer/test_projects/check/src/custom.erl

+16
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,22 @@ maps_map_2_10_neg() ->
479479
maps:map(F, non_kv),
480480
nok.
481481

482+
-spec maps_map_2_11(
483+
fun((dynamic(), dynamic()) -> ret),
484+
#{ka => va, kb => vb}
485+
) ->
486+
#{ka => ret, kb => ret}.
487+
maps_map_2_11(Fun, M) ->
488+
maps:map(Fun, M).
489+
490+
-spec maps_map_2_12(
491+
fun((dynamic(), dynamic()) -> ret),
492+
#{ka => va1, kb => vb1} | #{ka => va2, kb => vb2} | #{kb => vb3, kc => vc3}
493+
) ->
494+
#{ka => ret, kb => ret} | #{kb => ret, kc => ret}.
495+
maps_map_2_12(Fun, M) ->
496+
maps:map(Fun, M).
497+
482498
-spec maps_fold_3_1()
483499
-> [number() | a | b].
484500
maps_fold_3_1() ->

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

+23-2
Original file line numberDiff line numberDiff line change
@@ -572,8 +572,13 @@ maps_map_2_9_neg() -> | ERROR |
572572
M = #{a => a, b => b}, | |
573573
F = fun erlang:'=:='/2, | |
574574
maps:map(F, M). | | maps:map(F, M).
575-
| | Expression has type: dict map #D{'a' | 'b' => boolean()}
576-
| | Context expected type: shape map #S{a => 'a', b => 'b'}
575+
| | Expression has type: #S{a := boolean(), b := boolean()}
576+
| | Context expected type: #S{a => 'a', b => 'b'}
577+
| |
578+
| | at shape key 'a':
579+
| | #S{a := boolean(), b := boolean()} is not compatible with #S{a => 'a', b => 'b'}
580+
| | because
581+
| | boolean() is not compatible with 'a'
577582
| |
578583
-spec maps_map_2_10_neg() | |
579584
-> nok. | |
@@ -584,6 +589,22 @@ maps_map_2_10_neg() -> | ERROR |
584589
| | Context expected type: #D{term() => term()}
585590
nok. | |
586591
| |
592+
-spec maps_map_2_11( | |
593+
fun((dynamic(), dynamic()) -> ret), | |
594+
#{ka => va, kb => vb} | |
595+
) -> | |
596+
#{ka => ret, kb => ret}. | |
597+
maps_map_2_11(Fun, M) -> | OK |
598+
maps:map(Fun, M). | |
599+
| |
600+
-spec maps_map_2_12( | |
601+
fun((dynamic(), dynamic()) -> ret), | |
602+
#{ka => va1, kb => vb1} | #{ka => va2,…… |
603+
) -> | |
604+
#{ka => ret, kb => ret} | #{kb => ret,…… |
605+
maps_map_2_12(Fun, M) -> | OK |
606+
maps:map(Fun, M). | |
607+
| |
587608
-spec maps_fold_3_1() | |
588609
-> [number() | a | b]. | |
589610
maps_fold_3_1() -> | OK |

0 commit comments

Comments
 (0)