@@ -1263,6 +1263,11 @@ defmodule Module.Types.DescrTest do
12631263
12641264 assert tuple ( [ closed_map ( a: integer ( ) ) , open_map ( ) ] ) |> to_quoted_string ( ) ==
12651265 "{%{a: integer()}, %{...}}"
1266+
1267+ # TODO: eliminate tuple differences
1268+ # assert difference(tuple([number(), term()]), tuple([integer(), atom()]))
1269+ # |> to_quoted_string() ==
1270+ # "{float(), term()} or {number(), term() and not atom()}"
12661271 end
12671272
12681273 test "map" do
@@ -1278,9 +1283,8 @@ defmodule Module.Types.DescrTest do
12781283 assert open_map ( "Elixir.Foo.Bar": float ( ) ) |> to_quoted_string ( ) ==
12791284 "%{..., Foo.Bar => float()}"
12801285
1281- # TODO: support this simplification
1282- # assert difference(open_map(), open_map(a: term())) |> to_quoted_string() ==
1283- # "%{..., a: not_set()}"
1286+ assert difference ( open_map ( ) , open_map ( a: term ( ) ) ) |> to_quoted_string ( ) ==
1287+ "%{..., a: not_set()}"
12841288
12851289 assert closed_map ( a: integer ( ) , b: atom ( ) ) |> to_quoted_string ( ) ==
12861290 "%{a: integer(), b: atom()}"
@@ -1295,13 +1299,40 @@ defmodule Module.Types.DescrTest do
12951299 assert closed_map ( foo: union ( integer ( ) , not_set ( ) ) ) |> to_quoted_string ( ) ==
12961300 "%{foo: if_set(integer())}"
12971301
1298- assert difference ( open_map ( a: integer ( ) ) , closed_map ( b: boolean ( ) ) ) |> to_quoted_string ( ) ==
1299- "%{..., a: integer()}"
1300-
1302+ # Test normalization
13011303 assert open_map ( a: integer ( ) , b: atom ( ) )
13021304 |> difference ( open_map ( b: atom ( ) ) )
13031305 |> union ( open_map ( a: integer ( ) ) )
13041306 |> to_quoted_string ( ) == "%{..., a: integer()}"
1307+
1308+ assert union ( open_map ( a: integer ( ) ) , open_map ( a: integer ( ) ) ) |> to_quoted_string ( ) ==
1309+ "%{..., a: integer()}"
1310+
1311+ assert difference ( open_map ( a: number ( ) , b: atom ( ) ) , open_map ( a: integer ( ) ) )
1312+ |> to_quoted_string ( ) == "%{..., a: float(), b: atom()}"
1313+
1314+ # Test complex combinations
1315+ assert intersection ( open_map ( a: number ( ) , b: atom ( ) ) , open_map ( a: integer ( ) , c: boolean ( ) ) )
1316+ |> union ( difference ( open_map ( x: atom ( ) ) , open_map ( x: boolean ( ) ) ) )
1317+ |> to_quoted_string ( ) ==
1318+ "%{..., a: integer(), b: atom(), c: boolean()} or %{..., x: atom() and not boolean()}"
1319+
1320+ assert closed_map ( a: number ( ) , b: atom ( ) , c: pid ( ) )
1321+ |> difference ( closed_map ( a: integer ( ) , b: atom ( ) , c: pid ( ) ) )
1322+ |> to_quoted_string ( ) == "%{a: float(), b: atom(), c: pid()}"
1323+
1324+ # No simplification compared to above, as it is an open map
1325+ assert open_map ( a: number ( ) , b: atom ( ) )
1326+ |> difference ( closed_map ( a: integer ( ) , b: atom ( ) ) )
1327+ |> to_quoted_string ( ) ==
1328+ "%{..., a: float() or integer(), b: atom()} and not %{a: integer(), b: atom()}"
1329+
1330+ # Remark: this simplification is order dependent. Having the first difference
1331+ # after the second gives a different result.
1332+ assert open_map ( a: number ( ) , b: atom ( ) , c: union ( pid ( ) , port ( ) ) )
1333+ |> difference ( open_map ( a: float ( ) , b: atom ( ) , c: pid ( ) ) )
1334+ |> difference ( open_map ( a: integer ( ) , b: atom ( ) , c: union ( pid ( ) , port ( ) ) ) )
1335+ |> to_quoted_string ( ) == "%{..., a: float(), b: atom(), c: port()}"
13051336 end
13061337
13071338 test "structs" do
@@ -1344,5 +1375,56 @@ defmodule Module.Types.DescrTest do
13441375 assert subtype? ( descr1 , descr2 )
13451376 refute subtype? ( descr2 , descr1 )
13461377 end
1378+
1379+ test "map difference" do
1380+ # Create a large map with various types
1381+ map1 =
1382+ open_map ( [
1383+ { :id , integer ( ) } ,
1384+ { :name , binary ( ) } ,
1385+ { :age , union ( integer ( ) , atom ( ) ) } ,
1386+ { :email , binary ( ) } ,
1387+ { :active , boolean ( ) } ,
1388+ { :tags , list ( atom ( ) ) }
1389+ ] )
1390+
1391+ # Create another large map with some differences and many more entries
1392+ map2 =
1393+ open_map (
1394+ [
1395+ { :id , integer ( ) } ,
1396+ { :name , binary ( ) } ,
1397+ { :age , integer ( ) } ,
1398+ { :email , binary ( ) } ,
1399+ { :active , boolean ( ) } ,
1400+ { :tags , non_empty_list ( atom ( ) ) } ,
1401+ { :meta ,
1402+ open_map ( [
1403+ { :created_at , binary ( ) } ,
1404+ { :updated_at , binary ( ) } ,
1405+ { :status , atom ( ) }
1406+ ] ) } ,
1407+ { :permissions , tuple ( [ atom ( ) , integer ( ) , atom ( ) ] ) } ,
1408+ { :profile ,
1409+ open_map ( [
1410+ { :bio , binary ( ) } ,
1411+ { :interests , non_empty_list ( binary ( ) ) } ,
1412+ { :social_media ,
1413+ open_map ( [
1414+ { :twitter , binary ( ) } ,
1415+ { :instagram , binary ( ) } ,
1416+ { :linkedin , binary ( ) }
1417+ ] ) }
1418+ ] ) } ,
1419+ { :notifications , boolean ( ) }
1420+ ] ++
1421+ Enum . map ( 1 .. 50 , fn i ->
1422+ { :"field_#{ i } " , atom ( [ :"value_#{ i } " ] ) }
1423+ end )
1424+ )
1425+
1426+ refute subtype? ( map1 , map2 )
1427+ assert subtype? ( map2 , map1 )
1428+ end
13471429 end
13481430end
0 commit comments