summaryrefslogtreecommitdiff
path: root/lib/dialyzer/test/opaque_SUITE_data
diff options
context:
space:
mode:
authorSverker Eriksson <sverker@erlang.org>2017-08-30 20:55:08 +0200
committerSverker Eriksson <sverker@erlang.org>2017-08-30 20:55:08 +0200
commit7c67bbddb53c364086f66260701bc54a61c9659c (patch)
tree92ab0d4b91d5e2f6e7a3f9d61ea25089e8a71fe0 /lib/dialyzer/test/opaque_SUITE_data
parent97dc5e7f396129222419811c173edc7fa767b0f8 (diff)
parent3b7a6ffddc819bf305353a593904cea9e932e7dc (diff)
downloaderlang-7c67bbddb53c364086f66260701bc54a61c9659c.tar.gz
Merge tag 'OTP-19.0' into sverker/19/binary_to_atom-utf8-crash/ERL-474/OTP-14590
Diffstat (limited to 'lib/dialyzer/test/opaque_SUITE_data')
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/array4
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/crash10
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/dict26
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/ets5
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/ewgi6
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/inf_loop12
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/inf_loop25
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/int4
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/mixed_opaque2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/modules3
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/my_queue2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/opaque1
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/para33
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/queue21
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/simple92
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/timer2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/wings14
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/big_external_type.erl526
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/big_local_type.erl523
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_macros.hrl215
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_types.erl607
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_types.hrl26
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/dict/dict_use.erl2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/ets/ets_use.erl10
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/ewgi/ewgi.hrl2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/ewgi2/ewgi.hrl2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/gb_sets/gb_sets_rec.erl4
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/inf_loop1.erl4
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/inf_loop2.erl175
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/modules/opaque_digraph.erl655
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/modules/opaque_erl_scan.erl1301
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_adt.erl2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_bug5.erl10
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/myqueue.erl17
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/myqueue_params.erl15
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para1.erl93
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para1_adt.erl36
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para2.erl123
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para2_adt.erl64
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para3.erl77
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para3_adt.erl27
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl134
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl108
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para5.erl33
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/para/para5_adt.erl36
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_common.hrl55
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_gen.erl624
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_internal.hrl92
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_types.erl1349
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_typeserver.erl2402
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/simple/exact_adt.erl17
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/simple/exact_api.erl60
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/simple/is_rec.erl65
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/simple/rec_adt.erl28
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/simple/rec_api.erl123
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/simple/simple1_adt.erl138
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/simple/simple1_api.erl571
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/simple/simple2_api.erl125
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/union/union_adt.erl9
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/wings/wings_util.erl4
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/zoltan_adt.erl5
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis2.erl2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis3.erl4
64 files changed, 10670 insertions, 64 deletions
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/array b/lib/dialyzer/test/opaque_SUITE_data/results/array
index b05d088a03..9921b61669 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/array
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/array
@@ -1,3 +1,3 @@
-array_use.erl:12: The type test is_tuple(array()) breaks the opaqueness of the term array()
-array_use.erl:9: The attempt to match a term of type array() against the pattern {'array', _, _, 'undefined', _} breaks the opaqueness of the term
+array_use.erl:12: The type test is_tuple(array:array(_)) breaks the opaqueness of the term array:array(_)
+array_use.erl:9: The attempt to match a term of type array:array(_) against the pattern {'array', _, _, 'undefined', _} breaks the opaqueness of the term
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/crash b/lib/dialyzer/test/opaque_SUITE_data/results/crash
index 1ddae5149f..d63389f79c 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/crash
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/crash
@@ -1,6 +1,6 @@
-crash_1.erl:45: Record construction #targetlist{list::[]} violates the declared type of field list::'undefined' | crash_1:target()
-crash_1.erl:48: The call crash_1:get_using_branch2(Branch::maybe_improper_list(),L::'undefined' | crash_1:target()) contains an opaque term as 2nd argument when terms of different types are expected in these positions
-crash_1.erl:50: The pattern <_Branch, []> can never match the type <maybe_improper_list(),'undefined' | crash_1:target()>
-crash_1.erl:52: The pattern <Branch, [H = {'target', _, _} | _T]> can never match the type <maybe_improper_list(),'undefined' | crash_1:target()>
-crash_1.erl:54: The pattern <Branch, [{'target', _, _} | T]> can never match the type <maybe_improper_list(),'undefined' | crash_1:target()>
+crash_1.erl:45: Record construction #targetlist{list::[]} violates the declared type of field list::crash_1:target()
+crash_1.erl:48: The call crash_1:get_using_branch2(Branch::maybe_improper_list(),L::crash_1:target()) will never return since it differs in the 2nd argument from the success typing arguments: (any(),maybe_improper_list())
+crash_1.erl:50: The pattern <_Branch, []> can never match the type <maybe_improper_list(),crash_1:target()>
+crash_1.erl:52: The pattern <Branch, [H = {'target', _, _} | _T]> can never match the type <maybe_improper_list(),crash_1:target()>
+crash_1.erl:54: The pattern <Branch, [{'target', _, _} | T]> can never match the type <maybe_improper_list(),crash_1:target()>
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/dict b/lib/dialyzer/test/opaque_SUITE_data/results/dict
index 5c6bf6a927..42f6663191 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/dict
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/dict
@@ -1,15 +1,15 @@
-dict_use.erl:41: The attempt to match a term of type dict() against the pattern 'gazonk' breaks the opaqueness of the term
-dict_use.erl:45: The attempt to match a term of type dict() against the pattern [] breaks the opaqueness of the term
-dict_use.erl:46: The attempt to match a term of type dict() against the pattern 42 breaks the opaqueness of the term
-dict_use.erl:51: The attempt to match a term of type dict() against the pattern [] breaks the opaqueness of the term
-dict_use.erl:52: The attempt to match a term of type dict() against the pattern 42 breaks the opaqueness of the term
-dict_use.erl:58: Attempt to test for equality between a term of type maybe_improper_list() and a term of opaque type dict()
-dict_use.erl:60: Attempt to test for inequality between a term of type atom() and a term of opaque type dict()
-dict_use.erl:64: Guard test length(D::dict()) breaks the opaqueness of its argument
-dict_use.erl:65: Guard test is_atom(D::dict()) breaks the opaqueness of its argument
-dict_use.erl:66: Guard test is_list(D::dict()) breaks the opaqueness of its argument
-dict_use.erl:70: The type test is_list(dict()) breaks the opaqueness of the term dict()
-dict_use.erl:73: The call dict:fetch('foo',[1 | 2 | 3,...]) does not have an opaque term of type dict() as 2nd argument
+dict_use.erl:41: The attempt to match a term of type dict:dict(_,_) against the pattern 'gazonk' breaks the opaqueness of the term
+dict_use.erl:45: The attempt to match a term of type dict:dict(_,_) against the pattern [] breaks the opaqueness of the term
+dict_use.erl:46: The attempt to match a term of type dict:dict(_,_) against the pattern 42 breaks the opaqueness of the term
+dict_use.erl:51: The attempt to match a term of type dict:dict(_,_) against the pattern [] breaks the opaqueness of the term
+dict_use.erl:52: The attempt to match a term of type dict:dict(_,_) against the pattern 42 breaks the opaqueness of the term
+dict_use.erl:58: Attempt to test for equality between a term of type maybe_improper_list() and a term of opaque type dict:dict(_,_)
+dict_use.erl:60: Attempt to test for inequality between a term of type atom() and a term of opaque type dict:dict(_,_)
+dict_use.erl:64: Guard test length(D::dict:dict(_,_)) breaks the opaqueness of its argument
+dict_use.erl:65: Guard test is_atom(D::dict:dict(_,_)) breaks the opaqueness of its argument
+dict_use.erl:66: Guard test is_list(D::dict:dict(_,_)) breaks the opaqueness of its argument
+dict_use.erl:70: The type test is_list(dict:dict(_,_)) breaks the opaqueness of the term dict:dict(_,_)
+dict_use.erl:73: The call dict:fetch('foo',[1 | 2 | 3,...]) does not have an opaque term of type dict:dict(_,_) as 2nd argument
dict_use.erl:76: The call dict:merge(Fun::any(),42,[1 | 2,...]) does not have opaque terms as 2nd and 3rd arguments
-dict_use.erl:79: The call dict:store(42,'elli',{'dict',0,16,16,8,80,48,{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}) does not have an opaque term of type dict() as 3rd argument
+dict_use.erl:79: The call dict:store(42,'elli',{'dict',0,16,16,8,80,48,{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}) does not have an opaque term of type dict:dict(_,_) as 3rd argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/ets b/lib/dialyzer/test/opaque_SUITE_data/results/ets
index 5498ba1538..e11c7a8352 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/ets
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/ets
@@ -1,3 +1,4 @@
-ets_use.erl:12: Guard test is_integer(T::atom() | tid()) breaks the opaqueness of its argument
-ets_use.erl:7: Guard test is_integer(T::tid()) breaks the opaqueness of its argument
+ets_use.erl:12: Guard test is_integer(T::atom() | ets:tid()) breaks the opaqueness of its argument
+ets_use.erl:20: The type test is_integer(atom() | ets:tid()) breaks the opaqueness of the term atom() | ets:tid()
+ets_use.erl:7: Guard test is_integer(T::ets:tid()) breaks the opaqueness of its argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/ewgi b/lib/dialyzer/test/opaque_SUITE_data/results/ewgi
index 3c8cfb59f8..209f27b2f2 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/ewgi
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/ewgi
@@ -1,4 +1,4 @@
-ewgi_api.erl:55: The call gb_trees:to_list({non_neg_integer(),'nil' | {_,_,_,_}}) does not have an opaque term of type gb_tree() as 1st argument
-ewgi_testapp.erl:35: The call ewgi_testapp:htmlise_data("request_data",{non_neg_integer(),'nil' | {_,_,_,_}}) will never return since it differs in the 2nd argument from the success typing arguments: ([95 | 97 | 100 | 101 | 104 | 112 | 113 | 114 | 115 | 116 | 117,...],[{_,_}])
-ewgi_testapp.erl:43: The call gb_trees:to_list(T::{non_neg_integer(),'nil' | {_,_,_,_}}) does not have an opaque term of type gb_tree() as 1st argument
+ewgi_api.erl:55: The call gb_trees:to_list({non_neg_integer(),'nil' | {_,_,_,_}}) does not have an opaque term of type gb_trees:tree(_,_) as 1st argument
+ewgi_testapp.erl:35: The call ewgi_testapp:htmlise_data("request_data",{non_neg_integer(),'nil' | {_,_,_,_}}) does not have a term of type [{_,_}] | gb_trees:tree(_,_) (with opaque subterms) as 2nd argument
+ewgi_testapp.erl:43: The call gb_trees:to_list(T::{non_neg_integer(),'nil' | {_,_,_,_}}) does not have an opaque term of type gb_trees:tree(_,_) as 1st argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/inf_loop1 b/lib/dialyzer/test/opaque_SUITE_data/results/inf_loop1
index eb8f304905..ac5ef14041 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/inf_loop1
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/inf_loop1
@@ -2,4 +2,4 @@
inf_loop1.erl:119: The pattern [{_, LNorms}] can never match the type []
inf_loop1.erl:121: The pattern [{LinksA, LNormA}, {LinksB, LNormB}] can never match the type []
inf_loop1.erl:129: The pattern [{_, Norm} | _] can never match the type []
-inf_loop1.erl:71: The call gb_trees:get(Edge::any(),Etab::array()) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+inf_loop1.erl:71: The call gb_trees:get(Edge::any(),Etab::array:array(_)) does not have an opaque term of type gb_trees:tree(_,_) as 2nd argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/inf_loop2 b/lib/dialyzer/test/opaque_SUITE_data/results/inf_loop2
new file mode 100644
index 0000000000..8cd2abe8cd
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/inf_loop2
@@ -0,0 +1,5 @@
+
+inf_loop2.erl:122: The pattern [{_, LNorms}] can never match the type []
+inf_loop2.erl:124: The pattern [{LinksA, LNormA}, {LinksB, LNormB}] can never match the type []
+inf_loop2.erl:132: The pattern [{_, Norm} | _] can never match the type []
+inf_loop2.erl:74: The call gb_trees:get(Edge::any(),Etab::array:array(_)) does not have an opaque term of type gb_trees:tree(_,_) as 2nd argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/int b/lib/dialyzer/test/opaque_SUITE_data/results/int
index 3ee4def34b..dc806fa12c 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/int
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/int
@@ -1,3 +1,3 @@
-int_adt.erl:28: Invalid type specification for function int_adt:add_f/2. The success typing is (number(),float()) -> number()
-int_adt.erl:32: Invalid type specification for function int_adt:div_f/2. The success typing is (number(),number()) -> float()
+int_adt.erl:28: Invalid type specification for function int_adt:add_f/2. The success typing is (number() | int_adt:int(),float()) -> number() | int_adt:int()
+int_adt.erl:32: Invalid type specification for function int_adt:div_f/2. The success typing is (number() | int_adt:int(),number() | int_adt:int()) -> float()
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/mixed_opaque b/lib/dialyzer/test/opaque_SUITE_data/results/mixed_opaque
index ab850b613e..0363be544d 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/mixed_opaque
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/mixed_opaque
@@ -1,2 +1,2 @@
-mixed_opaque_use.erl:31: The call mixed_opaque_rec_adt:get_a(Q::mixed_opaque_queue_adt:my_queue()) contains an opaque term as 1st argument when an opaque term of type mixed_opaque_rec_adt:rec() is expected
+mixed_opaque_use.erl:31: The call mixed_opaque_rec_adt:get_a(Q::mixed_opaque_queue_adt:my_queue()) does not have an opaque term of type mixed_opaque_rec_adt:rec() as 1st argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/modules b/lib/dialyzer/test/opaque_SUITE_data/results/modules
new file mode 100644
index 0000000000..f71334b9de
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/modules
@@ -0,0 +1,3 @@
+
+opaque_digraph.erl:353: Cons will produce an improper list since its 2nd argument is number()
+opaque_digraph.erl:365: Cons will produce an improper list since its 2nd argument is number()
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/my_queue b/lib/dialyzer/test/opaque_SUITE_data/results/my_queue
index 2860b91084..1f25a6f9c3 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/my_queue
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/my_queue
@@ -4,4 +4,4 @@ my_queue_use.erl:19: The call my_queue_adt:add(42,Q0::[]) does not have an opaqu
my_queue_use.erl:24: The attempt to match a term of type my_queue_adt:my_queue() against the pattern [42 | Q2] breaks the opaqueness of the term
my_queue_use.erl:30: Attempt to test for equality between a term of type [] and a term of opaque type my_queue_adt:my_queue()
my_queue_use.erl:34: Cons will produce an improper list since its 2nd argument is my_queue_adt:my_queue()
-my_queue_use.erl:34: The call my_queue_adt:dequeue(nonempty_improper_list(42,my_queue_adt:my_queue())) does not have an opaque term of type my_queue_adt:my_queue() as 1st argument
+my_queue_use.erl:34: The call my_queue_adt:dequeue(nonempty_maybe_improper_list(42,my_queue_adt:my_queue())) does not have an opaque term of type my_queue_adt:my_queue() as 1st argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/opaque b/lib/dialyzer/test/opaque_SUITE_data/results/opaque
index ca76f57b54..5747f9061f 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/opaque
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/opaque
@@ -1,2 +1,3 @@
+opaque_bug3.erl:19: The pattern 'a' can never match the type #c{}
opaque_bug4.erl:20: The attempt to match a term of type opaque_adt:abc() against the pattern 'a' breaks the opaqueness of the term
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/para b/lib/dialyzer/test/opaque_SUITE_data/results/para
new file mode 100644
index 0000000000..8fe67e39ad
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/para
@@ -0,0 +1,33 @@
+
+para1.erl:18: The test para1:t(atom()) =:= para1:t(integer()) can never evaluate to 'true'
+para1.erl:23: The test para1:t(atom()) =:= para1:t() can never evaluate to 'true'
+para1.erl:28: The test para1:t() =:= para1:t(integer()) can never evaluate to 'true'
+para1.erl:33: The test {3,2} =:= {'a','b'} can never evaluate to 'true'
+para1.erl:38: Attempt to test for equality between a term of type para1_adt:t(integer()) and a term of opaque type para1_adt:t(atom())
+para1.erl:43: Attempt to test for equality between a term of type para1_adt:t() and a term of opaque type para1_adt:t(atom())
+para1.erl:48: Attempt to test for equality between a term of type para1_adt:t(integer()) and a term of opaque type para1_adt:t()
+para1.erl:53: The test {3,2} =:= {'a','b'} can never evaluate to 'true'
+para2.erl:103: Attempt to test for equality between a term of type para2_adt:circ(integer(),integer()) and a term of opaque type para2_adt:circ(integer())
+para2.erl:117: Attempt to test for equality between a term of type para2_adt:un(atom(),integer()) and a term of opaque type para2_adt:un(integer(),atom())
+para2.erl:31: The test 'a' =:= 'b' can never evaluate to 'true'
+para2.erl:61: Attempt to test for equality between a term of type para2_adt:c2() and a term of opaque type para2_adt:c1()
+para2.erl:66: The test 'a' =:= 'b' can never evaluate to 'true'
+para2.erl:88: The test para2:circ(integer()) =:= para2:circ(integer(),integer()) can never evaluate to 'true'
+para3.erl:28: Invalid type specification for function para3:ot2/0. The success typing is () -> 'foo'
+para3.erl:36: The pattern {{{17}}} can never match the type {{{{{{_,_,_,_,_}}}}}}
+para3.erl:55: Invalid type specification for function para3:t2/0. The success typing is () -> 'foo'
+para3.erl:65: The attempt to match a term of type {{{{{para3_adt:ot1(_,_,_,_,_)}}}}} against the pattern {{{{{17}}}}} breaks the opaqueness of para3_adt:ot1(_,_,_,_,_)
+para3.erl:68: The pattern {{{{17}}}} can never match the type {{{{{para3_adt:ot1(_,_,_,_,_)}}}}}
+para3.erl:74: Invalid type specification for function para3:exp_adt/0. The success typing is () -> 3
+para4.erl:21: Invalid type specification for function para4:a/1. The success typing is (dict:dict(atom() | integer(),atom() | integer()) | para4:d_all()) -> [{atom() | integer(),atom() | integer()}]
+para4.erl:26: Invalid type specification for function para4:i/1. The success typing is (dict:dict(atom() | integer(),atom() | integer()) | para4:d_all()) -> [{atom() | integer(),atom() | integer()}]
+para4.erl:31: Invalid type specification for function para4:t/1. The success typing is (dict:dict(atom() | integer(),atom() | integer()) | para4:d_all()) -> [{atom() | integer(),atom() | integer()}]
+para4.erl:59: Attempt to test for equality between a term of type para4_adt:t(atom() | integer()) and a term of opaque type para4_adt:t(integer())
+para4.erl:64: Attempt to test for equality between a term of type para4_adt:t(atom() | integer()) and a term of opaque type para4_adt:t(atom())
+para4.erl:69: Attempt to test for equality between a term of type para4_adt:int(1 | 2 | 3 | 4) and a term of opaque type para4_adt:int(1 | 2)
+para4.erl:74: Attempt to test for equality between a term of type para4_adt:int(2 | 3 | 4) and a term of opaque type para4_adt:int(1 | 2)
+para4.erl:79: Attempt to test for equality between a term of type para4_adt:int(2 | 3 | 4) and a term of opaque type para4_adt:int(5 | 6 | 7)
+para4.erl:84: Attempt to test for equality between a term of type para4_adt:un(3 | 4) and a term of opaque type para4_adt:un(1 | 2)
+para4.erl:89: Attempt to test for equality between a term of type para4_adt:tup({_,_}) and a term of opaque type para4_adt:tup(tuple())
+para5.erl:13: Attempt to test for inequality between a term of type para5_adt:dd(atom()) and a term of opaque type para5_adt:d()
+para5.erl:8: The test para5_adt:d() =:= para5_adt:d() can never evaluate to 'true'
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/queue b/lib/dialyzer/test/opaque_SUITE_data/results/queue
index c3f04ea64d..5b3813c418 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/queue
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/queue
@@ -1,12 +1,11 @@
-queue_use.erl:18: The call queue:is_empty({[],[]}) does not have an opaque term of type queue() as 1st argument
-queue_use.erl:22: The call queue:in(42,Q0::{[],[]}) does not have an opaque term of type queue() as 2nd argument
-queue_use.erl:27: The attempt to match a term of type queue() against the pattern {"*", Q2} breaks the opaqueness of the term
-queue_use.erl:33: Attempt to test for equality between a term of type {[42,...],[]} and a term of opaque type queue()
-queue_use.erl:36: The attempt to match a term of type queue() against the pattern {F, _R} breaks the opaqueness of the term
-queue_use.erl:40: The call queue:out({[42,...],[]}) does not have an opaque term of type queue() as 1st argument
-queue_use.erl:48: The call queue_use:add_unique(42,#db{p::[],q::queue()}) contains an opaque term as 2nd argument when terms of different types are expected in these positions
-queue_use.erl:51: The call queue_use:is_in_queue(E::42,DB::#db{p::[],q::queue()}) contains an opaque term as 2nd argument when terms of different types are expected in these positions
-queue_use.erl:56: The attempt to match a term of type #db{p::[],q::queue()} against the pattern {'db', _, {L1, L2}} breaks the opaqueness of queue()
-queue_use.erl:62: The call queue_use:tuple_queue({42,'gazonk'}) does not have a term of type {_,queue()} (with opaque subterms) as 1st argument
-queue_use.erl:65: The call queue:in(F::42,Q::'gazonk') does not have an opaque term of type queue() as 2nd argument
+queue_use.erl:18: The call queue:is_empty({[],[]}) does not have an opaque term of type queue:queue(_) as 1st argument
+queue_use.erl:22: The call queue:in(42,Q0::{[],[]}) does not have an opaque term of type queue:queue(_) as 2nd argument
+queue_use.erl:27: The attempt to match a term of type queue:queue(_) against the pattern {"*", Q2} breaks the opaqueness of the term
+queue_use.erl:33: Attempt to test for equality between a term of type {[42,...],[]} and a term of opaque type queue:queue(_)
+queue_use.erl:36: The attempt to match a term of type queue:queue(_) against the pattern {F, _R} breaks the opaqueness of the term
+queue_use.erl:40: The call queue:out({[42,...],[]}) does not have an opaque term of type queue:queue(_) as 1st argument
+queue_use.erl:51: The call queue_use:is_in_queue(E::42,DB::#db{p::[],q::queue:queue(_)}) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+queue_use.erl:56: The attempt to match a term of type #db{p::[],q::queue:queue(_)} against the pattern {'db', _, {L1, L2}} breaks the opaqueness of queue:queue(_)
+queue_use.erl:62: The call queue_use:tuple_queue({42,'gazonk'}) does not have a term of type {_,queue:queue(_)} (with opaque subterms) as 1st argument
+queue_use.erl:65: The call queue:in(F::42,Q::'gazonk') does not have an opaque term of type queue:queue(_) as 2nd argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/simple b/lib/dialyzer/test/opaque_SUITE_data/results/simple
new file mode 100644
index 0000000000..391c37664e
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/simple
@@ -0,0 +1,92 @@
+
+exact_api.erl:17: The call exact_api:set_type(A::#digraph{vtab::'notable',etab::'notable',ntab::'notable',cyclic::'true'}) does not have an opaque term of type digraph:graph() as 1st argument
+exact_api.erl:23: The call digraph:delete(G::#digraph{vtab::'notable',etab::'notable',ntab::'notable',cyclic::'true'}) does not have an opaque term of type digraph:graph() as 1st argument
+exact_api.erl:55: The attempt to match a term of type exact_adt:exact_adt() against the pattern {'exact_adt'} breaks the opaqueness of the term
+exact_api.erl:59: The call exact_adt:exact_adt_set_type2(A::#exact_adt{}) does not have an opaque term of type exact_adt:exact_adt() as 1st argument
+is_rec.erl:10: The call erlang:is_record(simple1_adt:d1(),'r',2) contains an opaque term as 1st argument when terms of different types are expected in these positions
+is_rec.erl:15: The call erlang:is_record(A::simple1_adt:d1(),'r',I::1 | 2 | 3) contains an opaque term as 1st argument when terms of different types are expected in these positions
+is_rec.erl:19: Guard test is_record(A::simple1_adt:d1(),'r',2) breaks the opaqueness of its argument
+is_rec.erl:23: Guard test is_record({simple1_adt:d1(),1},'r',2) breaks the opaqueness of its argument
+is_rec.erl:41: The call erlang:is_record(A::simple1_adt:d1(),R::'a') contains an opaque term as 1st argument when terms of different types are expected in these positions
+is_rec.erl:45: The call erlang:is_record(A::simple1_adt:d1(),A::simple1_adt:d1(),1) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+is_rec.erl:49: The call erlang:is_record(A::simple1_adt:d1(),any(),1) contains an opaque term as 1st argument when terms of different types are expected in these positions
+is_rec.erl:53: The call erlang:is_record(A::simple1_adt:d1(),A::simple1_adt:d1(),any()) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+is_rec.erl:57: Guard test is_record(A::simple1_adt:d1(),'r',2) breaks the opaqueness of its argument
+is_rec.erl:61: The record #r{f1::simple1_adt:d1()} violates the declared type for #r{}
+is_rec.erl:65: The call erlang:is_record({simple1_adt:d1(),1},'r',2) contains an opaque term as 1st argument when terms of different types are expected in these positions
+rec_api.erl:104: Matching of pattern {'r2', 10} tagged with a record name violates the declared type of #r2{f1::10}
+rec_api.erl:113: The attempt to match a term of type #r3{f1::queue:queue(_)} against the pattern {'r3', 'a'} breaks the opaqueness of queue:queue(_)
+rec_api.erl:118: Record construction #r3{f1::10} violates the declared type of field f1::queue:queue(_)
+rec_api.erl:123: The attempt to match a term of type #r3{f1::10} against the pattern {'r3', 10} breaks the opaqueness of queue:queue(_)
+rec_api.erl:24: Record construction #r1{f1::10} violates the declared type of field f1::rec_api:a()
+rec_api.erl:29: Matching of pattern {'r1', 10} tagged with a record name violates the declared type of #r1{f1::10}
+rec_api.erl:33: The attempt to match a term of type rec_adt:r1() against the pattern {'r1', 'a'} breaks the opaqueness of the term
+rec_api.erl:35: Invalid type specification for function rec_api:adt_t1/1. The success typing is (#r1{f1::'a'}) -> #r1{f1::'a'}
+rec_api.erl:40: Invalid type specification for function rec_api:adt_r1/0. The success typing is () -> #r1{f1::'a'}
+rec_api.erl:85: The attempt to match a term of type rec_api:f() against the variable _ breaks the opaqueness of rec_adt:f()
+rec_api.erl:99: Record construction #r2{f1::10} violates the declared type of field f1::rec_api:a()
+simple1_api.erl:113: The test simple1_api:d1() =:= simple1_api:d2() can never evaluate to 'true'
+simple1_api.erl:118: Guard test simple1_api:d2() =:= A::simple1_api:d1() can never succeed
+simple1_api.erl:142: Attempt to test for equality between a term of type simple1_adt:o2() and a term of opaque type simple1_adt:o1()
+simple1_api.erl:148: Guard test simple1_adt:o2() =:= A::simple1_adt:o1() contains opaque terms as 1st and 2nd arguments
+simple1_api.erl:154: Attempt to test for inequality between a term of type simple1_adt:o2() and a term of opaque type simple1_adt:o1()
+simple1_api.erl:160: Attempt to test for inequality between a term of type simple1_adt:o2() and a term of opaque type simple1_adt:o1()
+simple1_api.erl:165: Attempt to test for equality between a term of type simple1_adt:c2() and a term of opaque type simple1_adt:c1()
+simple1_api.erl:181: Guard test A::simple1_adt:d1() =< B::simple1_adt:d2() contains opaque terms as 1st and 2nd arguments
+simple1_api.erl:185: Guard test 'a' =< B::simple1_adt:d2() contains an opaque term as 2nd argument
+simple1_api.erl:189: Guard test A::simple1_adt:d1() =< 'd' contains an opaque term as 1st argument
+simple1_api.erl:197: The type test is_integer(A::simple1_adt:d1()) breaks the opaqueness of the term A::simple1_adt:d1()
+simple1_api.erl:221: Guard test A::simple1_api:i1() > 3 can never succeed
+simple1_api.erl:225: Guard test A::simple1_adt:i1() > 3 contains an opaque term as 1st argument
+simple1_api.erl:233: Guard test A::simple1_adt:i1() < 3 contains an opaque term as 1st argument
+simple1_api.erl:239: Guard test A::1 > 3 can never succeed
+simple1_api.erl:243: Guard test A::1 > 3 can never succeed
+simple1_api.erl:257: Guard test is_function(T::simple1_api:o1()) can never succeed
+simple1_api.erl:265: Guard test is_function(T::simple1_adt:o1()) breaks the opaqueness of its argument
+simple1_api.erl:269: The type test is_function(T::simple1_adt:o1()) breaks the opaqueness of the term T::simple1_adt:o1()
+simple1_api.erl:274: Guard test is_function(T::simple1_api:o1(),A::simple1_api:i1()) can never succeed
+simple1_api.erl:284: Guard test is_function(T::simple1_adt:o1(),A::simple1_adt:i1()) breaks the opaqueness of its argument
+simple1_api.erl:289: The type test is_function(T::simple1_adt:o1(),A::simple1_adt:i1()) breaks the opaqueness of the term T::simple1_adt:o1()
+simple1_api.erl:294: The call erlang:is_function(T::simple1_api:o1(),A::simple1_adt:i1()) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+simple1_api.erl:300: The type test is_function(T::simple1_adt:o1(),A::simple1_api:i1()) breaks the opaqueness of the term T::simple1_adt:o1()
+simple1_api.erl:306: Guard test B::simple1_api:b2() =:= 'true' can never succeed
+simple1_api.erl:315: Guard test A::simple1_api:b1() =:= 'false' can never succeed
+simple1_api.erl:319: Guard test not('and'('true','true')) can never succeed
+simple1_api.erl:337: Clause guard cannot succeed.
+simple1_api.erl:342: Guard test B::simple1_adt:b2() =:= 'true' contains an opaque term as 1st argument
+simple1_api.erl:347: Guard test A::simple1_adt:b1() =:= 'true' contains an opaque term as 1st argument
+simple1_api.erl:355: Invalid type specification for function simple1_api:bool_adt_t6/1. The success typing is ('true') -> 1
+simple1_api.erl:365: Clause guard cannot succeed.
+simple1_api.erl:368: Invalid type specification for function simple1_api:bool_adt_t8/2. The success typing is (boolean(),boolean()) -> 1
+simple1_api.erl:378: Clause guard cannot succeed.
+simple1_api.erl:381: Invalid type specification for function simple1_api:bool_adt_t9/2. The success typing is ('false','false') -> 1
+simple1_api.erl:407: The size simple1_adt:i1() breaks the opaqueness of A
+simple1_api.erl:418: The attempt to match a term of type non_neg_integer() against the variable A breaks the opaqueness of simple1_adt:i1()
+simple1_api.erl:425: The attempt to match a term of type non_neg_integer() against the variable B breaks the opaqueness of simple1_adt:i1()
+simple1_api.erl:432: The pattern <<_:B/integer-unit:1>> can never match the type any()
+simple1_api.erl:448: The attempt to match a term of type non_neg_integer() against the variable Sz breaks the opaqueness of simple1_adt:i1()
+simple1_api.erl:460: The attempt to match a term of type simple1_adt:bit1() against the pattern <<_/binary-unit:8>> breaks the opaqueness of the term
+simple1_api.erl:478: The call 'foo':A(A::simple1_adt:a()) breaks the opaqueness of the term A :: simple1_adt:a()
+simple1_api.erl:486: The call A:'foo'(A::simple1_adt:a()) breaks the opaqueness of the term A :: simple1_adt:a()
+simple1_api.erl:499: The call 'foo':A(A::simple1_api:i()) requires that A is of type atom() not simple1_api:i()
+simple1_api.erl:503: The call 'foo':A(A::simple1_adt:i()) requires that A is of type atom() not simple1_adt:i()
+simple1_api.erl:507: The call A:'foo'(A::simple1_api:i()) requires that A is of type atom() not simple1_api:i()
+simple1_api.erl:511: The call A:'foo'(A::simple1_adt:i()) requires that A is of type atom() not simple1_adt:i()
+simple1_api.erl:519: Guard test A::simple1_adt:d2() == B::simple1_adt:d1() contains opaque terms as 1st and 2nd arguments
+simple1_api.erl:534: Guard test A::simple1_adt:d1() >= 3 contains an opaque term as 1st argument
+simple1_api.erl:536: Guard test A::simple1_adt:d1() == 3 contains an opaque term as 1st argument
+simple1_api.erl:538: Guard test A::simple1_adt:d1() =:= 3 contains an opaque term as 1st argument
+simple1_api.erl:548: The call erlang:'<'(A::simple1_adt:d1(),3) contains an opaque term as 1st argument when terms of different types are expected in these positions
+simple1_api.erl:558: The call erlang:'=<'(A::simple1_adt:d1(),B::simple1_adt:d2()) contains opaque terms as 1st and 2nd arguments when terms of different types are expected in these positions
+simple1_api.erl:565: Guard test {digraph:graph(),3} > {digraph:graph(),atom() | ets:tid()} contains an opaque term as 2nd argument
+simple1_api.erl:91: Invalid type specification for function simple1_api:tup/0. The success typing is () -> {'a','b'}
+simple2_api.erl:100: The call lists:flatten(A::simple1_adt:tuple1()) contains an opaque term as 1st argument when a structured term of type [any()] is expected
+simple2_api.erl:116: The call lists:flatten({simple1_adt:tuple1()}) will never return since it differs in the 1st argument from the success typing arguments: ([any()])
+simple2_api.erl:121: Guard test {simple1_adt:d1(),3} > {simple1_adt:d1(),simple1_adt:tuple1()} contains an opaque term as 2nd argument
+simple2_api.erl:125: The call erlang:tuple_to_list(B::simple1_adt:tuple1()) contains an opaque term as 1st argument when a structured term of type tuple() is expected
+simple2_api.erl:31: The call erlang:'!'(A::simple1_adt:d1(),'foo') contains an opaque term as 1st argument when terms of different types are expected in these positions
+simple2_api.erl:35: The call erlang:send(A::simple1_adt:d1(),'foo') contains an opaque term as 1st argument when terms of different types are expected in these positions
+simple2_api.erl:51: The call erlang:'<'(A::simple1_adt:d1(),3) contains an opaque term as 1st argument when terms of different types are expected in these positions
+simple2_api.erl:59: The call lists:keysearch(1,A::simple1_adt:d1(),[]) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+simple2_api.erl:67: The call lists:keysearch('key',1,A::simple1_adt:tuple1()) contains an opaque term as 3rd argument when terms of different types are expected in these positions
+simple2_api.erl:96: The call lists:keyreplace('a',1,[{1, 2}],A::simple1_adt:tuple1()) contains an opaque term as 4th argument when terms of different types are expected in these positions
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/timer b/lib/dialyzer/test/opaque_SUITE_data/results/timer
index e917b76b08..b1cfcd4e9f 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/timer
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/timer
@@ -1,4 +1,4 @@
timer_use.erl:16: The pattern 'gazonk' can never match the type {'error',_} | {'ok',timer:tref()}
-timer_use.erl:17: The attempt to match a term of type {'ok',timer:tref()} against the pattern {'ok', 42} breaks the opaqueness of timer:tref()
+timer_use.erl:17: The attempt to match a term of type {'error',_} | {'ok',timer:tref()} against the pattern {'ok', 42} breaks the opaqueness of timer:tref()
timer_use.erl:18: The attempt to match a term of type {'error',_} | {'ok',timer:tref()} against the pattern {Tag, 'gazonk'} breaks the opaqueness of timer:tref()
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/wings b/lib/dialyzer/test/opaque_SUITE_data/results/wings
index a9571441f8..511263b70a 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/wings
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/wings
@@ -1,11 +1,11 @@
-wings_dissolve.erl:103: Guard test is_list(List::gb_set()) breaks the opaqueness of its argument
-wings_dissolve.erl:19: Guard test is_list(Faces::gb_set()) breaks the opaqueness of its argument
-wings_dissolve.erl:272: Guard test is_list(Faces::gb_set()) breaks the opaqueness of its argument
-wings_dissolve.erl:31: The call gb_sets:is_empty(Faces::[any(),...]) does not have an opaque term of type gb_set() as 1st argument
+wings_dissolve.erl:103: Guard test is_list(List::gb_sets:set(_)) breaks the opaqueness of its argument
+wings_dissolve.erl:19: Guard test is_list(Faces::gb_sets:set(_)) breaks the opaqueness of its argument
+wings_dissolve.erl:272: Guard test is_list(Faces::gb_sets:set(_)) breaks the opaqueness of its argument
+wings_dissolve.erl:31: The call gb_sets:is_empty(Faces::[any(),...]) does not have an opaque term of type gb_sets:set(_) as 1st argument
wings_edge.erl:205: The pattern <Edge, 'hard', Htab> can never match the type <_,'soft',_>
-wings_edge_cmd.erl:30: The call gb_trees:size(P::gb_set()) contains an opaque term as 1st argument when an opaque term of type gb_tree() is expected
+wings_edge_cmd.erl:30: The call gb_trees:size(P::gb_sets:set(_)) does not have an opaque term of type gb_trees:tree(_,_) as 1st argument
wings_edge_cmd.erl:32: The pattern [_ | Parts] can never match the type []
wings_edge_cmd.erl:32: The pattern [{_, P} | _] can never match the type []
-wings_io.erl:30: The attempt to match a term of type {'empty',queue()} against the pattern {'empty', {In, Out}} breaks the opaqueness of queue()
-wings_we.erl:155: The call wings_util:gb_trees_largest_key(Etab::gb_tree()) contains an opaque term as 1st argument when a structured term of type {_,{_,_,_,'nil' | {_,_,_,'nil' | {_,_,_,_}}}} is expected
+wings_io.erl:30: The attempt to match a term of type {'empty',queue:queue(_)} against the pattern {'empty', {In, Out}} breaks the opaqueness of queue:queue(_)
+wings_we.erl:155: The call wings_util:gb_trees_largest_key(Etab::gb_trees:tree(_,_)) contains an opaque term as 1st argument when a structured term of type {_,{_,_,_,'nil' | {_,_,_,'nil' | {_,_,_,_}}}} is expected
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/big_external_type.erl b/lib/dialyzer/test/opaque_SUITE_data/src/big_external_type.erl
new file mode 100644
index 0000000000..d286a378ed
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/big_external_type.erl
@@ -0,0 +1,526 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%% A copy of small_SUITE_data/src/big_external_type.erl, where
+%%% abstract_expr() is opaque. The transformation of forms to types is
+%%% now much faster than it used to be, for this module.
+
+-module(big_external_type).
+
+-export([parse_form/1,parse_exprs/1,parse_term/1]).
+-export([normalise/1,tokens/1,tokens/2]).
+-export([inop_prec/1,preop_prec/1,func_prec/0,max_prec/0]).
+
+-export_type([abstract_clause/0, abstract_expr/0, abstract_form/0,
+ error_info/0]).
+
+%% Start of Abstract Format
+
+-type line() :: erl_anno:line().
+
+-export_type([af_record_index/0, af_record_field/1, af_record_name/0,
+ af_field_name/0, af_function_decl/0]).
+
+-export_type([af_module/0, af_export/0, af_import/0, af_fa_list/0,
+ af_compile/0, af_file/0, af_record_decl/0,
+ af_field_decl/0, af_wild_attribute/0,
+ af_record_update/1, af_catch/0, af_local_call/0,
+ af_remote_call/0, af_args/0, af_local_function/0,
+ af_remote_function/0, af_list_comprehension/0,
+ af_binary_comprehension/0, af_template/0,
+ af_qualifier_seq/0, af_qualifier/0, af_generator/0,
+ af_filter/0, af_block/0, af_if/0, af_case/0, af_try/0,
+ af_clause_seq/0, af_catch_clause_seq/0, af_receive/0,
+ af_local_fun/0, af_remote_fun/0, af_fun/0, af_query/0,
+ af_query_access/0, af_clause/0,
+ af_catch_clause/0, af_catch_pattern/0, af_catch_class/0,
+ af_body/0, af_guard_seq/0, af_guard/0, af_guard_test/0,
+ af_record_access/1, af_guard_call/0,
+ af_remote_guard_call/0, af_pattern/0, af_literal/0,
+ af_atom/0, af_lit_atom/1, af_integer/0, af_float/0,
+ af_string/0, af_match/1, af_variable/0,
+ af_anon_variable/0, af_tuple/1, af_nil/0, af_cons/1,
+ af_bin/1, af_binelement/1, af_binelement_size/0,
+ af_binary_op/1, af_binop/0, af_unary_op/1, af_unop/0]).
+
+-type abstract_form() :: ?MODULE:af_module()
+ | ?MODULE:af_export()
+ | ?MODULE:af_import()
+ | ?MODULE:af_compile()
+ | ?MODULE:af_file()
+ | ?MODULE:af_record_decl()
+ | ?MODULE:af_wild_attribute()
+ | ?MODULE:af_function_decl().
+
+-type af_module() :: {attribute, line(), module, module()}.
+
+-type af_export() :: {attribute, line(), export, ?MODULE:af_fa_list()}.
+
+-type af_import() :: {attribute, line(), import, ?MODULE:af_fa_list()}.
+
+-type af_fa_list() :: [{function(), arity()}].
+
+-type af_compile() :: {attribute, line(), compile, any()}.
+
+-type af_file() :: {attribute, line(), file, {string(), line()}}.
+
+-type af_record_decl() ::
+ {attribute, line(), record, ?MODULE:af_record_name(), [?MODULE:af_field_decl()]}.
+
+-type af_field_decl() :: {record_field, line(), ?MODULE:af_atom()}
+ | {record_field, line(), ?MODULE:af_atom(), ?MODULE:abstract_expr()}.
+
+%% Types and specs, among other things...
+-type af_wild_attribute() :: {attribute, line(), ?MODULE:af_atom(), any()}.
+
+-type af_function_decl() ::
+ {function, line(), function(), arity(), ?MODULE:af_clause_seq()}.
+
+-opaque abstract_expr() :: ?MODULE:af_literal()
+ | ?MODULE:af_match(?MODULE:abstract_expr())
+ | ?MODULE:af_variable()
+ | ?MODULE:af_tuple(?MODULE:abstract_expr())
+ | ?MODULE:af_nil()
+ | ?MODULE:af_cons(?MODULE:abstract_expr())
+ | ?MODULE:af_bin(?MODULE:abstract_expr())
+ | ?MODULE:af_binary_op(?MODULE:abstract_expr())
+ | ?MODULE:af_unary_op(?MODULE:abstract_expr())
+ | ?MODULE:af_record_access(?MODULE:abstract_expr())
+ | ?MODULE:af_record_update(?MODULE:abstract_expr())
+ | ?MODULE:af_record_index()
+ | ?MODULE:af_record_field(?MODULE:abstract_expr())
+ | ?MODULE:af_catch()
+ | ?MODULE:af_local_call()
+ | ?MODULE:af_remote_call()
+ | ?MODULE:af_list_comprehension()
+ | ?MODULE:af_binary_comprehension()
+ | ?MODULE:af_block()
+ | ?MODULE:af_if()
+ | ?MODULE:af_case()
+ | ?MODULE:af_try()
+ | ?MODULE:af_receive()
+ | ?MODULE:af_local_fun()
+ | ?MODULE:af_remote_fun()
+ | ?MODULE:af_fun()
+ | ?MODULE:af_query()
+ | ?MODULE:af_query_access().
+
+-type af_record_update(T) :: {record,
+ line(),
+ ?MODULE:abstract_expr(),
+ ?MODULE:af_record_name(),
+ [?MODULE:af_record_field(T)]}.
+
+-type af_catch() :: {'catch', line(), ?MODULE:abstract_expr()}.
+
+-type af_local_call() :: {call, line(), ?MODULE:af_local_function(), ?MODULE:af_args()}.
+
+-type af_remote_call() :: {call, line(), ?MODULE:af_remote_function(), ?MODULE:af_args()}.
+
+-type af_args() :: [?MODULE:abstract_expr()].
+
+-type af_local_function() :: ?MODULE:abstract_expr().
+
+-type af_remote_function() ::
+ {remote, line(), ?MODULE:abstract_expr(), ?MODULE:abstract_expr()}.
+
+-type af_list_comprehension() ::
+ {lc, line(), ?MODULE:af_template(), ?MODULE:af_qualifier_seq()}.
+
+-type af_binary_comprehension() ::
+ {bc, line(), ?MODULE:af_template(), ?MODULE:af_qualifier_seq()}.
+
+-type af_template() :: ?MODULE:abstract_expr().
+
+-type af_qualifier_seq() :: [?MODULE:af_qualifier()].
+
+-type af_qualifier() :: ?MODULE:af_generator() | ?MODULE:af_filter().
+
+-type af_generator() :: {generate, line(), ?MODULE:af_pattern(), ?MODULE:abstract_expr()}
+ | {b_generate, line(), ?MODULE:af_pattern(), ?MODULE:abstract_expr()}.
+
+-type af_filter() :: ?MODULE:abstract_expr().
+
+-type af_block() :: {block, line(), ?MODULE:af_body()}.
+
+-type af_if() :: {'if', line(), ?MODULE:af_clause_seq()}.
+
+-type af_case() :: {'case', line(), ?MODULE:abstract_expr(), ?MODULE:af_clause_seq()}.
+
+-type af_try() :: {'try',
+ line(),
+ ?MODULE:af_body(),
+ ?MODULE:af_clause_seq(),
+ ?MODULE:af_catch_clause_seq(),
+ ?MODULE:af_body()}.
+
+-type af_clause_seq() :: [?MODULE:af_clause(), ...].
+
+-type af_catch_clause_seq() :: [?MODULE:af_clause(), ...].
+
+-type af_receive() ::
+ {'receive', line(), ?MODULE:af_clause_seq()}
+ | {'receive', line(), ?MODULE:af_clause_seq(), ?MODULE:abstract_expr(), ?MODULE:af_body()}.
+
+-type af_local_fun() :: {'fun', line(), {function, function(), arity()}}.
+
+-type af_remote_fun() ::
+ {'fun', line(), {function, module(), function(), arity()}}
+ | {'fun', line(), {function, ?MODULE:af_atom(), ?MODULE:af_atom(), ?MODULE:af_integer()}}.
+
+-type af_fun() :: {'fun', line(), {clauses, ?MODULE:af_clause_seq()}}.
+
+-type af_query() :: {'query', line(), ?MODULE:af_list_comprehension()}.
+
+-type af_query_access() ::
+ {record_field, line(), ?MODULE:abstract_expr(), ?MODULE:af_field_name()}.
+
+-type abstract_clause() :: ?MODULE:af_clause() | ?MODULE:af_catch_clause().
+
+-type af_clause() ::
+ {clause, line(), [?MODULE:af_pattern()], ?MODULE:af_guard_seq(), ?MODULE:af_body()}.
+
+-type af_catch_clause() ::
+ {clause, line(), [?MODULE:af_catch_pattern()], ?MODULE:af_guard_seq(), ?MODULE:af_body()}.
+
+-type af_catch_pattern() ::
+ {?MODULE:af_catch_class(), ?MODULE:af_pattern(), ?MODULE:af_anon_variable()}.
+
+-type af_catch_class() ::
+ ?MODULE:af_variable()
+ | ?MODULE:af_lit_atom(throw) | ?MODULE:af_lit_atom(error) | ?MODULE:af_lit_atom(exit).
+
+-type af_body() :: [?MODULE:abstract_expr(), ...].
+
+-type af_guard_seq() :: [?MODULE:af_guard()].
+
+-type af_guard() :: [?MODULE:af_guard_test(), ...].
+
+-type af_guard_test() :: ?MODULE:af_literal()
+ | ?MODULE:af_variable()
+ | ?MODULE:af_tuple(?MODULE:af_guard_test())
+ | ?MODULE:af_nil()
+ | ?MODULE:af_cons(?MODULE:af_guard_test())
+ | ?MODULE:af_bin(?MODULE:af_guard_test())
+ | ?MODULE:af_binary_op(?MODULE:af_guard_test())
+ | ?MODULE:af_unary_op(?MODULE:af_guard_test())
+ | ?MODULE:af_record_access(?MODULE:af_guard_test())
+ | ?MODULE:af_record_index()
+ | ?MODULE:af_record_field(?MODULE:af_guard_test())
+ | ?MODULE:af_guard_call()
+ | ?MODULE:af_remote_guard_call().
+
+-type af_record_access(T) ::
+ {record, line(), ?MODULE:af_record_name(), [?MODULE:af_record_field(T)]}.
+
+-type af_guard_call() :: {call, line(), function(), [?MODULE:af_guard_test()]}.
+
+-type af_remote_guard_call() ::
+ {call, line(), atom(), ?MODULE:af_lit_atom(erlang), [?MODULE:af_guard_test()]}.
+
+-type af_pattern() :: ?MODULE:af_literal()
+ | ?MODULE:af_match(?MODULE:af_pattern())
+ | ?MODULE:af_variable()
+ | ?MODULE:af_anon_variable()
+ | ?MODULE:af_tuple(?MODULE:af_pattern())
+ | ?MODULE:af_nil()
+ | ?MODULE:af_cons(?MODULE:af_pattern())
+ | ?MODULE:af_bin(?MODULE:af_pattern())
+ | ?MODULE:af_binary_op(?MODULE:af_pattern())
+ | ?MODULE:af_unary_op(?MODULE:af_pattern())
+ | ?MODULE:af_record_index()
+ | ?MODULE:af_record_field(?MODULE:af_pattern()).
+
+-type af_literal() :: ?MODULE:af_atom() | ?MODULE:af_integer() | ?MODULE:af_float() | ?MODULE:af_string().
+
+-type af_atom() :: ?MODULE:af_lit_atom(atom()).
+
+-type af_lit_atom(A) :: {atom, line(), A}.
+
+-type af_integer() :: {integer, line(), non_neg_integer()}.
+
+-type af_float() :: {float, line(), float()}.
+
+-type af_string() :: {string, line(), [byte()]}.
+
+-type af_match(T) :: {match, line(), T, T}.
+
+-type af_variable() :: {var, line(), atom()}.
+
+-type af_anon_variable() :: {var, line(), '_'}.
+
+-type af_tuple(T) :: {tuple, line(), [T]}.
+
+-type af_nil() :: {nil, line()}.
+
+-type af_cons(T) :: {cons, line, T, T}.
+
+-type af_bin(T) :: {bin, line(), [?MODULE:af_binelement(T)]}.
+
+-type af_binelement(T) :: {bin_element,
+ line(),
+ T,
+ ?MODULE:af_binelement_size(),
+ type_specifier_list()}.
+
+-type af_binelement_size() :: default | ?MODULE:abstract_expr().
+
+-type af_binary_op(T) :: {op, line(), T, ?MODULE:af_binop(), T}.
+
+-type af_binop() :: '/' | '*' | 'div' | 'rem' | 'band' | 'and' | '+' | '-'
+ | 'bor' | 'bxor' | 'bsl' | 'bsr' | 'or' | 'xor' | '++'
+ | '--' | '==' | '/=' | '=<' | '<' | '>=' | '>' | '=:='
+ | '=/='.
+
+-type af_unary_op(T) :: {op, line(), ?MODULE:af_unop(), T}.
+
+-type af_unop() :: '+' | '*' | 'bnot' | 'not'.
+
+%% See also lib/stdlib/{src/erl_bits.erl,include/erl_bits.hrl}.
+-type type_specifier_list() :: default | [type_specifier(), ...].
+
+-type type_specifier() :: af_type()
+ | af_signedness()
+ | af_endianness()
+ | af_unit().
+
+-type af_type() :: integer
+ | float
+ | binary
+ | bytes
+ | bitstring
+ | bits
+ | utf8
+ | utf16
+ | utf32.
+
+-type af_signedness() :: signed | unsigned.
+
+-type af_endianness() :: big | little | native.
+
+-type af_unit() :: {unit, 1..256}.
+
+-type af_record_index() ::
+ {record_index, line(), af_record_name(), af_field_name()}.
+
+-type af_record_field(T) :: {record_field, line(), af_field_name(), T}.
+
+-type af_record_name() :: atom().
+
+-type af_field_name() :: atom().
+
+%% End of Abstract Format
+
+-type error_description() :: term().
+-type error_info() :: {erl_anno:line(), module(), error_description()}.
+-type token() :: {Tag :: atom(), Line :: erl_anno:line()}.
+
+%% mkop(Op, Arg) -> {op,Line,Op,Arg}.
+%% mkop(Left, Op, Right) -> {op,Line,Op,Left,Right}.
+
+-define(mkop2(L, OpPos, R),
+ begin
+ {Op,Pos} = OpPos,
+ {op,Pos,Op,L,R}
+ end).
+
+-define(mkop1(OpPos, A),
+ begin
+ {Op,Pos} = OpPos,
+ {op,Pos,Op,A}
+ end).
+
+%% keep track of line info in tokens
+-define(line(Tup), element(2, Tup)).
+
+%% Entry points compatible to old erl_parse.
+%% These really suck and are only here until Calle gets multiple
+%% entry points working.
+
+-spec parse_form(Tokens) -> {ok, AbsForm} | {error, ErrorInfo} when
+ Tokens :: [token()],
+ AbsForm :: abstract_form(),
+ ErrorInfo :: error_info().
+parse_form([{'-',L1},{atom,L2,spec}|Tokens]) ->
+ parse([{'-',L1},{'spec',L2}|Tokens]);
+parse_form([{'-',L1},{atom,L2,callback}|Tokens]) ->
+ parse([{'-',L1},{'callback',L2}|Tokens]);
+parse_form(Tokens) ->
+ parse(Tokens).
+
+-spec parse_exprs(Tokens) -> {ok, ExprList} | {error, ErrorInfo} when
+ Tokens :: [token()],
+ ExprList :: [abstract_expr()],
+ ErrorInfo :: error_info().
+parse_exprs(Tokens) ->
+ case parse([{atom,0,f},{'(',0},{')',0},{'->',0}|Tokens]) of
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],Exprs}]}} ->
+ {ok,Exprs};
+ {error,_} = Err -> Err
+ end.
+
+-spec parse_term(Tokens) -> {ok, Term} | {error, ErrorInfo} when
+ Tokens :: [token()],
+ Term :: term(),
+ ErrorInfo :: error_info().
+parse_term(Tokens) ->
+ case parse([{atom,0,f},{'(',0},{')',0},{'->',0}|Tokens]) of
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],[Expr]}]}} ->
+ try normalise(Expr) of
+ Term -> {ok,Term}
+ catch
+ _:_R -> {error,{?line(Expr),?MODULE,"bad term"}}
+ end;
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],[_E1,E2|_Es]}]}} ->
+ {error,{?line(E2),?MODULE,"bad term"}};
+ {error,_} = Err -> Err
+ end.
+
+%% Convert between the abstract form of a term and a term.
+
+-spec normalise(AbsTerm) -> Data when
+ AbsTerm :: abstract_expr(),
+ Data :: term().
+normalise({char,_,C}) -> C;
+normalise({integer,_,I}) -> I;
+normalise({float,_,F}) -> F;
+normalise({atom,_,A}) -> A;
+normalise({string,_,S}) -> S;
+normalise({nil,_}) -> [];
+normalise({bin,_,Fs}) ->
+ {value, B, _} =
+ eval_bits:expr_grp(Fs, [],
+ fun(E, _) ->
+ {value, normalise(E), []}
+ end, [], true),
+ B;
+normalise({cons,_,Head,Tail}) ->
+ [normalise(Head)|normalise(Tail)];
+normalise({tuple,_,Args}) ->
+ list_to_tuple(normalise_list(Args));
+%% Atom dot-notation, as in 'foo.bar.baz'
+%% Special case for unary +/-.
+normalise({op,_,'+',{char,_,I}}) -> I;
+normalise({op,_,'+',{integer,_,I}}) -> I;
+normalise({op,_,'+',{float,_,F}}) -> F;
+normalise({op,_,'-',{char,_,I}}) -> -I; %Weird, but compatible!
+normalise({op,_,'-',{integer,_,I}}) -> -I;
+normalise({op,_,'-',{float,_,F}}) -> -F;
+normalise(X) -> erlang:error({badarg, X}).
+
+normalise_list([H|T]) ->
+ [normalise(H)|normalise_list(T)];
+normalise_list([]) ->
+ [].
+
+%% Generate a list of tokens representing the abstract term.
+
+-spec tokens(AbsTerm) -> Tokens when
+ AbsTerm :: abstract_expr(),
+ Tokens :: [token()].
+tokens(Abs) ->
+ tokens(Abs, []).
+
+-spec tokens(AbsTerm, MoreTokens) -> Tokens when
+ AbsTerm :: abstract_expr(),
+ MoreTokens :: [token()],
+ Tokens :: [token()].
+tokens({char,L,C}, More) -> [{char,L,C}|More];
+tokens({integer,L,N}, More) -> [{integer,L,N}|More];
+tokens({float,L,F}, More) -> [{float,L,F}|More];
+tokens({atom,L,A}, More) -> [{atom,L,A}|More];
+tokens({var,L,V}, More) -> [{var,L,V}|More];
+tokens({string,L,S}, More) -> [{string,L,S}|More];
+tokens({nil,L}, More) -> [{'[',L},{']',L}|More];
+tokens({cons,L,Head,Tail}, More) ->
+ [{'[',L}|tokens(Head, tokens_tail(Tail, More))];
+tokens({tuple,L,[]}, More) ->
+ [{'{',L},{'}',L}|More];
+tokens({tuple,L,[E|Es]}, More) ->
+ [{'{',L}|tokens(E, tokens_tuple(Es, ?line(E), More))].
+
+tokens_tail({cons,L,Head,Tail}, More) ->
+ [{',',L}|tokens(Head, tokens_tail(Tail, More))];
+tokens_tail({nil,L}, More) ->
+ [{']',L}|More];
+tokens_tail(Other, More) ->
+ L = ?line(Other),
+ [{'|',L}|tokens(Other, [{']',L}|More])].
+
+tokens_tuple([E|Es], Line, More) ->
+ [{',',Line}|tokens(E, tokens_tuple(Es, ?line(E), More))];
+tokens_tuple([], Line, More) ->
+ [{'}',Line}|More].
+
+%% Give the relative precedences of operators.
+
+inop_prec('=') -> {150,100,100};
+inop_prec('!') -> {150,100,100};
+inop_prec('orelse') -> {160,150,150};
+inop_prec('andalso') -> {200,160,160};
+inop_prec('==') -> {300,200,300};
+inop_prec('/=') -> {300,200,300};
+inop_prec('=<') -> {300,200,300};
+inop_prec('<') -> {300,200,300};
+inop_prec('>=') -> {300,200,300};
+inop_prec('>') -> {300,200,300};
+inop_prec('=:=') -> {300,200,300};
+inop_prec('=/=') -> {300,200,300};
+inop_prec('++') -> {400,300,300};
+inop_prec('--') -> {400,300,300};
+inop_prec('+') -> {400,400,500};
+inop_prec('-') -> {400,400,500};
+inop_prec('bor') -> {400,400,500};
+inop_prec('bxor') -> {400,400,500};
+inop_prec('bsl') -> {400,400,500};
+inop_prec('bsr') -> {400,400,500};
+inop_prec('or') -> {400,400,500};
+inop_prec('xor') -> {400,400,500};
+inop_prec('*') -> {500,500,600};
+inop_prec('/') -> {500,500,600};
+inop_prec('div') -> {500,500,600};
+inop_prec('rem') -> {500,500,600};
+inop_prec('band') -> {500,500,600};
+inop_prec('and') -> {500,500,600};
+inop_prec('#') -> {800,700,800};
+inop_prec(':') -> {900,800,900};
+inop_prec('.') -> {900,900,1000}.
+
+-type pre_op() :: 'catch' | '+' | '-' | 'bnot' | 'not' | '#'.
+
+-spec preop_prec(pre_op()) -> {0 | 600 | 700, 100 | 700 | 800}.
+
+preop_prec('catch') -> {0,100};
+preop_prec('+') -> {600,700};
+preop_prec('-') -> {600,700};
+preop_prec('bnot') -> {600,700};
+preop_prec('not') -> {600,700};
+preop_prec('#') -> {700,800}.
+
+-spec func_prec() -> {800,700}.
+
+func_prec() -> {800,700}.
+
+-spec max_prec() -> 1000.
+
+max_prec() -> 1000.
+
+parse(T) ->
+ bar:foo(T).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/big_local_type.erl b/lib/dialyzer/test/opaque_SUITE_data/src/big_local_type.erl
new file mode 100644
index 0000000000..7daceb5260
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/big_local_type.erl
@@ -0,0 +1,523 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%% A copy of small_SUITE_data/src/big_local_type.erl, where
+%%% abstract_expr() is opaque. The transformation of forms to types is
+%%% now much faster than it used to be, for this module.
+
+-module(big_local_type).
+
+-export([parse_form/1,parse_exprs/1,parse_term/1]).
+-export([normalise/1,tokens/1,tokens/2]).
+-export([inop_prec/1,preop_prec/1,func_prec/0,max_prec/0]).
+
+-export_type([abstract_clause/0, abstract_expr/0, abstract_form/0,
+ error_info/0]).
+
+%% Start of Abstract Format
+
+-type line() :: erl_anno:line().
+
+-export_type([af_module/0, af_export/0, af_import/0, af_fa_list/0,
+ af_compile/0, af_file/0, af_record_decl/0,
+ af_field_decl/0, af_wild_attribute/0,
+ af_record_update/1, af_catch/0, af_local_call/0,
+ af_remote_call/0, af_args/0, af_local_function/0,
+ af_remote_function/0, af_list_comprehension/0,
+ af_binary_comprehension/0, af_template/0,
+ af_qualifier_seq/0, af_qualifier/0, af_generator/0,
+ af_filter/0, af_block/0, af_if/0, af_case/0, af_try/0,
+ af_clause_seq/0, af_catch_clause_seq/0, af_receive/0,
+ af_local_fun/0, af_remote_fun/0, af_fun/0, af_query/0,
+ af_query_access/0, af_clause/0,
+ af_catch_clause/0, af_catch_pattern/0, af_catch_class/0,
+ af_body/0, af_guard_seq/0, af_guard/0, af_guard_test/0,
+ af_record_access/1, af_guard_call/0,
+ af_remote_guard_call/0, af_pattern/0, af_literal/0,
+ af_atom/0, af_lit_atom/1, af_integer/0, af_float/0,
+ af_string/0, af_match/1, af_variable/0,
+ af_anon_variable/0, af_tuple/1, af_nil/0, af_cons/1,
+ af_bin/1, af_binelement/1, af_binelement_size/0,
+ af_binary_op/1, af_binop/0, af_unary_op/1, af_unop/0]).
+
+-type abstract_form() :: af_module()
+ | af_export()
+ | af_import()
+ | af_compile()
+ | af_file()
+ | af_record_decl()
+ | af_wild_attribute()
+ | af_function_decl().
+
+-type af_module() :: {attribute, line(), module, module()}.
+
+-type af_export() :: {attribute, line(), export, af_fa_list()}.
+
+-type af_import() :: {attribute, line(), import, af_fa_list()}.
+
+-type af_fa_list() :: [{function(), arity()}].
+
+-type af_compile() :: {attribute, line(), compile, any()}.
+
+-type af_file() :: {attribute, line(), file, {string(), line()}}.
+
+-type af_record_decl() ::
+ {attribute, line(), record, af_record_name(), [af_field_decl()]}.
+
+-type af_field_decl() :: {record_field, line(), af_atom()}
+ | {record_field, line(), af_atom(), abstract_expr()}.
+
+%% Types and specs, among other things...
+-type af_wild_attribute() :: {attribute, line(), af_atom(), any()}.
+
+-type af_function_decl() ::
+ {function, line(), function(), arity(), af_clause_seq()}.
+
+-opaque abstract_expr() :: af_literal()
+ | af_match(abstract_expr())
+ | af_variable()
+ | af_tuple(abstract_expr())
+ | af_nil()
+ | af_cons(abstract_expr())
+ | af_bin(abstract_expr())
+ | af_binary_op(abstract_expr())
+ | af_unary_op(abstract_expr())
+ | af_record_access(abstract_expr())
+ | af_record_update(abstract_expr())
+ | af_record_index()
+ | af_record_field(abstract_expr())
+ | af_catch()
+ | af_local_call()
+ | af_remote_call()
+ | af_list_comprehension()
+ | af_binary_comprehension()
+ | af_block()
+ | af_if()
+ | af_case()
+ | af_try()
+ | af_receive()
+ | af_local_fun()
+ | af_remote_fun()
+ | af_fun()
+ | af_query()
+ | af_query_access().
+
+-type af_record_update(T) :: {record,
+ line(),
+ abstract_expr(),
+ af_record_name(),
+ [af_record_field(T)]}.
+
+-type af_catch() :: {'catch', line(), abstract_expr()}.
+
+-type af_local_call() :: {call, line(), af_local_function(), af_args()}.
+
+-type af_remote_call() :: {call, line(), af_remote_function(), af_args()}.
+
+-type af_args() :: [abstract_expr()].
+
+-type af_local_function() :: abstract_expr().
+
+-type af_remote_function() ::
+ {remote, line(), abstract_expr(), abstract_expr()}.
+
+-type af_list_comprehension() ::
+ {lc, line(), af_template(), af_qualifier_seq()}.
+
+-type af_binary_comprehension() ::
+ {bc, line(), af_template(), af_qualifier_seq()}.
+
+-type af_template() :: abstract_expr().
+
+-type af_qualifier_seq() :: [af_qualifier()].
+
+-type af_qualifier() :: af_generator() | af_filter().
+
+-type af_generator() :: {generate, line(), af_pattern(), abstract_expr()}
+ | {b_generate, line(), af_pattern(), abstract_expr()}.
+
+-type af_filter() :: abstract_expr().
+
+-type af_block() :: {block, line(), af_body()}.
+
+-type af_if() :: {'if', line(), af_clause_seq()}.
+
+-type af_case() :: {'case', line(), abstract_expr(), af_clause_seq()}.
+
+-type af_try() :: {'try',
+ line(),
+ af_body(),
+ af_clause_seq(),
+ af_catch_clause_seq(),
+ af_body()}.
+
+-type af_clause_seq() :: [af_clause(), ...].
+
+-type af_catch_clause_seq() :: [af_clause(), ...].
+
+-type af_receive() ::
+ {'receive', line(), af_clause_seq()}
+ | {'receive', line(), af_clause_seq(), abstract_expr(), af_body()}.
+
+-type af_local_fun() :: {'fun', line(), {function, function(), arity()}}.
+
+-type af_remote_fun() ::
+ {'fun', line(), {function, module(), function(), arity()}}
+ | {'fun', line(), {function, af_atom(), af_atom(), af_integer()}}.
+
+-type af_fun() :: {'fun', line(), {clauses, af_clause_seq()}}.
+
+-type af_query() :: {'query', line(), af_list_comprehension()}.
+
+-type af_query_access() ::
+ {record_field, line(), abstract_expr(), af_field_name()}.
+
+-type abstract_clause() :: af_clause() | af_catch_clause().
+
+-type af_clause() ::
+ {clause, line(), [af_pattern()], af_guard_seq(), af_body()}.
+
+-type af_catch_clause() ::
+ {clause, line(), [af_catch_pattern()], af_guard_seq(), af_body()}.
+
+-type af_catch_pattern() ::
+ {af_catch_class(), af_pattern(), af_anon_variable()}.
+
+-type af_catch_class() ::
+ af_variable()
+ | af_lit_atom(throw) | af_lit_atom(error) | af_lit_atom(exit).
+
+-type af_body() :: [abstract_expr(), ...].
+
+-type af_guard_seq() :: [af_guard()].
+
+-type af_guard() :: [af_guard_test(), ...].
+
+-type af_guard_test() :: af_literal()
+ | af_variable()
+ | af_tuple(af_guard_test())
+ | af_nil()
+ | af_cons(af_guard_test())
+ | af_bin(af_guard_test())
+ | af_binary_op(af_guard_test())
+ | af_unary_op(af_guard_test())
+ | af_record_access(af_guard_test())
+ | af_record_index()
+ | af_record_field(af_guard_test())
+ | af_guard_call()
+ | af_remote_guard_call().
+
+-type af_record_access(T) ::
+ {record, line(), af_record_name(), [af_record_field(T)]}.
+
+-type af_guard_call() :: {call, line(), function(), [af_guard_test()]}.
+
+-type af_remote_guard_call() ::
+ {call, line(), atom(), af_lit_atom(erlang), [af_guard_test()]}.
+
+-type af_pattern() :: af_literal()
+ | af_match(af_pattern())
+ | af_variable()
+ | af_anon_variable()
+ | af_tuple(af_pattern())
+ | af_nil()
+ | af_cons(af_pattern())
+ | af_bin(af_pattern())
+ | af_binary_op(af_pattern())
+ | af_unary_op(af_pattern())
+ | af_record_index()
+ | af_record_field(af_pattern()).
+
+-type af_literal() :: af_atom() | af_integer() | af_float() | af_string().
+
+-type af_atom() :: af_lit_atom(atom()).
+
+-type af_lit_atom(A) :: {atom, line(), A}.
+
+-type af_integer() :: {integer, line(), non_neg_integer()}.
+
+-type af_float() :: {float, line(), float()}.
+
+-type af_string() :: {string, line(), [byte()]}.
+
+-type af_match(T) :: {match, line(), T, T}.
+
+-type af_variable() :: {var, line(), atom()}.
+
+-type af_anon_variable() :: {var, line(), '_'}.
+
+-type af_tuple(T) :: {tuple, line(), [T]}.
+
+-type af_nil() :: {nil, line()}.
+
+-type af_cons(T) :: {cons, line, T, T}.
+
+-type af_bin(T) :: {bin, line(), [af_binelement(T)]}.
+
+-type af_binelement(T) :: {bin_element,
+ line(),
+ T,
+ af_binelement_size(),
+ type_specifier_list()}.
+
+-type af_binelement_size() :: default | abstract_expr().
+
+-type af_binary_op(T) :: {op, line(), T, af_binop(), T}.
+
+-type af_binop() :: '/' | '*' | 'div' | 'rem' | 'band' | 'and' | '+' | '-'
+ | 'bor' | 'bxor' | 'bsl' | 'bsr' | 'or' | 'xor' | '++'
+ | '--' | '==' | '/=' | '=<' | '<' | '>=' | '>' | '=:='
+ | '=/='.
+
+-type af_unary_op(T) :: {op, line(), af_unop(), T}.
+
+-type af_unop() :: '+' | '*' | 'bnot' | 'not'.
+
+%% See also lib/stdlib/{src/erl_bits.erl,include/erl_bits.hrl}.
+-type type_specifier_list() :: default | [type_specifier(), ...].
+
+-type type_specifier() :: af_type()
+ | af_signedness()
+ | af_endianness()
+ | af_unit().
+
+-type af_type() :: integer
+ | float
+ | binary
+ | bytes
+ | bitstring
+ | bits
+ | utf8
+ | utf16
+ | utf32.
+
+-type af_signedness() :: signed | unsigned.
+
+-type af_endianness() :: big | little | native.
+
+-type af_unit() :: {unit, 1..256}.
+
+-type af_record_index() ::
+ {record_index, line(), af_record_name(), af_field_name()}.
+
+-type af_record_field(T) :: {record_field, line(), af_field_name(), T}.
+
+-type af_record_name() :: atom().
+
+-type af_field_name() :: atom().
+
+%% End of Abstract Format
+
+-type error_description() :: term().
+-type error_info() :: {erl_anno:line(), module(), error_description()}.
+-type token() :: {Tag :: atom(), Line :: erl_anno:line()}.
+
+%% mkop(Op, Arg) -> {op,Line,Op,Arg}.
+%% mkop(Left, Op, Right) -> {op,Line,Op,Left,Right}.
+
+-define(mkop2(L, OpPos, R),
+ begin
+ {Op,Pos} = OpPos,
+ {op,Pos,Op,L,R}
+ end).
+
+-define(mkop1(OpPos, A),
+ begin
+ {Op,Pos} = OpPos,
+ {op,Pos,Op,A}
+ end).
+
+%% keep track of line info in tokens
+-define(line(Tup), element(2, Tup)).
+
+%% Entry points compatible to old erl_parse.
+%% These really suck and are only here until Calle gets multiple
+%% entry points working.
+
+-spec parse_form(Tokens) -> {ok, AbsForm} | {error, ErrorInfo} when
+ Tokens :: [token()],
+ AbsForm :: abstract_form(),
+ ErrorInfo :: error_info().
+parse_form([{'-',L1},{atom,L2,spec}|Tokens]) ->
+ parse([{'-',L1},{'spec',L2}|Tokens]);
+parse_form([{'-',L1},{atom,L2,callback}|Tokens]) ->
+ parse([{'-',L1},{'callback',L2}|Tokens]);
+parse_form(Tokens) ->
+ parse(Tokens).
+
+-spec parse_exprs(Tokens) -> {ok, ExprList} | {error, ErrorInfo} when
+ Tokens :: [token()],
+ ExprList :: [abstract_expr()],
+ ErrorInfo :: error_info().
+parse_exprs(Tokens) ->
+ case parse([{atom,0,f},{'(',0},{')',0},{'->',0}|Tokens]) of
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],Exprs}]}} ->
+ {ok,Exprs};
+ {error,_} = Err -> Err
+ end.
+
+-spec parse_term(Tokens) -> {ok, Term} | {error, ErrorInfo} when
+ Tokens :: [token()],
+ Term :: term(),
+ ErrorInfo :: error_info().
+parse_term(Tokens) ->
+ case parse([{atom,0,f},{'(',0},{')',0},{'->',0}|Tokens]) of
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],[Expr]}]}} ->
+ try normalise(Expr) of
+ Term -> {ok,Term}
+ catch
+ _:_R -> {error,{?line(Expr),?MODULE,"bad term"}}
+ end;
+ {ok,{function,_Lf,f,0,[{clause,_Lc,[],[],[_E1,E2|_Es]}]}} ->
+ {error,{?line(E2),?MODULE,"bad term"}};
+ {error,_} = Err -> Err
+ end.
+
+%% Convert between the abstract form of a term and a term.
+
+-spec normalise(AbsTerm) -> Data when
+ AbsTerm :: abstract_expr(),
+ Data :: term().
+normalise({char,_,C}) -> C;
+normalise({integer,_,I}) -> I;
+normalise({float,_,F}) -> F;
+normalise({atom,_,A}) -> A;
+normalise({string,_,S}) -> S;
+normalise({nil,_}) -> [];
+normalise({bin,_,Fs}) ->
+ {value, B, _} =
+ eval_bits:expr_grp(Fs, [],
+ fun(E, _) ->
+ {value, normalise(E), []}
+ end, [], true),
+ B;
+normalise({cons,_,Head,Tail}) ->
+ [normalise(Head)|normalise(Tail)];
+normalise({tuple,_,Args}) ->
+ list_to_tuple(normalise_list(Args));
+%% Atom dot-notation, as in 'foo.bar.baz'
+%% Special case for unary +/-.
+normalise({op,_,'+',{char,_,I}}) -> I;
+normalise({op,_,'+',{integer,_,I}}) -> I;
+normalise({op,_,'+',{float,_,F}}) -> F;
+normalise({op,_,'-',{char,_,I}}) -> -I; %Weird, but compatible!
+normalise({op,_,'-',{integer,_,I}}) -> -I;
+normalise({op,_,'-',{float,_,F}}) -> -F;
+normalise(X) -> erlang:error({badarg, X}).
+
+normalise_list([H|T]) ->
+ [normalise(H)|normalise_list(T)];
+normalise_list([]) ->
+ [].
+
+%% Generate a list of tokens representing the abstract term.
+
+-spec tokens(AbsTerm) -> Tokens when
+ AbsTerm :: abstract_expr(),
+ Tokens :: [token()].
+tokens(Abs) ->
+ tokens(Abs, []).
+
+-spec tokens(AbsTerm, MoreTokens) -> Tokens when
+ AbsTerm :: abstract_expr(),
+ MoreTokens :: [token()],
+ Tokens :: [token()].
+tokens({char,L,C}, More) -> [{char,L,C}|More];
+tokens({integer,L,N}, More) -> [{integer,L,N}|More];
+tokens({float,L,F}, More) -> [{float,L,F}|More];
+tokens({atom,L,A}, More) -> [{atom,L,A}|More];
+tokens({var,L,V}, More) -> [{var,L,V}|More];
+tokens({string,L,S}, More) -> [{string,L,S}|More];
+tokens({nil,L}, More) -> [{'[',L},{']',L}|More];
+tokens({cons,L,Head,Tail}, More) ->
+ [{'[',L}|tokens(Head, tokens_tail(Tail, More))];
+tokens({tuple,L,[]}, More) ->
+ [{'{',L},{'}',L}|More];
+tokens({tuple,L,[E|Es]}, More) ->
+ [{'{',L}|tokens(E, tokens_tuple(Es, ?line(E), More))].
+
+tokens_tail({cons,L,Head,Tail}, More) ->
+ [{',',L}|tokens(Head, tokens_tail(Tail, More))];
+tokens_tail({nil,L}, More) ->
+ [{']',L}|More];
+tokens_tail(Other, More) ->
+ L = ?line(Other),
+ [{'|',L}|tokens(Other, [{']',L}|More])].
+
+tokens_tuple([E|Es], Line, More) ->
+ [{',',Line}|tokens(E, tokens_tuple(Es, ?line(E), More))];
+tokens_tuple([], Line, More) ->
+ [{'}',Line}|More].
+
+%% Give the relative precedences of operators.
+
+inop_prec('=') -> {150,100,100};
+inop_prec('!') -> {150,100,100};
+inop_prec('orelse') -> {160,150,150};
+inop_prec('andalso') -> {200,160,160};
+inop_prec('==') -> {300,200,300};
+inop_prec('/=') -> {300,200,300};
+inop_prec('=<') -> {300,200,300};
+inop_prec('<') -> {300,200,300};
+inop_prec('>=') -> {300,200,300};
+inop_prec('>') -> {300,200,300};
+inop_prec('=:=') -> {300,200,300};
+inop_prec('=/=') -> {300,200,300};
+inop_prec('++') -> {400,300,300};
+inop_prec('--') -> {400,300,300};
+inop_prec('+') -> {400,400,500};
+inop_prec('-') -> {400,400,500};
+inop_prec('bor') -> {400,400,500};
+inop_prec('bxor') -> {400,400,500};
+inop_prec('bsl') -> {400,400,500};
+inop_prec('bsr') -> {400,400,500};
+inop_prec('or') -> {400,400,500};
+inop_prec('xor') -> {400,400,500};
+inop_prec('*') -> {500,500,600};
+inop_prec('/') -> {500,500,600};
+inop_prec('div') -> {500,500,600};
+inop_prec('rem') -> {500,500,600};
+inop_prec('band') -> {500,500,600};
+inop_prec('and') -> {500,500,600};
+inop_prec('#') -> {800,700,800};
+inop_prec(':') -> {900,800,900};
+inop_prec('.') -> {900,900,1000}.
+
+-type pre_op() :: 'catch' | '+' | '-' | 'bnot' | 'not' | '#'.
+
+-spec preop_prec(pre_op()) -> {0 | 600 | 700, 100 | 700 | 800}.
+
+preop_prec('catch') -> {0,100};
+preop_prec('+') -> {600,700};
+preop_prec('-') -> {600,700};
+preop_prec('bnot') -> {600,700};
+preop_prec('not') -> {600,700};
+preop_prec('#') -> {700,800}.
+
+-spec func_prec() -> {800,700}.
+
+func_prec() -> {800,700}.
+
+-spec max_prec() -> 1000.
+
+max_prec() -> 1000.
+
+parse(T) ->
+ bar:foo(T).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_macros.hrl b/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_macros.hrl
new file mode 100644
index 0000000000..07243f8d23
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_macros.hrl
@@ -0,0 +1,215 @@
+%% -*- erlang-indent-level: 2 -*-
+%%------------------------------------------------------------------------------
+
+%%====================================================================
+%% Types
+%%====================================================================
+
+%% Code and Monitor servers' info.
+-record(svs, {
+ code :: pid(),
+ monitor :: pid()
+}).
+
+%% Tags of an AST's node.
+-record(tags, {
+ this = undefined :: cuter_cerl:tag() | undefined,
+ next = undefined :: cuter_cerl:tag() | undefined
+}).
+
+-type loaded_ret_atoms() :: cover_compiled | preloaded | non_existing.
+-type servers() :: #svs{}.
+-type ast_tags() :: #tags{}.
+
+%%====================================================================
+%% Directories
+%%====================================================================
+
+-define(RELATIVE_TMP_DIR, "temp").
+-define(PYTHON_CALL, ?PYTHON_PATH ++ " -u " ++ ?PRIV ++ "/cuter_interface.py").
+
+%%====================================================================
+%% Prefixes
+%%====================================================================
+
+-define(DEPTH_PREFIX, '__conc_depth').
+-define(EXECUTION_PREFIX, '__conc_prefix').
+-define(SYMBOLIC_PREFIX, '__s').
+-define(CONCOLIC_PREFIX_MSG, '__concm').
+-define(ZIPPED_VALUE_PREFIX, '__czip').
+-define(CONCOLIC_PREFIX_PDICT, '__concp').
+-define(FUNCTION_PREFIX, '__cfunc').
+-define(UNBOUND_VAR_PREFIX, '__uboundvar').
+-define(BRANCH_TAG_PREFIX, '__branch_tag').
+-define(VISITED_TAGS_PREFIX, '__visited_tags').
+-define(EXECUTION_COUNTER_PREFIX, '__exec_counter').
+
+%%====================================================================
+%% Flags & Default Values
+%%====================================================================
+
+-define(LOGGING_FLAG, ok).
+-define(DELETE_TRACE, ok).
+-define(LOG_UNSUPPORTED_MFAS, ok).
+%%-define(VERBOSE_SCHEDULER, ok).
+%%-define(VERBOSE_FILE_DELETION, ok).
+%%-define(VERBOSE_SOLVING, ok).
+%%-define(VERBOSE_MERGING, ok).
+%%-define(VERBOSE_REPORTING, ok).
+-define(USE_SPECS, ok).
+
+%%====================================================================
+%% Solver Responses
+%%====================================================================
+
+-define(RSP_MODEL_DELIMITER_START, <<"model_start">>).
+-define(RSP_MODEL_DELIMITER_END, <<"model_end">>).
+
+%%====================================================================
+%% OpCodes for types in JSON objects
+%%====================================================================
+
+-define(JSON_TYPE_ANY, 0).
+-define(JSON_TYPE_INT, 1).
+-define(JSON_TYPE_FLOAT, 2).
+-define(JSON_TYPE_ATOM, 3).
+-define(JSON_TYPE_LIST, 4).
+-define(JSON_TYPE_TUPLE, 5).
+-define(JSON_TYPE_PID, 6).
+-define(JSON_TYPE_REF, 7).
+
+%%====================================================================
+%% OpCodes for the commands to the solver
+%%====================================================================
+
+-define(JSON_CMD_LOAD_TRACE_FILE, 1).
+-define(JSON_CMD_SOLVE, 2).
+-define(JSON_CMD_GET_MODEL, 3).
+-define(JSON_CMD_ADD_AXIOMS, 4).
+-define(JSON_CMD_FIX_VARIABLE, 5).
+-define(JSON_CMD_RESET_SOLVER, 6).
+-define(JSON_CMD_STOP, 42).
+
+%%====================================================================
+%% OpCodes for constraint types
+%%====================================================================
+
+-define(CONSTRAINT_TRUE, 1).
+-define(CONSTRAINT_FALSE, 2).
+-define(NOT_CONSTRAINT, 3).
+
+-define(CONSTRAINT_TRUE_REPR, 84). %% $T
+-define(CONSTRAINT_FALSE_REPR, 70). %% $F
+
+%%====================================================================
+%% OpCodes of constraints & built-in operations
+%%====================================================================
+
+%% Empty tag ID
+-define(EMPTY_TAG_ID, 0).
+
+%% MFA's Parameters & Spec definitions.
+-define(OP_PARAMS, 1).
+-define(OP_SPEC, 2).
+%% Constraints.
+-define(OP_GUARD_TRUE, 3).
+-define(OP_GUARD_FALSE, 4).
+-define(OP_MATCH_EQUAL_TRUE, 5).
+-define(OP_MATCH_EQUAL_FALSE, 6).
+-define(OP_TUPLE_SZ, 7).
+-define(OP_TUPLE_NOT_SZ, 8).
+-define(OP_TUPLE_NOT_TPL, 9).
+-define(OP_LIST_NON_EMPTY, 10).
+-define(OP_LIST_EMPTY, 11).
+-define(OP_LIST_NOT_LST, 12).
+%% Information used for syncing & merging the traces of many processes.
+-define(OP_SPAWN, 13).
+-define(OP_SPAWNED, 14).
+-define(OP_MSG_SEND, 15).
+-define(OP_MSG_RECEIVE, 16).
+-define(OP_MSG_CONSUME, 17).
+%% Necessary operations for the evaluation of Core Erlang.
+-define(OP_UNFOLD_TUPLE, 18).
+-define(OP_UNFOLD_LIST, 19).
+%% Bogus operation (operations interpreted as the identity function).
+-define(OP_BOGUS, 48).
+%% Type conversions.
+-define(OP_FLOAT, 47).
+-define(OP_LIST_TO_TUPLE, 52).
+-define(OP_TUPLE_TO_LIST, 53).
+%% Query types.
+-define(OP_IS_INTEGER, 27).
+-define(OP_IS_ATOM, 28).
+-define(OP_IS_FLOAT, 29).
+-define(OP_IS_LIST, 30).
+-define(OP_IS_TUPLE, 31).
+-define(OP_IS_BOOLEAN, 32).
+-define(OP_IS_NUMBER, 33).
+%% Arithmetic operations.
+-define(OP_PLUS, 34).
+-define(OP_MINUS, 35).
+-define(OP_TIMES, 36).
+-define(OP_RDIV, 37).
+-define(OP_IDIV_NAT, 38).
+-define(OP_REM_NAT, 39).
+-define(OP_UNARY, 40).
+%% Operations on atoms.
+-define(OP_ATOM_NIL, 49).
+-define(OP_ATOM_HEAD, 50).
+-define(OP_ATOM_TAIL, 51).
+%% Operations on lists.
+-define(OP_HD, 25).
+-define(OP_TL, 26).
+-define(OP_CONS, 56).
+%% Operations on tuples.
+-define(OP_TCONS, 57).
+%% Comparisons.
+-define(OP_EQUAL, 41).
+-define(OP_UNEQUAL, 42).
+-define(OP_LT_INT, 54).
+-define(OP_LT_FLOAT, 55).
+
+%% Maps MFAs to their JSON Opcodes
+-define(OPCODE_MAPPING,
+ dict:from_list([ %% Simulated built-in operations
+ { {cuter_erlang, atom_to_list_bogus, 1}, ?OP_BOGUS }
+ , { {cuter_erlang, is_atom_nil, 1}, ?OP_ATOM_NIL }
+ , { {cuter_erlang, safe_atom_head, 1}, ?OP_ATOM_HEAD }
+ , { {cuter_erlang, safe_atom_tail, 1}, ?OP_ATOM_TAIL }
+ , { {cuter_erlang, safe_pos_div, 2}, ?OP_IDIV_NAT }
+ , { {cuter_erlang, safe_pos_rem, 2}, ?OP_REM_NAT }
+ , { {cuter_erlang, lt_int, 2}, ?OP_LT_INT }
+ , { {cuter_erlang, lt_float, 2}, ?OP_LT_FLOAT }
+ , { {cuter_erlang, safe_plus, 2}, ?OP_PLUS }
+ , { {cuter_erlang, safe_minus, 2}, ?OP_MINUS }
+ , { {cuter_erlang, safe_times, 2}, ?OP_TIMES }
+ , { {cuter_erlang, safe_rdiv, 2}, ?OP_RDIV }
+ , { {cuter_erlang, safe_float, 1}, ?OP_FLOAT }
+ , { {cuter_erlang, safe_list_to_tuple, 1}, ?OP_LIST_TO_TUPLE }
+ , { {cuter_erlang, safe_tuple_to_list, 1}, ?OP_TUPLE_TO_LIST }
+ , { {bogus_erlang, cons, 2}, ?OP_CONS }
+ %% Actual erlang BIFs
+ , { {erlang, hd, 1}, ?OP_HD }
+ , { {erlang, tl, 1}, ?OP_TL }
+ , { {erlang, is_integer, 1}, ?OP_IS_INTEGER }
+ , { {erlang, is_atom, 1}, ?OP_IS_ATOM }
+ , { {erlang, is_boolean, 1}, ?OP_IS_BOOLEAN }
+ , { {erlang, is_float, 1}, ?OP_IS_FLOAT }
+ , { {erlang, is_list, 1}, ?OP_IS_LIST }
+ , { {erlang, is_tuple, 1}, ?OP_IS_TUPLE }
+ , { {erlang, is_number, 1}, ?OP_IS_NUMBER }
+ , { {erlang, '-', 1}, ?OP_UNARY }
+ , { {erlang, '=:=', 2}, ?OP_EQUAL }
+ , { {erlang, '=/=', 2}, ?OP_UNEQUAL }
+ ])).
+
+%% All the MFAs that are supported for symbolic evaluation.
+-define(SUPPORTED_MFAS, gb_sets:from_list(dict:fetch_keys(?OPCODE_MAPPING))).
+
+-define(UNSUPPORTED_MFAS,
+ gb_sets:from_list([ {cuter_erlang, unsupported_lt, 2} ])).
+
+%% The set of all the built-in operations that the solver can try to reverse.
+-define (REVERSIBLE_OPERATIONS,
+ gb_sets:from_list([ ?OP_HD, ?OP_TL
+ ])).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_types.erl b/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_types.erl
new file mode 100644
index 0000000000..e9561374cc
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_types.erl
@@ -0,0 +1,607 @@
+%% -*- erlang-indent-level: 2 -*-
+%%------------------------------------------------------------------------------
+-module(cuter_types).
+
+-export([parse_spec/3, retrieve_types/1, retrieve_specs/1, find_spec/2, get_kind/1]).
+
+-export([params_of_t_function_det/1, ret_of_t_function_det/1, atom_of_t_atom_lit/1, integer_of_t_integer_lit/1,
+ elements_type_of_t_list/1, elements_type_of_t_nonempty_list/1, elements_types_of_t_tuple/1,
+ elements_types_of_t_union/1, bounds_of_t_range/1, segment_size_of_bitstring/1]).
+
+-export_type([erl_type/0, erl_spec_clause/0, erl_spec/0, stored_specs/0, stored_types/0, stored_spec_value/0, t_range_limit/0]).
+
+-include("cuter_macros.hrl").
+-include("cuter_types.hrl").
+
+
+%% Define tags
+-define(type_variable, vart).
+-define(type_var, tvar).
+-define(max_char, 16#10ffff).
+
+%% Pre-processed types.
+
+-type type_name() :: atom().
+-type type_arity() :: byte().
+-type type_var() :: {?type_var, atom()}.
+-type remote_type() :: {module(), type_name(), type_arity()}.
+-type record_name() :: atom().
+-type record_field_name() :: atom().
+-type record_field_type() :: {record_field_name(), raw_type()}.
+-type dep() :: remote_type().
+-type deps() :: ordsets:ordset(remote_type()).
+-record(t, {
+ kind,
+ rep,
+ deps = ordsets:new() :: deps()
+}).
+-type erl_type() :: t_any() % any()
+ | t_nil() % []
+ | t_atom() % atom()
+ | t_atom_lit() % Erlang_Atom
+ | t_integer() % integer(), +infinity, -inifinity
+ | t_integer_lit() % Erlang_Integer
+ | t_float() % float()
+ | t_tuple() % tuple(), {TList}
+ | t_list() % list(Type)
+ | t_nonempty_list() % nonempty_list(Type)
+ | t_union() % Type1 | ... | TypeN
+ | t_range() % Erlang_Integer..Erlang_Integer
+ | t_bitstring() % <<_:M>>
+ | t_function() % function() | Fun | BoundedFun
+ .
+-type raw_type() :: erl_type()
+ | t_local() % Local Type Usage
+ | t_remote() % Remote Type Usage
+ | t_record() % Record Usage
+ | t_type_var() % Type Variable
+ .
+
+-type t_any() :: #t{kind :: ?any_tag}.
+-type t_nil() :: #t{kind :: ?nil_tag}.
+-type t_atom() :: #t{kind :: ?atom_tag}.
+-type t_atom_lit() :: #t{kind :: ?atom_lit_tag, rep :: atom()}.
+-type t_integer() :: #t{kind :: ?integer_tag}.
+-type t_integer_lit() :: #t{kind :: ?integer_lit_tag, rep :: integer()}.
+-type t_float() :: #t{kind :: ?float_tag}.
+-type t_tuple() :: #t{kind :: ?tuple_tag, rep :: [raw_type()]}.
+-type t_list() :: #t{kind :: ?list_tag, rep :: raw_type()}.
+-type t_nonempty_list() :: #t{kind :: ?nonempty_list_tag, rep :: raw_type()}.
+-type t_union() :: #t{kind :: ?union_tag, rep :: [raw_type()]}.
+-type t_range() :: #t{kind :: ?range_tag, rep :: {t_range_limit(), t_range_limit()}}.
+-type t_range_limit() :: t_integer_lit() | t_integer_inf().
+-type t_integer_inf() :: t_integer_pos_inf() | t_integer_neg_inf().
+-type t_integer_pos_inf() :: #t{kind :: ?pos_inf}.
+-type t_integer_neg_inf() :: #t{kind :: ?neg_inf}.
+-type t_bitstring() :: #t{kind :: ?bitstring_tag, rep :: 1|8}.
+-type t_function() :: #t{kind :: ?function_tag} | t_function_det().
+-type t_function_det() :: #t{kind :: ?function_tag, rep :: {[raw_type()], raw_type(), [t_constraint()]}, deps :: deps()}.
+-type t_constraint() :: {t_type_var(), raw_type()}.
+-type t_local() :: #t{kind :: ?local_tag, rep :: {type_name(), [raw_type()]}}.
+-type t_remote() :: #t{kind :: ?remote_tag, rep :: {module(), type_name(), [raw_type()]}}.
+-type t_record() :: #t{kind :: ?record_tag, rep :: {record_name(), [record_field_type()]}}.
+-type t_type_var() :: #t{kind :: ?type_variable, rep :: type_var()}.
+
+%% How pre-processed types are stored.
+-type stored_type_key() :: {record, record_name()} | {type, type_name(), type_arity()}.
+-type stored_type_value() :: [record_field_type()] | {any(), [type_var()]}. % raw_type()
+-type stored_types() :: dict:dict(stored_type_key(), stored_type_value()).
+
+-type stored_spec_key() :: {type_name(), type_arity()}.
+-type stored_spec_value() :: [t_function_det()].
+-type stored_specs() :: dict:dict(stored_spec_key(), stored_spec_value()).
+
+-type type_var_env() :: dict:dict(type_var(), raw_type()).
+-type erl_spec_clause() :: t_function_det().
+-type erl_spec() :: [erl_spec_clause()].
+
+%% Pre-process the type & record declarations of a module.
+-spec retrieve_types([cuter_cerl:cerl_attr_type()]) -> stored_types().
+retrieve_types(TypeAttrs) ->
+ lists:foldl(fun process_type_attr/2, dict:new(), TypeAttrs).
+
+-spec process_type_attr(cuter_cerl:cerl_recdef() | cuter_cerl:cerl_typedef(), stored_types()) -> stored_types().
+%% Declaration of a record.
+process_type_attr({{record, Name}, Fields, []}, Processed) ->
+ Fs = [t_field_from_form(Field) || Field <- Fields],
+ Record = t_record(Name, Fs),
+ dict:store({record, Name}, Record, Processed);
+%% Declaration of a type.
+process_type_attr({Name, Repr, Vars}, Processed) ->
+ Type = safe_t_from_form(Repr),
+ Vs = [{?type_var, Var} || {var, _, Var} <- Vars],
+ dict:store({type, Name, length(Vs)}, {Type, Vs}, Processed).
+
+%% The fields of a declared record.
+-spec t_field_from_form(cuter_cerl:cerl_record_field()) -> record_field_type().
+t_field_from_form({record_field, _, {atom, _, Name}}) ->
+ {Name, t_any()};
+t_field_from_form({record_field, _, {atom, _, Name}, _Default}) ->
+ {Name, t_any()};
+t_field_from_form({typed_record_field, {record_field, _, {atom, _, Name}}, Type}) ->
+ {Name, safe_t_from_form(Type)};
+t_field_from_form({typed_record_field, {record_field, _, {atom, _, Name}, _Default}, Type}) ->
+ {Name, safe_t_from_form(Type)}.
+
+%% Provision for unsupported types.
+safe_t_from_form(Form) ->
+ try t_from_form(Form)
+ catch throw:{unsupported, Info} ->
+ cuter_pp:form_has_unsupported_type(Info),
+ t_any()
+ end.
+
+%% Parse a type.
+
+-spec t_from_form(cuter_cerl:cerl_type()) -> raw_type().
+%% Erlang_Atom
+t_from_form({atom, _, Atom}) ->
+ t_atom_lit(Atom);
+%% Erlang_Integer
+t_from_form({integer, _, Integer}) ->
+ t_integer_lit(Integer);
+%% integer()
+t_from_form({type, _, integer, []}) ->
+ t_integer();
+%% nil
+t_from_form({type, _, nil, []}) ->
+ t_nil();
+%% any()
+t_from_form({type, _, any, []}) ->
+ t_any();
+%% term()
+t_from_form({type, _, term, []}) ->
+ t_any();
+%% atom()
+t_from_form({type, _, atom, []}) ->
+ t_atom();
+%% module()
+t_from_form({type, _, module, []}) ->
+ t_module();
+%% float()
+t_from_form({type, _, float, []}) ->
+ t_float();
+%% tuple()
+t_from_form({type, _, tuple, any}) ->
+ t_tuple();
+%% {TList}
+t_from_form({type, _, tuple, Types}) ->
+ Ts = [t_from_form(T) || T <- Types],
+ t_tuple(Ts);
+%% list()
+t_from_form({type, _, list, []}) ->
+ t_list();
+%% list(Type)
+t_from_form({type, _, list, [Type]}) ->
+ T = t_from_form(Type),
+ t_list(T);
+%% Type1 | ... | TypeN
+t_from_form({type, _, union, Types}) ->
+ Ts = [t_from_form(T) || T <- Types],
+ t_union(Ts);
+%% boolean()
+t_from_form({type, _, boolean, []}) ->
+ t_union([t_atom_lit(true), t_atom_lit(false)]);
+%% number()
+t_from_form({type, _, number, []}) ->
+ t_union([t_integer(), t_float()]);
+%% Erlang_Integer..Erlang_Integer
+t_from_form({type, _, range, [{integer, _, I1}, {integer, _, I2}]}) ->
+ t_range(t_integer_lit(I1), t_integer_lit(I2));
+%% non_neg_integer()
+t_from_form({type, _, non_neg_integer, []}) ->
+ t_range(t_integer_lit(0), t_pos_inf());
+%% pos_integer()
+t_from_form({type, _, pos_integer, []}) ->
+ t_range(t_integer_lit(1), t_pos_inf());
+%% neg_integer()
+t_from_form({type, _, neg_integer, []}) ->
+ t_range(t_neg_inf(), t_integer_lit(-1));
+%% char()
+t_from_form({type, _, char, []}) ->
+ t_char();
+%% byte()
+t_from_form({type, _, byte, []}) ->
+ t_byte();
+%% mfa()
+t_from_form({type, _, mfa, []}) ->
+ t_tuple([t_module(), t_atom(), t_byte()]);
+%% string()
+t_from_form({type, _, string, []}) ->
+ t_list(t_char());
+%% nonempty_list()
+t_from_form({type, _, nonempty_list, []}) ->
+ t_nonempty_list();
+%% nonempty_list(Type)
+t_from_form({type, _, nonempty_list, [Type]}) ->
+ T = t_from_form(Type),
+ t_nonempty_list(T);
+%% binary()
+t_from_form({type, _, binary, []}) ->
+ t_bitstring(8);
+%% bitstring()
+t_from_form({type, _, bitstring, []}) ->
+ t_bitstring(1);
+%% function()
+t_from_form({type, _, function, []}) ->
+ t_function();
+%% fun((TList) -> Type)
+t_from_form({type, _, 'fun', [_Product, _RetType]}=Fun) ->
+ t_function_from_form(Fun);
+%% fun((TList) -> Type) (bounded_fun)
+t_from_form({type, _, 'bounded_fun', [_Fun, _Cs]}=BoundedFun) ->
+ t_bounded_function_from_form(BoundedFun);
+%% ann_type
+t_from_form({ann_type, _, [_Var, Type]}) ->
+ t_from_form(Type);
+%% paren_type
+t_from_form({paren_type, _, [Type]}) ->
+ t_from_form(Type);
+%% remote_type
+t_from_form({remote_type, _, [{atom, _, M}, {atom, _, Name}, Types]}) ->
+ Ts = [t_from_form(T) || T <- Types],
+ t_remote(M, Name, Ts);
+%% Record
+t_from_form({type, _, record, [{atom, _, Name} | FieldTypes]}) ->
+ Fields = [t_bound_field_from_form(F) || F <- FieldTypes],
+ t_record(Name, Fields);
+%% Map
+t_from_form({type, _, map, _}=X) ->
+ throw({unsupported, X});
+%% local type
+t_from_form({type, _, Name, Types}) ->
+ Ts = [t_from_form(T) || T <- Types],
+ t_local(Name, Ts);
+%% Type Variable
+t_from_form({var, _, Var}) ->
+ t_var(Var);
+%% Unsupported forms
+t_from_form(Type) ->
+ throw({unsupported, Type}).
+
+-spec t_bound_field_from_form(cuter_cerl:cerl_type_record_field()) -> record_field_type().
+%% Record Field.
+t_bound_field_from_form({type, _, field_type, [{atom, _, Name}, Type]}) ->
+ {Name, t_from_form(Type)}.
+
+-spec t_function_from_form(cuter_cerl:cerl_func()) -> t_function_det().
+t_function_from_form({type, _, 'fun', [{type, _, 'product', Types}, RetType]}) ->
+ Ret = t_from_form(RetType),
+ Ts = [t_from_form(T) || T <- Types],
+ t_function(Ts, Ret).
+
+-spec t_bounded_function_from_form(cuter_cerl:cerl_bounded_func()) -> t_function_det().
+t_bounded_function_from_form({type, _, 'bounded_fun', [Fun, Constraints]}) ->
+ {type, _, 'fun', [{type, _, 'product', Types}, RetType]} = Fun,
+ Ret = t_from_form(RetType),
+ Ts = [t_from_form(T) || T <- Types],
+ Cs = [t_constraint_from_form(C) || C <- Constraints],
+ t_function(Ts, Ret, Cs).
+
+-spec t_constraint_from_form(cuter_cerl:cerl_constraint()) -> t_constraint().
+t_constraint_from_form({type, _, constraint, [{atom, _, is_subtype}, [{var, _, Var}, Type]]}) ->
+ {t_var(Var), t_from_form(Type)}.
+
+
+%% Type constructors.
+
+-spec t_any() -> t_any().
+t_any() ->
+ #t{kind = ?any_tag}.
+
+-spec t_atom_lit(atom()) -> t_atom_lit().
+t_atom_lit(Atom) ->
+ #t{kind = ?atom_lit_tag, rep = Atom}.
+
+-spec t_atom() -> t_atom().
+t_atom() ->
+ #t{kind = ?atom_tag}.
+
+-spec t_module() -> t_atom().
+t_module() -> t_atom().
+
+-spec t_integer_lit(integer()) -> t_integer_lit().
+t_integer_lit(Integer) ->
+ #t{kind = ?integer_lit_tag, rep = Integer}.
+
+-spec t_integer() -> t_integer().
+t_integer() ->
+ #t{kind = ?integer_tag}.
+
+-spec t_range(t_range_limit(), t_range_limit()) -> t_range().
+t_range(Int1, Int2) ->
+ #t{kind = ?range_tag, rep = {Int1, Int2}}.
+
+-spec t_pos_inf() -> t_integer_pos_inf().
+t_pos_inf() ->
+ #t{kind = ?pos_inf}.
+
+-spec t_neg_inf() -> t_integer_neg_inf().
+t_neg_inf() ->
+ #t{kind = ?neg_inf}.
+
+-spec t_char() -> t_range().
+t_char() ->
+ t_range(t_integer_lit(0), t_integer_lit(?max_char)).
+
+-spec t_nil() -> t_nil().
+t_nil() ->
+ #t{kind = ?nil_tag}.
+
+-spec t_float() -> t_float().
+t_float() ->
+ #t{kind = ?float_tag}.
+
+-spec t_list() -> t_list().
+t_list() ->
+ #t{kind = ?list_tag, rep = t_any()}.
+
+-spec t_list(raw_type()) -> t_list().
+t_list(Type) ->
+ #t{kind = ?list_tag, rep = Type, deps = get_deps(Type)}.
+
+-spec t_nonempty_list() -> t_nonempty_list().
+t_nonempty_list() ->
+ #t{kind = ?nonempty_list_tag, rep = t_any()}.
+
+-spec t_nonempty_list(raw_type()) -> t_nonempty_list().
+t_nonempty_list(Type) ->
+ #t{kind = ?nonempty_list_tag, rep = Type, deps = get_deps(Type)}.
+
+-spec t_tuple() -> t_tuple().
+t_tuple() ->
+ #t{kind = ?tuple_tag, rep = []}.
+
+-spec t_tuple([raw_type()]) -> t_tuple().
+t_tuple(Types) ->
+ #t{kind = ?tuple_tag, rep = Types, deps = unify_deps(Types)}.
+
+-spec t_union([raw_type()]) -> t_union().
+t_union(Types) ->
+ #t{kind = ?union_tag, rep = Types, deps = unify_deps(Types)}.
+
+-spec t_byte() -> t_range().
+t_byte() ->
+ t_range(t_integer_lit(0), t_integer_lit(255)).
+
+-spec t_local(type_name(), [raw_type()]) -> t_local().
+t_local(Name, Types) ->
+ Rep = {Name, Types},
+ #t{kind = ?local_tag, rep = Rep, deps = unify_deps(Types)}.
+
+-spec t_remote(module(), type_name(), [raw_type()]) -> t_remote().
+t_remote(Mod, Name, Types) ->
+ Rep = {Mod, Name, Types},
+ Dep = {Mod, Name, length(Types)},
+ #t{kind = ?remote_tag, rep = Rep, deps = add_dep(Dep, unify_deps(Types))}.
+
+-spec t_var(atom()) -> t_type_var().
+t_var(Var) ->
+ #t{kind = ?type_variable, rep = {?type_var, Var}}.
+
+-spec t_record(record_name(), [record_field_type()]) -> t_record().
+t_record(Name, Fields) ->
+ Rep = {Name, Fields},
+ Ts = [T || {_, T} <- Fields],
+ #t{kind = ?record_tag, rep = Rep, deps = unify_deps(Ts)}.
+
+-spec fields_of_t_record(t_record()) -> [record_field_type()].
+fields_of_t_record(Record) ->
+ Rep = Record#t.rep,
+ element(2, Rep).
+
+-spec t_bitstring(1 | 8) -> t_bitstring().
+t_bitstring(N) ->
+ #t{kind = ?bitstring_tag, rep = N}.
+
+-spec t_function() -> t_function().
+t_function() ->
+ #t{kind = ?function_tag}.
+
+-spec t_function([raw_type()], raw_type()) -> t_function_det().
+t_function(Types, Ret) ->
+ Rep = {Types, Ret, []},
+ #t{kind = ?function_tag, rep = Rep, deps = unify_deps([Ret|Types])}.
+
+-spec t_function([raw_type()], raw_type(), [t_constraint()]) -> t_function_det().
+t_function(Types, Ret, Constraints) ->
+ Rep = {Types, Ret, Constraints},
+ Ts = [T || {_V, T} <- Constraints],
+ #t{kind = ?function_tag, rep = Rep, deps = unify_deps([Ret|Types] ++ Ts)}.
+
+%% Accessors of representations.
+
+-spec params_of_t_function_det(t_function_det()) -> [raw_type()].
+params_of_t_function_det(#t{kind = ?function_tag, rep = {Params, _Ret, _Constraints}}) ->
+ Params.
+
+-spec ret_of_t_function_det(t_function_det()) -> raw_type().
+ret_of_t_function_det(#t{kind = ?function_tag, rep = {_Params, Ret, _Constraints}}) ->
+ Ret.
+
+-spec atom_of_t_atom_lit(t_atom_lit()) -> atom().
+atom_of_t_atom_lit(#t{kind = ?atom_lit_tag, rep = Atom}) ->
+ Atom.
+
+-spec integer_of_t_integer_lit(t_integer_lit()) -> integer().
+integer_of_t_integer_lit(#t{kind = ?integer_lit_tag, rep = Integer}) ->
+ Integer.
+
+-spec elements_type_of_t_list(t_list()) -> raw_type().
+elements_type_of_t_list(#t{kind = ?list_tag, rep = Type}) ->
+ Type.
+
+-spec elements_type_of_t_nonempty_list(t_nonempty_list()) -> raw_type().
+elements_type_of_t_nonempty_list(#t{kind = ?nonempty_list_tag, rep = Type}) ->
+ Type.
+
+-spec elements_types_of_t_tuple(t_tuple()) -> [raw_type()].
+elements_types_of_t_tuple(#t{kind = ?tuple_tag, rep = Types}) ->
+ Types.
+
+-spec elements_types_of_t_union(t_union()) -> [raw_type()].
+elements_types_of_t_union(#t{kind = ?union_tag, rep = Types}) ->
+ Types.
+
+-spec bounds_of_t_range(t_range()) -> {t_range_limit(), t_range_limit()}.
+bounds_of_t_range(#t{kind = ?range_tag, rep = Limits}) ->
+ Limits.
+
+-spec segment_size_of_bitstring(t_bitstring()) -> integer().
+segment_size_of_bitstring(#t{kind = ?bitstring_tag, rep = Sz}) ->
+ Sz.
+
+-spec is_tvar_wild_card(t_type_var()) -> boolean().
+is_tvar_wild_card(#t{kind = ?type_variable, rep = {?type_var, Var}}) ->
+ Var =:= '_'.
+
+%% Helper functions for kinds.
+
+-spec get_kind(raw_type()) -> atom().
+get_kind(Type) ->
+ Type#t.kind.
+
+%% Helper functions for dependencies.
+
+-spec get_deps(raw_type()) -> deps().
+get_deps(Type) ->
+ Type#t.deps.
+
+-spec has_deps(raw_type()) -> boolean().
+has_deps(Type) ->
+ get_deps(Type) =/= ordsets:new().
+
+-spec add_dep(dep(), deps()) -> deps().
+add_dep(Dep, Deps) ->
+ ordsets:add_element(Dep, Deps).
+
+-spec unify_deps([raw_type()]) -> deps().
+unify_deps(Types) ->
+ ordsets:union([T#t.deps || T <- Types]).
+
+%% Deal with specs.
+
+-spec retrieve_specs([cuter_cerl:cerl_attr_spec()]) -> stored_specs().
+retrieve_specs(SpecAttrs) ->
+ lists:foldl(fun process_spec_attr/2, dict:new(), SpecAttrs).
+
+-spec process_spec_attr(cuter_cerl:cerl_attr_spec(), stored_specs()) -> stored_specs().
+process_spec_attr({FA, Specs}, Processed) ->
+ Xs = [t_spec_from_form(Spec) || Spec <- Specs],
+ dict:store(FA, Xs, Processed).
+
+-spec t_spec_from_form(cuter_cerl:cerl_spec_func()) -> t_function_det().
+t_spec_from_form({type, _, 'fun', _}=Fun) ->
+ t_function_from_form(Fun);
+t_spec_from_form({type, _, 'bounded_fun', _}=Fun) ->
+ t_bounded_function_from_form(Fun).
+
+-spec find_spec(stored_spec_key(), stored_specs()) -> {'ok', stored_spec_value()} | 'error'.
+find_spec(FA, Specs) ->
+ dict:find(FA, Specs).
+
+%% Parse the spec of an MFA.
+
+-type spec_parse_reply() :: {error, has_remote_types | recursive_type}
+ | {error, unsupported_type, type_name()}
+ | {ok, erl_spec()}.
+
+-spec parse_spec(stored_spec_key(), stored_spec_value(), stored_types()) -> spec_parse_reply().
+parse_spec(FA, Spec, Types) ->
+ try parse_spec_clauses(FA, Spec, Types, []) of
+ {error, has_remote_types}=E -> E;
+ Parsed -> {ok, Parsed}
+ catch
+ throw:remote_type -> {error, has_remote_types};
+ throw:recursive_type -> {error, recursive_type};
+ throw:{unsupported, Name} -> {error, unsupported_type, Name}
+ end.
+
+
+parse_spec_clauses(_FA, [], _Types, Acc) ->
+ lists:reverse(Acc);
+parse_spec_clauses(FA, [Clause|Clauses], Types, Acc) ->
+ case has_deps(Clause) of
+ true -> {error, has_remote_types};
+ false ->
+ Visited = ordsets:add_element(FA, ordsets:new()),
+ Simplified = simplify(Clause, Types, dict:new(), Visited),
+ parse_spec_clauses(FA, Clauses, Types, [Simplified|Acc])
+ end.
+
+add_constraints_to_env([], Env) ->
+ Env;
+add_constraints_to_env([{Var, Type}|Cs], Env) ->
+ F = fun(StoredTypes, E, Visited) -> simplify(Type, StoredTypes, E, Visited) end,
+ Env1 = dict:store(Var#t.rep, F, Env),
+ add_constraints_to_env(Cs, Env1).
+
+bind_parameters([], [], Env) ->
+ Env;
+bind_parameters([P|Ps], [A|As], Env) ->
+ F = fun(StoredTypes, E, Visited) -> simplify(A, StoredTypes, E, Visited) end,
+ Env1 = dict:store(P, F, Env),
+ bind_parameters(Ps, As, Env1).
+
+-spec simplify(raw_type(), stored_types(), type_var_env(), ordsets:ordset(stored_spec_key())) -> raw_type().
+%% fun
+simplify(#t{kind = ?function_tag, rep = {Params, Ret, Constraints}}=Raw, StoredTypes, Env, Visited) ->
+ Env1 = add_constraints_to_env(Constraints, Env),
+ ParamsSimplified = [simplify(P, StoredTypes, Env1, Visited) || P <- Params],
+ RetSimplified = simplify(Ret, StoredTypes, Env1, Visited),
+ Rep = {ParamsSimplified, RetSimplified, []},
+ Raw#t{rep = Rep};
+%% tuple
+simplify(#t{kind = ?tuple_tag, rep = Types}=Raw, StoredTypes, Env, Visited) ->
+ Rep = [simplify(T, StoredTypes, Env, Visited) || T <- Types],
+ Raw#t{rep = Rep};
+%% list / nonempty_list
+simplify(#t{kind = Tag, rep = Type}=Raw, StoredTypes, Env, Visited) when Tag =:= ?list_tag; Tag =:= ?nonempty_list_tag ->
+ Rep = simplify(Type, StoredTypes, Env, Visited),
+ Raw#t{rep = Rep};
+%% union
+simplify(#t{kind = ?union_tag, rep = Types}=Raw, StoredTypes, Env, Visited) ->
+ Rep = [simplify(T, StoredTypes, Env, Visited) || T <- Types],
+ Raw#t{rep = Rep};
+%% local type
+simplify(#t{kind = ?local_tag, rep = {Name, Args}}, StoredTypes, Env, Visited) ->
+ Arity = length(Args),
+ TA = {Name, Arity},
+ case ordsets:is_element(TA, Visited) of
+ true -> throw(recursive_type);
+ false ->
+ case dict:find({type, Name, Arity}, StoredTypes) of
+ error -> throw({unsupported, Name});
+ {ok, {Type, Params}} ->
+ Env1 = bind_parameters(Params, Args, Env),
+ simplify(Type, StoredTypes, Env1, [TA|Visited])
+ end
+ end;
+%% type variable
+simplify(#t{kind = ?type_variable, rep = TVar}=T, StoredTypes, Env, Visited) ->
+ case is_tvar_wild_card(T) of
+ true -> t_any();
+ false ->
+ V = dict:fetch(TVar, Env),
+ V(StoredTypes, Env, Visited)
+ end;
+simplify(#t{kind = ?remote_tag}, _StoredTypes, _Env, _Visited) ->
+ throw(remote_type);
+%% record
+simplify(#t{kind = ?record_tag, rep = {Name, OverridenFields}}, StoredTypes, Env, Visited) ->
+ RecordDecl = dict:fetch({record, Name}, StoredTypes),
+ Fields = fields_of_t_record(RecordDecl),
+ ActualFields = replace_record_fields(Fields, OverridenFields),
+ FinalFields = [{N, simplify(T, StoredTypes, Env, Visited)} || {N, T} <- ActualFields],
+ Simplified = [T || {_, T} <- FinalFields],
+ t_tuple([t_atom_lit(Name)|Simplified]);
+%% all others
+simplify(Raw, _StoredTypes, _Env, _Visited) ->
+ Raw.
+
+-spec replace_record_fields([record_field_type()], [record_field_type()]) -> [record_field_type()].
+replace_record_fields(Fields, []) ->
+ Fields;
+replace_record_fields(Fields, [{Name, Type}|Rest]) ->
+ Replaced = lists:keyreplace(Name, 1, Fields, {Name, Type}),
+ replace_record_fields(Replaced, Rest).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_types.hrl b/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_types.hrl
new file mode 100644
index 0000000000..4172184709
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/cuter/cuter_types.hrl
@@ -0,0 +1,26 @@
+%% -*- erlang-indent-level: 2 -*-
+%%------------------------------------------------------------------------------
+
+%%====================================================================
+%% Tags for the kind of encoded types.
+%%====================================================================
+
+-define(atom_lit_tag, atom_lit).
+-define(integer_lit_tag, integer_lit).
+-define(integer_tag, integer).
+-define(nil_tag, nil).
+-define(any_tag, any).
+-define(atom_tag, atom).
+-define(float_tag, float).
+-define(tuple_tag, tuple).
+-define(list_tag, list).
+-define(nonempty_list_tag, nonempty_list).
+-define(union_tag, union).
+-define(range_tag, range).
+-define(bitstring_tag, bitstring).
+-define(neg_inf, neg_inf).
+-define(pos_inf, pos_inf).
+-define(remote_tag, remote).
+-define(local_tag, local).
+-define(record_tag, record).
+-define(function_tag, function).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/dict/dict_use.erl b/lib/dialyzer/test/opaque_SUITE_data/src/dict/dict_use.erl
index 8a2cd86f43..a4cec065ab 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/dict/dict_use.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/dict/dict_use.erl
@@ -24,7 +24,7 @@ ok3() ->
ok4() ->
dict:fetch(foo, dict:new()).
-ok5() -> % this is OK since some_mod:new/0 might be returning a dict()
+ok5() -> % this is OK since some_mod:new/0 might be returning a dict:dict()
dict:fetch(foo, some_mod:new()).
ok6() ->
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/ets/ets_use.erl b/lib/dialyzer/test/opaque_SUITE_data/src/ets/ets_use.erl
index d65af0af4e..593d9a669d 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/ets/ets_use.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/ets/ets_use.erl
@@ -1,5 +1,5 @@
-module(ets_use).
--export([t1/0, t2/0]).
+-export([t1/0, t2/0, t3/0, t4/0]).
t1() ->
case n() of
@@ -13,4 +13,10 @@ t2() ->
T when is_atom(T) -> atm
end.
-n() -> ets:new(n, [named_table]).
+t3() ->
+ is_atom(n()). % no warning since atom() is possible
+
+t4() ->
+ is_integer(n()). % opaque warning since ets:tid() is opaque
+
+n() -> ets:new(n, [named_table]). % -> atom() | ets:tid()
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/ewgi/ewgi.hrl b/lib/dialyzer/test/opaque_SUITE_data/src/ewgi/ewgi.hrl
index 0b98f550f1..5cbc79f948 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/ewgi/ewgi.hrl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/ewgi/ewgi.hrl
@@ -28,7 +28,7 @@
%% @type bag() = gb_tree()
-ifdef(HAS_GB_TREE_SPEC).
--type bag() :: gb_tree().
+-type bag() :: gb_trees:tree().
-else.
-type bag() :: {non_neg_integer(), {any(), any(), any(), any()} | 'nil'}.
-endif.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/ewgi2/ewgi.hrl b/lib/dialyzer/test/opaque_SUITE_data/src/ewgi2/ewgi.hrl
index 5da8ff0ecf..d8e15cb081 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/ewgi2/ewgi.hrl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/ewgi2/ewgi.hrl
@@ -29,7 +29,7 @@
%% @type bag() = gb_tree()
-ifdef(HAS_GB_TREE_SPEC).
--type bag() :: gb_tree().
+-type bag() :: gb_trees:tree().
-else.
-type bag() :: {non_neg_integer(), {any(), any(), any(), any()} | 'nil'}.
-endif.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/gb_sets/gb_sets_rec.erl b/lib/dialyzer/test/opaque_SUITE_data/src/gb_sets/gb_sets_rec.erl
index 008b0a486a..7c34b01c2d 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/gb_sets/gb_sets_rec.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/gb_sets/gb_sets_rec.erl
@@ -12,12 +12,12 @@
-export([new/0, get_g/1]).
--record(rec, {g :: gb_set()}).
+-record(rec, {g :: gb_sets:set()}).
-spec new() -> #rec{}.
new() ->
#rec{g = gb_sets:empty()}.
--spec get_g(#rec{}) -> gb_set().
+-spec get_g(#rec{}) -> gb_sets:set().
get_g(R) ->
R#rec.g.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/inf_loop1.erl b/lib/dialyzer/test/opaque_SUITE_data/src/inf_loop1.erl
index 0dff16cf14..3275736e75 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/inf_loop1.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/inf_loop1.erl
@@ -1,7 +1,7 @@
%% -*- erlang-indent-level: 2 -*-
%%----------------------------------------------------------------------------
%% Non-sensical (i.e., stripped-down) program that sends the analysis
-%% into an infinite loop. The #we.es field was originally a gb_tree()
+%% into an infinite loop. The #we.es field was originally a gb_trees:tree()
%% but the programmer declared it as an array in order to change it to
%% that data type instead. In the file, there are two calls to function
%% gb_trees:get/2 which seem to be the ones responsible for sending the
@@ -14,7 +14,7 @@
-export([command/1]).
-record(we, {id,
- es = array:new() :: array(),
+ es = array:new() :: array:array(),
vp,
mirror = none}).
-record(edge, {vs,ve,a = none,b = none,lf,rf,ltpr,ltsu,rtpr,rtsu}).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/inf_loop2.erl b/lib/dialyzer/test/opaque_SUITE_data/src/inf_loop2.erl
new file mode 100644
index 0000000000..3787fc6750
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/inf_loop2.erl
@@ -0,0 +1,175 @@
+%% -*- erlang-indent-level: 2 -*-
+%%----------------------------------------------------------------------------
+%% Copy of inf_loop1.erl, where the calls mentioned below have been
+%% restored.
+
+%% Non-sensical (i.e., stripped-down) program that sends the analysis
+%% into an infinite loop. The #we.es field was originally a gb_trees:tree()
+%% but the programmer declared it as an array in order to change it to
+%% that data type instead. In the file, there are two calls to function
+%% gb_trees:get/2 which seem to be the ones responsible for sending the
+%% analysis into an infinite loop. Currently, these calls are marked and
+%% have been changed to gbee_trees:get/2 in order to be able to see that
+%% the analysis works if these two calls are taken out of the picture.
+%%----------------------------------------------------------------------------
+-module(inf_loop2).
+
+-export([command/1]).
+
+-record(we, {id,
+ es = array:new() :: array:array(),
+ vp,
+ mirror = none}).
+-record(edge, {vs,ve,a = none,b = none,lf,rf,ltpr,ltsu,rtpr,rtsu}).
+
+command(St) ->
+ State = drag_mode(offset_region),
+ SetupSt = wings_sel_conv:more(St),
+ Tvs = wings_sel:fold(fun(Faces, #we{id = Id} = We, Acc) ->
+ FaceRegions = wings_sel:face_regions(Faces, We),
+ {AllVs0,VsData} =
+ collect_offset_regions_data(FaceRegions, We, [], []),
+ AllVs = ordsets:from_list(AllVs0),
+ [{Id,{AllVs,offset_regions_fun(VsData, State)}}|Acc]
+ end,
+ [],
+ SetupSt),
+ wings_drag:setup(Tvs, 42, [], St).
+
+drag_mode(Type) ->
+ {Mode,Norm} = wings_pref:get_value(Type, {average,loop}),
+ {Type,Mode,Norm}.
+
+collect_offset_regions_data([Faces|Regions], We, AllVs, VsData) ->
+ {FaceNormTab,OuterEdges,RegVs} =
+ some_fake_module:faces_data_0(Faces, We, [], [], []),
+ {LoopNorm,LoopVsData,LoopVs} =
+ offset_regions_loop_data(OuterEdges, Faces, We, FaceNormTab),
+ Vs = RegVs -- LoopVs,
+ RegVsData = vertex_normals(Vs, FaceNormTab, We, LoopVsData),
+ collect_offset_regions_data(Regions, We, RegVs ++ AllVs,
+ [{LoopNorm,RegVsData}|VsData]);
+collect_offset_regions_data([], _, AllVs, VsData) ->
+ {AllVs,VsData}.
+
+offset_regions_loop_data(Edges, Faces, We, FNtab) ->
+ EdgeSet = gb_sets:from_list(Edges),
+ offset_loop_data_0(EdgeSet, Faces, We, FNtab, [], [], []).
+
+offset_loop_data_0(EdgeSet0, Faces, We, FNtab, LNorms, VData0, Vs0) ->
+ case gb_sets:is_empty(EdgeSet0) of
+ false ->
+ {Edge,EdgeSet1} = gb_sets:take_smallest(EdgeSet0),
+ {EdgeSet,VData,Links,LoopNorm,Vs} =
+ offset_loop_data_1(Edge, EdgeSet1, Faces, We, FNtab, VData0, Vs0),
+ offset_loop_data_0(EdgeSet, Faces, We, FNtab,
+ [{Links,LoopNorm}|LNorms], VData, Vs);
+ true ->
+ AvgLoopNorm = average_loop_norm(LNorms),
+ {AvgLoopNorm,VData0,Vs0}
+ end.
+
+offset_loop_data_1(Edge, EdgeSet, _Faces,
+ #we{es = Etab, vp = Vtab} = We, FNtab, VData, Vs) ->
+ #edge{vs = Va, ve = Vb, lf = Lf, ltsu = NextLeft} = gb_trees:get(Edge, Etab),
+ VposA = gb_trees:get(Va, Vtab),
+ VposB = gb_trees:get(Vb, Vtab),
+ VDir = e3d_vec:sub(VposB, VposA),
+ FNorm = wings_face:normal(Lf, We),
+ EdgeData = gb_trees:get(NextLeft, Etab),
+ offset_loop_data_2(NextLeft, EdgeData, Va, VposA, Lf, Edge, We, FNtab,
+ EdgeSet, VDir, [], [FNorm], VData, [], Vs, 0).
+
+offset_loop_data_2(CurE, #edge{vs = Va, ve = Vb, lf = PrevFace,
+ rtsu = NextEdge, ltsu = IfCurIsMember},
+ Vb, VposB, PrevFace, LastE,
+ #we{mirror = M} = We,
+ FNtab, EdgeSet0, VDir, EDir0, VNorms0, VData0, VPs0, Vs0,
+ Links) ->
+ Mirror = M == PrevFace,
+ offset_loop_is_member(Mirror, Vb, Va, VposB, CurE, IfCurIsMember, VNorms0,
+ NextEdge, EdgeSet0, VDir, EDir0, FNtab, PrevFace,
+ LastE, We, VData0, VPs0, Vs0, Links).
+
+offset_loop_is_member(Mirror, V1, V2, Vpos1, CurE, NextE, VNorms0, NEdge,
+ EdgeSet0, VDir, EDir0, FNtab, PFace, LastE, We,
+ VData0, VPs0, Vs0, Links) ->
+ #we{es = Etab, vp = Vtab} = We,
+ Vpos2 = gb_trees:get(V2, Vtab),
+ Dir = e3d_vec:sub(Vpos2, Vpos1),
+ NextVDir = e3d_vec:neg(Dir),
+ EdgeSet = gb_sets:delete(CurE, EdgeSet0),
+ EdgeData = gb_trees:get(NextE, Etab), %% HERE
+ [FNorm|_] = VNorms0,
+ VData = offset_loop_data_3(Mirror, V1, Vpos1, VNorms0, NEdge, VDir,
+ Dir, EDir0, FNtab, We, VData0),
+ VPs = [Vpos1|VPs0],
+ Vs = [V1|Vs0],
+ offset_loop_data_2(NextE, EdgeData, V2, Vpos2, PFace, LastE, We, FNtab,
+ EdgeSet, NextVDir, [], [FNorm], VData, VPs, Vs, Links + 1).
+
+offset_loop_data_3(false, V, Vpos, VNorms0, NextEdge,
+ VDir, Dir, EDir0, FNtab, We, VData0) ->
+ #we{es = Etab} = We,
+ VNorm = e3d_vec:norm(e3d_vec:add(VNorms0)),
+ NV = wings_vertex:other(V, gb_trees:get(NextEdge, Etab)), %% HERE
+ ANorm = vertex_normal(NV, FNtab, We),
+ EDir = some_fake_module:average_edge_dir(VNorm, VDir, Dir, EDir0),
+ AvgDir = some_fake_module:evaluate_vdata(VDir, Dir, VNorm),
+ ScaledDir = some_fake_module:along_edge_scale_factor(VDir, Dir, EDir, ANorm),
+ [{V,{Vpos,AvgDir,EDir,ScaledDir}}|VData0].
+
+average_loop_norm([{_,LNorms}]) ->
+ e3d_vec:norm(LNorms);
+average_loop_norm([{LinksA,LNormA},{LinksB,LNormB}]) ->
+ case LinksA < LinksB of
+ true ->
+ e3d_vec:norm(e3d_vec:add(e3d_vec:neg(LNormA), LNormB));
+ false ->
+ e3d_vec:norm(e3d_vec:add(e3d_vec:neg(LNormB), LNormA))
+ end;
+average_loop_norm(LNorms) ->
+ LoopNorms = [Norm || {_,Norm} <- LNorms],
+ e3d_vec:norm(e3d_vec:neg(e3d_vec:add(LoopNorms))).
+
+vertex_normals([V|Vs], FaceNormTab, #we{vp = Vtab, mirror = M} = We, Acc) ->
+ FaceNorms =
+ wings_vertex:fold(fun(_, Face, _, A) when Face == M ->
+ [e3d_vec:neg(wings_face:normal(M, We))|A];
+ (_, Face, _, A) ->
+ [gb_trees:get(Face, FaceNormTab)|A]
+ end, [], V, We),
+ VNorm = e3d_vec:norm(e3d_vec:add(FaceNorms)),
+ Vpos = gb_trees:get(V, Vtab),
+ vertex_normals(Vs, FaceNormTab, We, [{V,{Vpos,VNorm}}|Acc]);
+vertex_normals([], _, _, Acc) ->
+ Acc.
+
+vertex_normal(V, FaceNormTab, #we{mirror = M} = We) ->
+ wings_vertex:fold(fun(_, Face, _, A) when Face == M ->
+ [e3d_vec:neg(wings_face:normal(Face, We))|A];
+ (_, Face, _, A) ->
+ N = gb_trees:get(Face, FaceNormTab),
+ case e3d_vec:is_zero(N) of
+ true -> A;
+ false -> [N|A]
+ end
+ end, [], V, We).
+
+offset_regions_fun(OffsetData, {_,Solution,_} = State) ->
+ fun(new_mode_data, {NewState,_}) ->
+ offset_regions_fun(OffsetData, NewState);
+ ([Dist,_,_,Bump|_], A) ->
+ lists:foldl(fun({LoopNormal,VsData}, VsAcc0) ->
+ lists:foldl(fun({V,{Vpos0,VNorm}}, VsAcc) ->
+ [{V,Vpos0}|VsAcc];
+ ({V,{Vpos0,Dir,EDir,ScaledEDir}}, VsAcc) ->
+ Vec = case Solution of
+ average -> Dir;
+ along_edges -> EDir;
+ scaled -> ScaledEDir
+ end,
+ [{V,Vpos0}|VsAcc]
+ end, VsAcc0, VsData)
+ end, A, OffsetData)
+ end.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/modules/opaque_digraph.erl b/lib/dialyzer/test/opaque_SUITE_data/src/modules/opaque_digraph.erl
new file mode 100644
index 0000000000..27d937277e
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/modules/opaque_digraph.erl
@@ -0,0 +1,655 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%% The Erlang scanner. All types are opaque, which puts some stress
+%%% on Dialyzer.
+
+-module(opaque_digraph).
+
+-export([new/0, new/1, delete/1, info/1]).
+
+-export([add_vertex/1, add_vertex/2, add_vertex/3]).
+-export([del_vertex/2, del_vertices/2]).
+-export([vertex/2, no_vertices/1, vertices/1]).
+-export([source_vertices/1, sink_vertices/1]).
+
+-export([add_edge/3, add_edge/4, add_edge/5]).
+-export([del_edge/2, del_edges/2, del_path/3]).
+-export([edge/2, no_edges/1, edges/1]).
+
+-export([out_neighbours/2, in_neighbours/2]).
+-export([out_edges/2, in_edges/2, edges/2]).
+-export([out_degree/2, in_degree/2]).
+-export([get_path/3, get_cycle/2]).
+
+-export([get_short_path/3, get_short_cycle/2]).
+
+-export_type([local_digraph/0, d_type/0, vertex/0]).
+
+-record(digraph, {vtab = notable :: ets:tab(),
+ etab = notable :: ets:tab(),
+ ntab = notable :: ets:tab(),
+ cyclic = true :: boolean()}).
+
+-opaque local_digraph() :: #digraph{}.
+
+-export_type([edge/0, label/0, add_edge_err_rsn/0,
+ d_protection/0, d_cyclicity/0]).
+
+-opaque edge() :: term().
+-opaque label() :: term().
+-opaque vertex() :: term().
+
+-opaque add_edge_err_rsn() :: {'bad_edge', Path :: [vertex()]}
+ | {'bad_vertex', V :: vertex()}.
+
+%%
+%% Type is a list of
+%% protected | private
+%% acyclic | cyclic
+%%
+%% default is [cyclic,protected]
+%%
+-opaque d_protection() :: 'private' | 'protected'.
+-opaque d_cyclicity() :: 'acyclic' | 'cyclic'.
+-opaque d_type() :: d_cyclicity() | d_protection().
+
+-spec new() -> local_digraph().
+
+new() -> new([]).
+
+-spec new(Type) -> local_digraph() when
+ Type :: [d_type()].
+
+new(Type) ->
+ case check_type(Type, protected, []) of
+ {Access, Ts} ->
+ V = ets:new(vertices, [set, Access]),
+ E = ets:new(edges, [set, Access]),
+ N = ets:new(neighbours, [bag, Access]),
+ ets:insert(N, [{'$vid', 0}, {'$eid', 0}]),
+ set_type(Ts, #digraph{vtab=V, etab=E, ntab=N});
+ error ->
+ erlang:error(badarg)
+ end.
+
+%%
+%% Check type of graph
+%%
+%-spec check_type([d_type()], d_protection(), [{'cyclic', boolean()}]) ->
+% {d_protection(), [{'cyclic', boolean()}]}.
+
+check_type([acyclic|Ts], A, L) ->
+ check_type(Ts, A,[{cyclic,false} | L]);
+check_type([cyclic | Ts], A, L) ->
+ check_type(Ts, A, [{cyclic,true} | L]);
+check_type([protected | Ts], _, L) ->
+ check_type(Ts, protected, L);
+check_type([private | Ts], _, L) ->
+ check_type(Ts, private, L);
+check_type([], A, L) -> {A, L};
+check_type(_, _, _) -> error.
+
+%%
+%% Set graph type
+%%
+-spec set_type([{'cyclic', boolean()}], local_digraph()) -> local_digraph().
+
+set_type([{cyclic,V} | Ks], G) ->
+ set_type(Ks, G#digraph{cyclic = V});
+set_type([], G) -> G.
+
+
+%% Data access functions
+
+-spec delete(G) -> 'true' when
+ G :: local_digraph().
+
+delete(G) ->
+ ets:delete(G#digraph.vtab),
+ ets:delete(G#digraph.etab),
+ ets:delete(G#digraph.ntab).
+
+-spec info(G) -> InfoList when
+ G :: local_digraph(),
+ InfoList :: [{'cyclicity', Cyclicity :: d_cyclicity()} |
+ {'memory', NoWords :: non_neg_integer()} |
+ {'protection', Protection :: d_protection()}].
+
+info(G) ->
+ VT = G#digraph.vtab,
+ ET = G#digraph.etab,
+ NT = G#digraph.ntab,
+ Cyclicity = case G#digraph.cyclic of
+ true -> cyclic;
+ false -> acyclic
+ end,
+ Protection = ets:info(VT, protection),
+ Memory = ets:info(VT, memory) + ets:info(ET, memory) + ets:info(NT, memory),
+ [{cyclicity, Cyclicity}, {memory, Memory}, {protection, Protection}].
+
+-spec add_vertex(G) -> vertex() when
+ G :: local_digraph().
+
+add_vertex(G) ->
+ do_add_vertex({new_vertex_id(G), []}, G).
+
+-spec add_vertex(G, V) -> vertex() when
+ G :: local_digraph(),
+ V :: vertex().
+
+add_vertex(G, V) ->
+ do_add_vertex({V, []}, G).
+
+-spec add_vertex(G, V, Label) -> vertex() when
+ G :: local_digraph(),
+ V :: vertex(),
+ Label :: label().
+
+add_vertex(G, V, D) ->
+ do_add_vertex({V, D}, G).
+
+-spec del_vertex(G, V) -> 'true' when
+ G :: local_digraph(),
+ V :: vertex().
+
+del_vertex(G, V) ->
+ do_del_vertex(V, G).
+
+-spec del_vertices(G, Vertices) -> 'true' when
+ G :: local_digraph(),
+ Vertices :: [vertex()].
+
+del_vertices(G, Vs) ->
+ do_del_vertices(Vs, G).
+
+-spec vertex(G, V) -> {V, Label} | 'false' when
+ G :: local_digraph(),
+ V :: vertex(),
+ Label :: label().
+
+vertex(G, V) ->
+ case ets:lookup(G#digraph.vtab, V) of
+ [] -> false;
+ [Vertex] -> Vertex
+ end.
+
+-spec no_vertices(G) -> non_neg_integer() when
+ G :: local_digraph().
+
+no_vertices(G) ->
+ ets:info(G#digraph.vtab, size).
+
+-spec vertices(G) -> Vertices when
+ G :: local_digraph(),
+ Vertices :: [vertex()].
+
+vertices(G) ->
+ ets:select(G#digraph.vtab, [{{'$1', '_'}, [], ['$1']}]).
+
+-spec source_vertices(local_digraph()) -> [vertex()].
+
+source_vertices(G) ->
+ collect_vertices(G, in).
+
+-spec sink_vertices(local_digraph()) -> [vertex()].
+
+sink_vertices(G) ->
+ collect_vertices(G, out).
+
+-spec in_degree(G, V) -> non_neg_integer() when
+ G :: local_digraph(),
+ V :: vertex().
+
+in_degree(G, V) ->
+ length(ets:lookup(G#digraph.ntab, {in, V})).
+
+-spec in_neighbours(G, V) -> Vertex when
+ G :: local_digraph(),
+ V :: vertex(),
+ Vertex :: [vertex()].
+
+in_neighbours(G, V) ->
+ ET = G#digraph.etab,
+ NT = G#digraph.ntab,
+ collect_elems(ets:lookup(NT, {in, V}), ET, 2).
+
+-spec in_edges(G, V) -> Edges when
+ G :: local_digraph(),
+ V :: vertex(),
+ Edges :: [edge()].
+
+in_edges(G, V) ->
+ ets:select(G#digraph.ntab, [{{{in, V}, '$1'}, [], ['$1']}]).
+
+-spec out_degree(G, V) -> non_neg_integer() when
+ G :: local_digraph(),
+ V :: vertex().
+
+out_degree(G, V) ->
+ length(ets:lookup(G#digraph.ntab, {out, V})).
+
+-spec out_neighbours(G, V) -> Vertices when
+ G :: local_digraph(),
+ V :: vertex(),
+ Vertices :: [vertex()].
+
+out_neighbours(G, V) ->
+ ET = G#digraph.etab,
+ NT = G#digraph.ntab,
+ collect_elems(ets:lookup(NT, {out, V}), ET, 3).
+
+-spec out_edges(G, V) -> Edges when
+ G :: local_digraph(),
+ V :: vertex(),
+ Edges :: [edge()].
+
+out_edges(G, V) ->
+ ets:select(G#digraph.ntab, [{{{out, V}, '$1'}, [], ['$1']}]).
+
+-spec add_edge(G, V1, V2) -> edge() | {'error', add_edge_err_rsn()} when
+ G :: local_digraph(),
+ V1 :: vertex(),
+ V2 :: vertex().
+
+add_edge(G, V1, V2) ->
+ do_add_edge({new_edge_id(G), V1, V2, []}, G).
+
+-spec add_edge(G, V1, V2, Label) -> edge() | {'error', add_edge_err_rsn()} when
+ G :: local_digraph(),
+ V1 :: vertex(),
+ V2 :: vertex(),
+ Label :: label().
+
+add_edge(G, V1, V2, D) ->
+ do_add_edge({new_edge_id(G), V1, V2, D}, G).
+
+-spec add_edge(G, E, V1, V2, Label) -> edge() | {'error', add_edge_err_rsn()} when
+ G :: local_digraph(),
+ E :: edge(),
+ V1 :: vertex(),
+ V2 :: vertex(),
+ Label :: label().
+
+add_edge(G, E, V1, V2, D) ->
+ do_add_edge({E, V1, V2, D}, G).
+
+-spec del_edge(G, E) -> 'true' when
+ G :: local_digraph(),
+ E :: edge().
+
+del_edge(G, E) ->
+ do_del_edges([E], G).
+
+-spec del_edges(G, Edges) -> 'true' when
+ G :: local_digraph(),
+ Edges :: [edge()].
+
+del_edges(G, Es) ->
+ do_del_edges(Es, G).
+
+-spec no_edges(G) -> non_neg_integer() when
+ G :: local_digraph().
+
+no_edges(G) ->
+ ets:info(G#digraph.etab, size).
+
+-spec edges(G) -> Edges when
+ G :: local_digraph(),
+ Edges :: [edge()].
+
+edges(G) ->
+ ets:select(G#digraph.etab, [{{'$1', '_', '_', '_'}, [], ['$1']}]).
+
+-spec edges(G, V) -> Edges when
+ G :: local_digraph(),
+ V :: vertex(),
+ Edges :: [edge()].
+
+edges(G, V) ->
+ ets:select(G#digraph.ntab, [{{{out, V},'$1'}, [], ['$1']},
+ {{{in, V}, '$1'}, [], ['$1']}]).
+
+-spec edge(G, E) -> {E, V1, V2, Label} | 'false' when
+ G :: local_digraph(),
+ E :: edge(),
+ V1 :: vertex(),
+ V2 :: vertex(),
+ Label :: label().
+
+edge(G, E) ->
+ case ets:lookup(G#digraph.etab,E) of
+ [] -> false;
+ [Edge] -> Edge
+ end.
+
+%%
+%% Generate a "unique" edge identifier (relative to this graph)
+%%
+-spec new_edge_id(local_digraph()) -> edge().
+
+new_edge_id(G) ->
+ NT = G#digraph.ntab,
+ [{'$eid', K}] = ets:lookup(NT, '$eid'),
+ true = ets:delete(NT, '$eid'),
+ true = ets:insert(NT, {'$eid', K+1}),
+ ['$e' | K].
+
+%%
+%% Generate a "unique" vertex identifier (relative to this graph)
+%%
+-spec new_vertex_id(local_digraph()) -> vertex().
+
+new_vertex_id(G) ->
+ NT = G#digraph.ntab,
+ [{'$vid', K}] = ets:lookup(NT, '$vid'),
+ true = ets:delete(NT, '$vid'),
+ true = ets:insert(NT, {'$vid', K+1}),
+ ['$v' | K].
+
+%%
+%% Collect elements for a index in a tuple
+%%
+collect_elems(Keys, Table, Index) ->
+ collect_elems(Keys, Table, Index, []).
+
+collect_elems([{_,Key}|Keys], Table, Index, Acc) ->
+ collect_elems(Keys, Table, Index,
+ [ets:lookup_element(Table, Key, Index)|Acc]);
+collect_elems([], _, _, Acc) -> Acc.
+
+-spec do_add_vertex({vertex(), label()}, local_digraph()) -> vertex().
+
+do_add_vertex({V, _Label} = VL, G) ->
+ ets:insert(G#digraph.vtab, VL),
+ V.
+
+%%
+%% Collect either source or sink vertices.
+%%
+collect_vertices(G, Type) ->
+ Vs = vertices(G),
+ lists:foldl(fun(V, A) ->
+ case ets:member(G#digraph.ntab, {Type, V}) of
+ true -> A;
+ false -> [V|A]
+ end
+ end, [], Vs).
+
+%%
+%% Delete vertices
+%%
+do_del_vertices([V | Vs], G) ->
+ do_del_vertex(V, G),
+ do_del_vertices(Vs, G);
+do_del_vertices([], #digraph{}) -> true.
+
+do_del_vertex(V, G) ->
+ do_del_nedges(ets:lookup(G#digraph.ntab, {in, V}), G),
+ do_del_nedges(ets:lookup(G#digraph.ntab, {out, V}), G),
+ ets:delete(G#digraph.vtab, V).
+
+do_del_nedges([{_, E}|Ns], G) ->
+ case ets:lookup(G#digraph.etab, E) of
+ [{E, V1, V2, _}] ->
+ do_del_edge(E, V1, V2, G),
+ do_del_nedges(Ns, G);
+ [] -> % cannot happen
+ do_del_nedges(Ns, G)
+ end;
+do_del_nedges([], #digraph{}) -> true.
+
+%%
+%% Delete edges
+%%
+do_del_edges([E|Es], G) ->
+ case ets:lookup(G#digraph.etab, E) of
+ [{E,V1,V2,_}] ->
+ do_del_edge(E,V1,V2,G),
+ do_del_edges(Es, G);
+ [] ->
+ do_del_edges(Es, G)
+ end;
+do_del_edges([], #digraph{}) -> true.
+
+do_del_edge(E, V1, V2, G) ->
+ ets:select_delete(G#digraph.ntab, [{{{in, V2}, E}, [], [true]},
+ {{{out,V1}, E}, [], [true]}]),
+ ets:delete(G#digraph.etab, E).
+
+-spec rm_edges([vertex(),...], local_digraph()) -> 'true'.
+
+rm_edges([V1, V2|Vs], G) ->
+ rm_edge(V1, V2, G),
+ rm_edges([V2|Vs], G);
+rm_edges(_, _) -> true.
+
+-spec rm_edge(vertex(), vertex(), local_digraph()) -> 'ok'.
+
+rm_edge(V1, V2, G) ->
+ Es = out_edges(G, V1),
+ rm_edge_0(Es, V1, V2, G).
+
+rm_edge_0([E|Es], V1, V2, G) ->
+ case ets:lookup(G#digraph.etab, E) of
+ [{E, V1, V2, _}] ->
+ do_del_edge(E, V1, V2, G),
+ rm_edge_0(Es, V1, V2, G);
+ _ ->
+ rm_edge_0(Es, V1, V2, G)
+ end;
+rm_edge_0([], _, _, #digraph{}) -> ok.
+
+%%
+%% Check that endpoints exist
+%%
+-spec do_add_edge({edge(), vertex(), vertex(), label()}, local_digraph()) ->
+ edge() | {'error', add_edge_err_rsn()}.
+
+do_add_edge({E, V1, V2, Label}, G) ->
+ case ets:member(G#digraph.vtab, V1) of
+ false -> {error, {bad_vertex, V1}};
+ true ->
+ case ets:member(G#digraph.vtab, V2) of
+ false -> {error, {bad_vertex, V2}};
+ true ->
+ case other_edge_exists(G, E, V1, V2) of
+ true -> {error, {bad_edge, [V1, V2]}};
+ false when G#digraph.cyclic =:= false ->
+ acyclic_add_edge(E, V1, V2, Label, G);
+ false ->
+ do_insert_edge(E, V1, V2, Label, G)
+ end
+ end
+ end.
+
+other_edge_exists(#digraph{etab = ET}, E, V1, V2) ->
+ case ets:lookup(ET, E) of
+ [{E, Vert1, Vert2, _}] when Vert1 =/= V1; Vert2 =/= V2 ->
+ true;
+ _ ->
+ false
+ end.
+
+-spec do_insert_edge(edge(), vertex(), vertex(), label(), local_digraph()) -> edge().
+
+do_insert_edge(E, V1, V2, Label, #digraph{ntab=NT, etab=ET}) ->
+ ets:insert(NT, [{{out, V1}, E}, {{in, V2}, E}]),
+ ets:insert(ET, {E, V1, V2, Label}),
+ E.
+
+-spec acyclic_add_edge(edge(), vertex(), vertex(), label(), local_digraph()) ->
+ edge() | {'error', {'bad_edge', [vertex()]}}.
+
+acyclic_add_edge(_E, V1, V2, _L, _G) when V1 =:= V2 ->
+ {error, {bad_edge, [V1, V2]}};
+acyclic_add_edge(E, V1, V2, Label, G) ->
+ case get_path(G, V2, V1) of
+ false -> do_insert_edge(E, V1, V2, Label, G);
+ Path -> {error, {bad_edge, Path}}
+ end.
+
+%%
+%% Delete all paths from vertex V1 to vertex V2
+%%
+
+-spec del_path(G, V1, V2) -> 'true' when
+ G :: local_digraph(),
+ V1 :: vertex(),
+ V2 :: vertex().
+
+del_path(G, V1, V2) ->
+ case get_path(G, V1, V2) of
+ false -> true;
+ Path ->
+ rm_edges(Path, G),
+ del_path(G, V1, V2)
+ end.
+
+%%
+%% Find a cycle through V
+%% return the cycle as list of vertices [V ... V]
+%% if no cycle exists false is returned
+%% if only a cycle of length one exists it will be
+%% returned as [V] but only after longer cycles have
+%% been searched.
+%%
+
+-spec get_cycle(G, V) -> Vertices | 'false' when
+ G :: local_digraph(),
+ V :: vertex(),
+ Vertices :: [vertex(),...].
+
+get_cycle(G, V) ->
+ case one_path(out_neighbours(G, V), V, [], [V], [V], 2, G, 1) of
+ false ->
+ case lists:member(V, out_neighbours(G, V)) of
+ true -> [V];
+ false -> false
+ end;
+ Vs -> Vs
+ end.
+
+%%
+%% Find a path from V1 to V2
+%% return the path as list of vertices [V1 ... V2]
+%% if no path exists false is returned
+%%
+
+-spec get_path(G, V1, V2) -> Vertices | 'false' when
+ G :: local_digraph(),
+ V1 :: vertex(),
+ V2 :: vertex(),
+ Vertices :: [vertex(),...].
+
+get_path(G, V1, V2) ->
+ one_path(out_neighbours(G, V1), V2, [], [V1], [V1], 1, G, 1).
+
+%%
+%% prune_short_path (evaluate conditions on path)
+%% short : if path is too short
+%% ok : if path is ok
+%%
+prune_short_path(Counter, Min) when Counter < Min ->
+ short;
+prune_short_path(_Counter, _Min) ->
+ ok.
+
+one_path([W|Ws], W, Cont, Xs, Ps, Prune, G, Counter) ->
+ case prune_short_path(Counter, Prune) of
+ short -> one_path(Ws, W, Cont, Xs, Ps, Prune, G, Counter);
+ ok -> lists:reverse([W|Ps])
+ end;
+one_path([V|Vs], W, Cont, Xs, Ps, Prune, G, Counter) ->
+ case lists:member(V, Xs) of
+ true -> one_path(Vs, W, Cont, Xs, Ps, Prune, G, Counter);
+ false -> one_path(out_neighbours(G, V), W,
+ [{Vs,Ps} | Cont], [V|Xs], [V|Ps],
+ Prune, G, Counter+1)
+ end;
+one_path([], W, [{Vs,Ps}|Cont], Xs, _, Prune, G, Counter) ->
+ one_path(Vs, W, Cont, Xs, Ps, Prune, G, Counter-1);
+one_path([], _, [], _, _, _, _, _Counter) -> false.
+
+%%
+%% Like get_cycle/2, but a cycle of length one is preferred.
+%%
+
+-spec get_short_cycle(G, V) -> Vertices | 'false' when
+ G :: local_digraph(),
+ V :: vertex(),
+ Vertices :: [vertex(),...].
+
+get_short_cycle(G, V) ->
+ get_short_path(G, V, V).
+
+%%
+%% Like get_path/3, but using a breadth-first search makes it possible
+%% to find a short path.
+%%
+
+-spec get_short_path(G, V1, V2) -> Vertices | 'false' when
+ G :: local_digraph(),
+ V1 :: vertex(),
+ V2 :: vertex(),
+ Vertices :: [vertex(),...].
+
+get_short_path(G, V1, V2) ->
+ T = new(),
+ add_vertex(T, V1),
+ Q = queue:new(),
+ Q1 = queue_out_neighbours(V1, G, Q),
+ L = spath(Q1, G, V2, T),
+ delete(T),
+ L.
+
+spath(Q, G, Sink, T) ->
+ case queue:out(Q) of
+ {{value, E}, Q1} ->
+ {_E, V1, V2, _Label} = edge(G, E),
+ if
+ Sink =:= V2 ->
+ follow_path(V1, T, [V2]);
+ true ->
+ case vertex(T, V2) of
+ false ->
+ add_vertex(T, V2),
+ add_edge(T, V2, V1),
+ NQ = queue_out_neighbours(V2, G, Q1),
+ spath(NQ, G, Sink, T);
+ _V ->
+ spath(Q1, G, Sink, T)
+ end
+ end;
+ {empty, _Q1} ->
+ false
+ end.
+
+follow_path(V, T, P) ->
+ P1 = [V | P],
+ case out_neighbours(T, V) of
+ [N] ->
+ follow_path(N, T, P1);
+ [] ->
+ P1
+ end.
+
+queue_out_neighbours(V, G, Q0) ->
+ lists:foldl(fun(E, Q) -> queue:in(E, Q) end, Q0, out_edges(G, V)).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/modules/opaque_erl_scan.erl b/lib/dialyzer/test/opaque_SUITE_data/src/modules/opaque_erl_scan.erl
new file mode 100644
index 0000000000..12506f5b4c
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/modules/opaque_erl_scan.erl
@@ -0,0 +1,1301 @@
+%%
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+
+%%% The Erlang scanner. All types are opaque, which puts some stress
+%%% on Dialyzer.
+
+-module(opaque_erl_scan).
+
+%%% External exports
+
+-export([string/1,string/2,string/3,tokens/3,tokens/4,
+ format_error/1,reserved_word/1,
+ token_info/1,token_info/2,
+ attributes_info/1,attributes_info/2,set_attribute/3]).
+
+%%% Private
+-export([continuation_location/1]).
+
+-export_type([error_info/0,
+ line/0,
+ location/0,
+ options/0,
+ return_cont/0,
+ token/0,
+ tokens_result/0]).
+
+%%%
+%%% Defines and type definitions
+%%%
+
+-define(COLUMN(C), (is_integer(C) andalso C >= 1)).
+%% Line numbers less than zero have always been allowed:
+-define(ALINE(L), is_integer(L)).
+-define(STRING(S), is_list(S)).
+-define(RESWORDFUN(F), is_function(F, 1)).
+-define(SETATTRFUN(F), is_function(F, 1)).
+
+-export_type([category/0, column/0, resword_fun/0, option/0, symbol/0,
+ info_line/0, attributes_data/0, attributes/0, tokens/0,
+ error_description/0, char_spec/0, cont_fun/0,
+ attribute_item/0, info_location/0, attribute_info/0,
+ token_item/0, token_info/0]).
+
+-opaque category() :: atom().
+-opaque column() :: pos_integer().
+-opaque line() :: integer().
+-opaque location() :: line() | {line(),column()}.
+-opaque resword_fun() :: fun((atom()) -> boolean()).
+-opaque option() :: 'return' | 'return_white_spaces' | 'return_comments'
+ | 'text' | {'reserved_word_fun', resword_fun()}.
+-opaque options() :: option() | [option()].
+-opaque symbol() :: atom() | float() | integer() | string().
+-opaque info_line() :: integer() | term().
+-opaque attributes_data()
+ :: [{'column', column()} | {'line', info_line()} | {'text', string()}]
+ | {line(), column()}.
+%% The fact that {line(),column()} is a possible attributes() type
+%% is hidden.
+-opaque attributes() :: line() | attributes_data().
+-opaque token() :: {category(), attributes(), symbol()}
+ | {category(), attributes()}.
+-opaque tokens() :: [token()].
+-opaque error_description() :: term().
+-opaque error_info() :: {location(), module(), error_description()}.
+
+%%% Local record.
+-record(erl_scan,
+ {resword_fun = fun reserved_word/1 :: resword_fun(),
+ ws = false :: boolean(),
+ comment = false :: boolean(),
+ text = false :: boolean()}).
+
+%%----------------------------------------------------------------------------
+
+-spec format_error(ErrorDescriptor) -> string() when
+ ErrorDescriptor :: error_description().
+format_error({string,Quote,Head}) ->
+ lists:flatten(["unterminated " ++ string_thing(Quote) ++
+ " starting with " ++
+ io_lib:write_string(Head, Quote)]);
+format_error({illegal,Type}) ->
+ lists:flatten(io_lib:fwrite("illegal ~w", [Type]));
+format_error(char) -> "unterminated character";
+format_error({base,Base}) ->
+ lists:flatten(io_lib:fwrite("illegal base '~w'", [Base]));
+format_error(Other) ->
+ lists:flatten(io_lib:write(Other)).
+
+-spec string(String) -> Return when
+ String :: string(),
+ Return :: {'ok', Tokens :: tokens(), EndLocation}
+ | {'error', ErrorInfo :: error_info(), ErrorLocation},
+ EndLocation :: location(),
+ ErrorLocation :: location().
+string(String) ->
+ string(String, 1, []).
+
+-spec string(String, StartLocation) -> Return when
+ String :: string(),
+ Return :: {'ok', Tokens :: tokens(), EndLocation}
+ | {'error', ErrorInfo :: error_info(), ErrorLocation},
+ StartLocation :: location(),
+ EndLocation :: location(),
+ ErrorLocation :: location().
+string(String, StartLocation) ->
+ string(String, StartLocation, []).
+
+-spec string(String, StartLocation, Options) -> Return when
+ String :: string(),
+ Options :: options(),
+ Return :: {'ok', Tokens :: tokens(), EndLocation}
+ | {'error', ErrorInfo :: error_info(), ErrorLocation},
+ StartLocation :: location(),
+ EndLocation :: location(),
+ ErrorLocation :: location().
+string(String, Line, Options) when ?STRING(String), ?ALINE(Line) ->
+ string1(String, options(Options), Line, no_col, []);
+string(String, {Line,Column}, Options) when ?STRING(String),
+ ?ALINE(Line),
+ ?COLUMN(Column) ->
+ string1(String, options(Options), Line, Column, []).
+
+-opaque char_spec() :: string() | 'eof'.
+-opaque cont_fun() :: fun((char_spec(), #erl_scan{}, line(), column(),
+ tokens(), any()) -> any()).
+-opaque return_cont() :: {erl_scan_continuation,
+ string(), column(), tokens(), line(),
+ #erl_scan{}, any(), cont_fun()}.
+-opaque tokens_result() :: {'ok', Tokens :: tokens(), EndLocation :: location()}
+ | {'eof', EndLocation :: location()}
+ | {'error', ErrorInfo :: error_info(),
+ EndLocation :: location()}.
+
+-spec tokens(Continuation, CharSpec, StartLocation) -> Return when
+ Continuation :: return_cont() | [],
+ CharSpec :: char_spec(),
+ StartLocation :: location(),
+ Return :: {'done',Result :: tokens_result(),LeftOverChars :: char_spec()}
+ | {'more', Continuation1 :: return_cont()}.
+tokens(Cont, CharSpec, StartLocation) ->
+ tokens(Cont, CharSpec, StartLocation, []).
+
+-spec tokens(Continuation, CharSpec, StartLocation, Options) -> Return when
+ Continuation :: return_cont() | [],
+ CharSpec :: char_spec(),
+ StartLocation :: location(),
+ Options :: options(),
+ Return :: {'done',Result :: tokens_result(),LeftOverChars :: char_spec()}
+ | {'more', Continuation1 :: return_cont()}.
+tokens([], CharSpec, Line, Options) when ?ALINE(Line) ->
+ tokens1(CharSpec, options(Options), Line, no_col, [], fun scan/6, []);
+tokens([], CharSpec, {Line,Column}, Options) when ?ALINE(Line),
+ ?COLUMN(Column) ->
+ tokens1(CharSpec, options(Options), Line, Column, [], fun scan/6, []);
+tokens({erl_scan_continuation,Cs,Col,Toks,Line,St,Any,Fun},
+ CharSpec, _Loc, _Opts) ->
+ tokens1(Cs++CharSpec, St, Line, Col, Toks, Fun, Any).
+
+continuation_location({erl_scan_continuation,_,no_col,_,Line,_,_,_}) ->
+ Line;
+continuation_location({erl_scan_continuation,_,Col,_,Line,_,_,_}) ->
+ {Line,Col}.
+
+-opaque attribute_item() :: 'column' | 'length' | 'line'
+ | 'location' | 'text'.
+-opaque info_location() :: location() | term().
+-opaque attribute_info() :: {'column', column()}| {'length', pos_integer()}
+ | {'line', info_line()}
+ | {'location', info_location()}
+ | {'text', string()}.
+-opaque token_item() :: 'category' | 'symbol' | attribute_item().
+-opaque token_info() :: {'category', category()} | {'symbol', symbol()}
+ | attribute_info().
+
+-spec token_info(Token) -> TokenInfo when
+ Token :: token(),
+ TokenInfo :: [TokenInfoTuple :: token_info()].
+token_info(Token) ->
+ Items = [category,column,length,line,symbol,text], % undefined order
+ token_info(Token, Items).
+
+-spec token_info(Token, TokenItem) -> TokenInfoTuple | 'undefined' when
+ Token :: token(),
+ TokenItem :: token_item(),
+ TokenInfoTuple :: token_info();
+ (Token, TokenItems) -> TokenInfo when
+ Token :: token(),
+ TokenItems :: [TokenItem :: token_item()],
+ TokenInfo :: [TokenInfoTuple :: token_info()].
+token_info(_Token, []) ->
+ [];
+token_info(Token, [Item|Items]) when is_atom(Item) ->
+ case token_info(Token, Item) of
+ undefined ->
+ token_info(Token, Items);
+ TokenInfo when is_tuple(TokenInfo) ->
+ [TokenInfo|token_info(Token, Items)]
+ end;
+token_info({Category,_Attrs}, category=Item) ->
+ {Item,Category};
+token_info({Category,_Attrs,_Symbol}, category=Item) ->
+ {Item,Category};
+token_info({Category,_Attrs}, symbol=Item) ->
+ {Item,Category};
+token_info({_Category,_Attrs,Symbol}, symbol=Item) ->
+ {Item,Symbol};
+token_info({_Category,Attrs}, Item) ->
+ attributes_info(Attrs, Item);
+token_info({_Category,Attrs,_Symbol}, Item) ->
+ attributes_info(Attrs, Item).
+
+-spec attributes_info(Attributes) -> AttributesInfo when
+ Attributes :: attributes(),
+ AttributesInfo :: [AttributeInfoTuple :: attribute_info()].
+attributes_info(Attributes) ->
+ Items = [column,length,line,text], % undefined order
+ attributes_info(Attributes, Items).
+
+-spec attributes_info
+ (Attributes, AttributeItem) -> AttributeInfoTuple | 'undefined' when
+ Attributes :: attributes(),
+ AttributeItem :: attribute_item(),
+ AttributeInfoTuple :: attribute_info();
+ (Attributes, AttributeItems) -> AttributeInfo when
+ Attributes :: attributes(),
+ AttributeItems :: [AttributeItem :: attribute_item()],
+ AttributeInfo :: [AttributeInfoTuple :: attribute_info()].
+attributes_info(_Attrs, []) ->
+ [];
+attributes_info(Attrs, [A|As]) when is_atom(A) ->
+ case attributes_info(Attrs, A) of
+ undefined ->
+ attributes_info(Attrs, As);
+ AttributeInfo when is_tuple(AttributeInfo) ->
+ [AttributeInfo|attributes_info(Attrs, As)]
+ end;
+attributes_info({Line,Column}, column=Item) when ?ALINE(Line),
+ ?COLUMN(Column) ->
+ {Item,Column};
+attributes_info(Line, column) when ?ALINE(Line) ->
+ undefined;
+attributes_info(Attrs, column=Item) ->
+ attr_info(Attrs, Item);
+attributes_info(Attrs, length=Item) ->
+ case attributes_info(Attrs, text) of
+ undefined ->
+ undefined;
+ {text,Text} ->
+ {Item,length(Text)}
+ end;
+attributes_info(Line, line=Item) when ?ALINE(Line) ->
+ {Item,Line};
+attributes_info({Line,Column}, line=Item) when ?ALINE(Line),
+ ?COLUMN(Column) ->
+ {Item,Line};
+attributes_info(Attrs, line=Item) ->
+ attr_info(Attrs, Item);
+attributes_info({Line,Column}=Location, location=Item) when ?ALINE(Line),
+ ?COLUMN(Column) ->
+ {Item,Location};
+attributes_info(Line, location=Item) when ?ALINE(Line) ->
+ {Item,Line};
+attributes_info(Attrs, location=Item) ->
+ {line,Line} = attributes_info(Attrs, line), % assume line is present
+ case attributes_info(Attrs, column) of
+ undefined ->
+ %% If set_attribute() has assigned a term such as {17,42}
+ %% to 'line', then Line will look like {Line,Column}. One
+ %% should not use 'location' but 'line' and 'column' in
+ %% such special cases.
+ {Item,Line};
+ {column,Column} ->
+ {Item,{Line,Column}}
+ end;
+attributes_info({Line,Column}, text) when ?ALINE(Line), ?COLUMN(Column) ->
+ undefined;
+attributes_info(Line, text) when ?ALINE(Line) ->
+ undefined;
+attributes_info(Attrs, text=Item) ->
+ attr_info(Attrs, Item);
+attributes_info(T1, T2) ->
+ erlang:error(badarg, [T1,T2]).
+
+-spec set_attribute(AttributeItem, Attributes, SetAttributeFun) -> Attributes when
+ AttributeItem :: 'line',
+ Attributes :: attributes(),
+ SetAttributeFun :: fun((info_line()) -> info_line()).
+set_attribute(Tag, Attributes, Fun) when ?SETATTRFUN(Fun) ->
+ set_attr(Tag, Attributes, Fun).
+
+%%%
+%%% Local functions
+%%%
+
+string_thing($') -> "atom"; %' Stupid Emacs
+string_thing(_) -> "string".
+
+-define(WHITE_SPACE(C),
+ is_integer(C) andalso
+ (C >= $\000 andalso C =< $\s orelse C >= $\200 andalso C =< $\240)).
+-define(DIGIT(C), C >= $0, C =< $9).
+-define(CHAR(C), is_integer(C), C >= 0).
+-define(UNICODE(C),
+ is_integer(C) andalso
+ (C >= 0 andalso C < 16#D800 orelse
+ C > 16#DFFF andalso C < 16#FFFE orelse
+ C > 16#FFFF andalso C =< 16#10FFFF)).
+
+-define(UNI255(C), C >= 0, C =< 16#ff).
+
+options(Opts0) when is_list(Opts0) ->
+ Opts = lists:foldr(fun expand_opt/2, [], Opts0),
+ [RW_fun] =
+ case opts(Opts, [reserved_word_fun], []) of
+ badarg ->
+ erlang:error(badarg, [Opts0]);
+ R ->
+ R
+ end,
+ Comment = proplists:get_bool(return_comments, Opts),
+ WS = proplists:get_bool(return_white_spaces, Opts),
+ Txt = proplists:get_bool(text, Opts),
+ #erl_scan{resword_fun = RW_fun,
+ comment = Comment,
+ ws = WS,
+ text = Txt};
+options(Opt) ->
+ options([Opt]).
+
+opts(Options, [Key|Keys], L) ->
+ V = case lists:keyfind(Key, 1, Options) of
+ {reserved_word_fun,F} when ?RESWORDFUN(F) ->
+ {ok,F};
+ {Key,_} ->
+ badarg;
+ false ->
+ {ok,default_option(Key)}
+ end,
+ case V of
+ badarg ->
+ badarg;
+ {ok,Value} ->
+ opts(Options, Keys, [Value|L])
+ end;
+opts(_Options, [], L) ->
+ lists:reverse(L).
+
+default_option(reserved_word_fun) ->
+ fun reserved_word/1.
+
+expand_opt(return, Os) ->
+ [return_comments,return_white_spaces|Os];
+expand_opt(O, Os) ->
+ [O|Os].
+
+attr_info(Attrs, Item) ->
+ try lists:keyfind(Item, 1, Attrs) of
+ {_Item, _Value} = T ->
+ T;
+ false ->
+ undefined
+ catch
+ _:_ ->
+ erlang:error(badarg, [Attrs, Item])
+ end.
+
+-spec set_attr('line', attributes(), fun((line()) -> line())) -> attributes().
+
+set_attr(line, Line, Fun) when ?ALINE(Line) ->
+ Ln = Fun(Line),
+ if
+ ?ALINE(Ln) ->
+ Ln;
+ true ->
+ [{line,Ln}]
+ end;
+set_attr(line, {Line,Column}, Fun) when ?ALINE(Line), ?COLUMN(Column) ->
+ Ln = Fun(Line),
+ if
+ ?ALINE(Ln) ->
+ {Ln,Column};
+ true ->
+ [{line,Ln},{column,Column}]
+ end;
+set_attr(line=Tag, Attrs, Fun) when is_list(Attrs) ->
+ {line,Line} = lists:keyfind(Tag, 1, Attrs),
+ case lists:keyreplace(Tag, 1, Attrs, {line,Fun(Line)}) of
+ [{line,Ln}] when ?ALINE(Ln) ->
+ Ln;
+ As ->
+ As
+ end;
+set_attr(T1, T2, T3) ->
+ erlang:error(badarg, [T1,T2,T3]).
+
+tokens1(Cs, St, Line, Col, Toks, Fun, Any) when ?STRING(Cs); Cs =:= eof ->
+ case Fun(Cs, St, Line, Col, Toks, Any) of
+ {more,{Cs0,Ncol,Ntoks,Nline,Nany,Nfun}} ->
+ {more,{erl_scan_continuation,Cs0,Ncol,Ntoks,Nline,St,Nany,Nfun}};
+ {ok,Toks0,eof,Nline,Ncol} ->
+ Res = case Toks0 of
+ [] ->
+ {eof,location(Nline, Ncol)};
+ _ ->
+ {ok,lists:reverse(Toks0),location(Nline,Ncol)}
+ end,
+ {done,Res,eof};
+ {ok,Toks0,Rest,Nline,Ncol} ->
+ {done,{ok,lists:reverse(Toks0),location(Nline, Ncol)},Rest};
+ {{error,_,_}=Error,Rest} ->
+ {done,Error,Rest}
+ end.
+
+string1(Cs, St, Line, Col, Toks) ->
+ case scan1(Cs, St, Line, Col, Toks) of
+ {more,{Cs0,Ncol,Ntoks,Nline,Any,Fun}} ->
+ case Fun(Cs0++eof, St, Nline, Ncol, Ntoks, Any) of
+ {ok,Toks1,_Rest,Line2,Col2} ->
+ {ok,lists:reverse(Toks1),location(Line2, Col2)};
+ {{error,_,_}=Error,_Rest} ->
+ Error
+ end;
+ {ok,Ntoks,[_|_]=Rest,Nline,Ncol} ->
+ string1(Rest, St, Nline, Ncol, Ntoks);
+ {ok,Ntoks,_,Nline,Ncol} ->
+ {ok,lists:reverse(Ntoks),location(Nline, Ncol)};
+ {{error,_,_}=Error,_Rest} ->
+ Error
+ end.
+
+scan(Cs, St, Line, Col, Toks, _) ->
+ scan1(Cs, St, Line, Col, Toks).
+
+scan1([$\s|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
+ scan_spcs(Cs, St, Line, Col, Toks, 1);
+scan1([$\s|Cs], St, Line, Col, Toks) ->
+ skip_white_space(Cs, St, Line, Col, Toks, 1);
+scan1([$\n|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
+ scan_newline(Cs, St, Line, Col, Toks);
+scan1([$\n|Cs], St, Line, Col, Toks) ->
+ skip_white_space(Cs, St, Line+1, new_column(Col, 1), Toks, 0);
+scan1([C|Cs], St, Line, Col, Toks) when C >= $A, C =< $Z ->
+ scan_variable(Cs, St, Line, Col, Toks, [C]);
+scan1([C|Cs], St, Line, Col, Toks) when C >= $a, C =< $z ->
+ scan_atom(Cs, St, Line, Col, Toks, [C]);
+%% Optimization: some very common punctuation characters:
+scan1([$,|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ",", ',', 1);
+scan1([$(|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "(", '(', 1);
+scan1([$)|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ")", ')', 1);
+scan1([${|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "{", '{', 1);
+scan1([$}|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "}", '}', 1);
+scan1([$[|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "[", '[', 1);
+scan1([$]|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "]", ']', 1);
+scan1([$;|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ";", ';', 1);
+scan1([$_=C|Cs], St, Line, Col, Toks) ->
+ scan_variable(Cs, St, Line, Col, Toks, [C]);
+%% More punctuation characters below.
+scan1([$\%|Cs], St, Line, Col, Toks) when not St#erl_scan.comment ->
+ skip_comment(Cs, St, Line, Col, Toks, 1);
+scan1([$\%=C|Cs], St, Line, Col, Toks) ->
+ scan_comment(Cs, St, Line, Col, Toks, [C]);
+scan1([C|Cs], St, Line, Col, Toks) when ?DIGIT(C) ->
+ scan_number(Cs, St, Line, Col, Toks, [C]);
+scan1("..."++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "...", '...', 3);
+scan1(".."=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1(".."++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "..", '..', 2);
+scan1("."=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1([$.=C|Cs], St, Line, Col, Toks) ->
+ scan_dot(Cs, St, Line, Col, Toks, [C]);
+scan1([$"|Cs], St, Line, Col, Toks) -> %" Emacs
+ State0 = {[],[],Line,Col},
+ scan_string(Cs, St, Line, incr_column(Col, 1), Toks, State0);
+scan1([$'|Cs], St, Line, Col, Toks) -> %' Emacs
+ State0 = {[],[],Line,Col},
+ scan_qatom(Cs, St, Line, incr_column(Col, 1), Toks, State0);
+scan1([$$|Cs], St, Line, Col, Toks) ->
+ scan_char(Cs, St, Line, Col, Toks);
+scan1([$\r|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
+ white_space_end(Cs, St, Line, Col, Toks, 1, "\r");
+scan1([C|Cs], St, Line, Col, Toks) when C >= $ß, C =< $ÿ, C =/= $÷ ->
+ scan_atom(Cs, St, Line, Col, Toks, [C]);
+scan1([C|Cs], St, Line, Col, Toks) when C >= $À, C =< $Þ, C /= $× ->
+ scan_variable(Cs, St, Line, Col, Toks, [C]);
+scan1([$\t|Cs], St, Line, Col, Toks) when St#erl_scan.ws ->
+ scan_tabs(Cs, St, Line, Col, Toks, 1);
+scan1([$\t|Cs], St, Line, Col, Toks) ->
+ skip_white_space(Cs, St, Line, Col, Toks, 1);
+scan1([C|Cs], St, Line, Col, Toks) when ?WHITE_SPACE(C) ->
+ case St#erl_scan.ws of
+ true ->
+ scan_white_space(Cs, St, Line, Col, Toks, [C]);
+ false ->
+ skip_white_space(Cs, St, Line, Col, Toks, 1)
+ end;
+%% Punctuation characters and operators, first recognise multiples.
+%% << <- <=
+scan1("<<"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "<<", '<<', 2);
+scan1("<-"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "<-", '<-', 2);
+scan1("<="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "<=", '<=', 2);
+scan1("<"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% >> >=
+scan1(">>"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ">>", '>>', 2);
+scan1(">="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ">=", '>=', 2);
+scan1(">"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% -> --
+scan1("->"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "->", '->', 2);
+scan1("--"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "--", '--', 2);
+scan1("-"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% ++
+scan1("++"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "++", '++', 2);
+scan1("+"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% =:= =/= =< ==
+scan1("=:="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "=:=", '=:=', 3);
+scan1("=:"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("=/="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "=/=", '=/=', 3);
+scan1("=/"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1("=<"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "=<", '=<', 2);
+scan1("=="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "==", '==', 2);
+scan1("="=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% /=
+scan1("/="++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "/=", '/=', 2);
+scan1("/"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% ||
+scan1("||"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "||", '||', 2);
+scan1("|"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% :-
+scan1(":-"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ":-", ':-', 2);
+%% :: for typed records
+scan1("::"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "::", '::', 2);
+scan1(":"=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+%% Optimization: punctuation characters less than 127:
+scan1([$=|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "=", '=', 1);
+scan1([$:|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ":", ':', 1);
+scan1([$||Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "|", '|', 1);
+scan1([$#|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "#", '#', 1);
+scan1([$/|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "/", '/', 1);
+scan1([$?|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "?", '?', 1);
+scan1([$-|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "-", '-', 1);
+scan1([$+|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "+", '+', 1);
+scan1([$*|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "*", '*', 1);
+scan1([$<|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "<", '<', 1);
+scan1([$>|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, ">", '>', 1);
+scan1([$!|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "!", '!', 1);
+scan1([$@|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "@", '@', 1);
+scan1([$\\|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "\\", '\\', 1);
+scan1([$^|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "^", '^', 1);
+scan1([$`|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "`", '`', 1);
+scan1([$~|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "~", '~', 1);
+scan1([$&|Cs], St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "&", '&', 1);
+%% End of optimization.
+scan1([C|Cs], St, Line, Col, Toks) when ?UNI255(C) ->
+ Str = [C],
+ tok2(Cs, St, Line, Col, Toks, Str, list_to_atom(Str), 1);
+scan1([C|Cs], _St, Line, Col, _Toks) when ?CHAR(C) ->
+ Ncol = incr_column(Col, 1),
+ scan_error({illegal,character}, Line, Col, Line, Ncol, Cs);
+scan1([]=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1(eof=Cs, _St, Line, Col, Toks) ->
+ {ok,Toks,Cs,Line,Col}.
+
+scan_atom(Cs0, St, Line, Col, Toks, Ncs0) ->
+ case scan_name(Cs0, Ncs0) of
+ {more,Ncs} ->
+ {more,{[],Col,Toks,Line,Ncs,fun scan_atom/6}};
+ {Wcs,Cs} ->
+ case catch list_to_atom(Wcs) of
+ Name when is_atom(Name) ->
+ case (St#erl_scan.resword_fun)(Name) of
+ true ->
+ tok2(Cs, St, Line, Col, Toks, Wcs, Name);
+ false ->
+ tok3(Cs, St, Line, Col, Toks, atom, Wcs, Name)
+ end;
+ _Error ->
+ Ncol = incr_column(Col, length(Wcs)),
+ scan_error({illegal,atom}, Line, Col, Line, Ncol, Cs)
+ end
+ end.
+
+scan_variable(Cs0, St, Line, Col, Toks, Ncs0) ->
+ case scan_name(Cs0, Ncs0) of
+ {more,Ncs} ->
+ {more,{[],Col,Toks,Line,Ncs,fun scan_variable/6}};
+ {Wcs,Cs} ->
+ case catch list_to_atom(Wcs) of
+ Name when is_atom(Name) ->
+ tok3(Cs, St, Line, Col, Toks, var, Wcs, Name);
+ _Error ->
+ Ncol = incr_column(Col, length(Wcs)),
+ scan_error({illegal,var}, Line, Col, Line, Ncol, Cs)
+ end
+ end.
+
+scan_name([C|Cs], Ncs) when C >= $a, C =< $z ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([C|Cs], Ncs) when C >= $A, C =< $Z ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([$_=C|Cs], Ncs) ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([C|Cs], Ncs) when ?DIGIT(C) ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([$@=C|Cs], Ncs) ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([C|Cs], Ncs) when C >= $ß, C =< $ÿ, C =/= $÷ ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([C|Cs], Ncs) when C >= $À, C =< $Þ, C =/= $× ->
+ scan_name(Cs, [C|Ncs]);
+scan_name([], Ncs) ->
+ {more,Ncs};
+scan_name(Cs, Ncs) ->
+ {lists:reverse(Ncs),Cs}.
+
+-define(STR(St, S), if St#erl_scan.text -> S; true -> [] end).
+
+scan_dot([$%|_]=Cs, St, Line, Col, Toks, Ncs) ->
+ Attrs = attributes(Line, Col, St, Ncs),
+ {ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 1)};
+scan_dot([$\n=C|Cs], St, Line, Col, Toks, Ncs) ->
+ Attrs = attributes(Line, Col, St, ?STR(St, Ncs++[C])),
+ {ok,[{dot,Attrs}|Toks],Cs,Line+1,new_column(Col, 1)};
+scan_dot([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
+ Attrs = attributes(Line, Col, St, ?STR(St, Ncs++[C])),
+ {ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 2)};
+scan_dot(eof=Cs, St, Line, Col, Toks, Ncs) ->
+ Attrs = attributes(Line, Col, St, Ncs),
+ {ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 1)};
+scan_dot(Cs, St, Line, Col, Toks, Ncs) ->
+ tok2(Cs, St, Line, Col, Toks, Ncs, '.', 1).
+
+%%% White space characters are very common, so it is worthwhile to
+%%% scan them fast and store them compactly. (The words "whitespace"
+%%% and "white space" usually mean the same thing. The Erlang
+%%% specification denotes the characters with ASCII code in the
+%%% interval 0 to 32 as "white space".)
+%%%
+%%% Convention: if there is a white newline ($\n) it will always be
+%%% the first character in the text string. As a consequence, there
+%%% cannot be more than one newline in a white_space token string.
+%%%
+%%% Some common combinations are recognized, some are not. Examples
+%%% of the latter are tab(s) followed by space(s), like "\t ".
+%%% (They will be represented by two (or more) tokens.)
+%%%
+%%% Note: the character sequence "\r\n" is *not* recognized since it
+%%% would violate the property that $\n will always be the first
+%%% character. (But since "\r\n\r\n" is common, it pays off to
+%%% recognize "\n\r".)
+
+scan_newline([$\s|Cs], St, Line, Col, Toks) ->
+ scan_nl_spcs(Cs, St, Line, Col, Toks, 2);
+scan_newline([$\t|Cs], St, Line, Col, Toks) ->
+ scan_nl_tabs(Cs, St, Line, Col, Toks, 2);
+scan_newline([$\r|Cs], St, Line, Col, Toks) ->
+ newline_end(Cs, St, Line, Col, Toks, 2, "\n\r");
+scan_newline([$\f|Cs], St, Line, Col, Toks) ->
+ newline_end(Cs, St, Line, Col, Toks, 2, "\n\f");
+scan_newline([], _St, Line, Col, Toks) ->
+ {more,{[$\n],Col,Toks,Line,[],fun scan/6}};
+scan_newline(Cs, St, Line, Col, Toks) ->
+ scan_nl_white_space(Cs, St, Line, Col, Toks, "\n").
+
+scan_nl_spcs([$\s|Cs], St, Line, Col, Toks, N) when N < 17 ->
+ scan_nl_spcs(Cs, St, Line, Col, Toks, N+1);
+scan_nl_spcs([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun scan_nl_spcs/6}};
+scan_nl_spcs(Cs, St, Line, Col, Toks, N) ->
+ newline_end(Cs, St, Line, Col, Toks, N, nl_spcs(N)).
+
+scan_nl_tabs([$\t|Cs], St, Line, Col, Toks, N) when N < 11 ->
+ scan_nl_tabs(Cs, St, Line, Col, Toks, N+1);
+scan_nl_tabs([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun scan_nl_tabs/6}};
+scan_nl_tabs(Cs, St, Line, Col, Toks, N) ->
+ newline_end(Cs, St, Line, Col, Toks, N, nl_tabs(N)).
+
+%% Note: returning {more,Cont} is meaningless here; one could just as
+%% well return several tokens. But since tokens() scans up to a full
+%% stop anyway, nothing is gained by not collecting all white spaces.
+scan_nl_white_space([$\n|Cs], #erl_scan{text = false}=St, Line, no_col=Col,
+ Toks0, Ncs) ->
+ Toks = [{white_space,Line,lists:reverse(Ncs)}|Toks0],
+ scan_newline(Cs, St, Line+1, Col, Toks);
+scan_nl_white_space([$\n|Cs], St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ Attrs = attributes(Line, Col, St, Ncs),
+ Token = {white_space,Attrs,Ncs},
+ scan_newline(Cs, St, Line+1, new_column(Col, length(Ncs)), [Token|Toks]);
+scan_nl_white_space([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
+ scan_nl_white_space(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_nl_white_space([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_nl_white_space/6}};
+scan_nl_white_space(Cs, #erl_scan{text = false}=St, Line, no_col=Col,
+ Toks, Ncs) ->
+ scan1(Cs, St, Line+1, Col, [{white_space,Line,lists:reverse(Ncs)}|Toks]);
+scan_nl_white_space(Cs, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ Attrs = attributes(Line, Col, St, Ncs),
+ Token = {white_space,Attrs,Ncs},
+ scan1(Cs, St, Line+1, new_column(Col, length(Ncs)), [Token|Toks]).
+
+newline_end(Cs, #erl_scan{text = false}=St, Line, no_col=Col,
+ Toks, _N, Ncs) ->
+ scan1(Cs, St, Line+1, Col, [{white_space,Line,Ncs}|Toks]);
+newline_end(Cs, St, Line, Col, Toks, N, Ncs) ->
+ Attrs = attributes(Line, Col, St, Ncs),
+ scan1(Cs, St, Line+1, new_column(Col, N), [{white_space,Attrs,Ncs}|Toks]).
+
+scan_spcs([$\s|Cs], St, Line, Col, Toks, N) when N < 16 ->
+ scan_spcs(Cs, St, Line, Col, Toks, N+1);
+scan_spcs([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun scan_spcs/6}};
+scan_spcs(Cs, St, Line, Col, Toks, N) ->
+ white_space_end(Cs, St, Line, Col, Toks, N, spcs(N)).
+
+scan_tabs([$\t|Cs], St, Line, Col, Toks, N) when N < 10 ->
+ scan_tabs(Cs, St, Line, Col, Toks, N+1);
+scan_tabs([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun scan_tabs/6}};
+scan_tabs(Cs, St, Line, Col, Toks, N) ->
+ white_space_end(Cs, St, Line, Col, Toks, N, tabs(N)).
+
+skip_white_space([$\n|Cs], St, Line, Col, Toks, _N) ->
+ skip_white_space(Cs, St, Line+1, new_column(Col, 1), Toks, 0);
+skip_white_space([C|Cs], St, Line, Col, Toks, N) when ?WHITE_SPACE(C) ->
+ skip_white_space(Cs, St, Line, Col, Toks, N+1);
+skip_white_space([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun skip_white_space/6}};
+skip_white_space(Cs, St, Line, Col, Toks, N) ->
+ scan1(Cs, St, Line, incr_column(Col, N), Toks).
+
+%% Maybe \t and \s should break the loop.
+scan_white_space([$\n|_]=Cs, St, Line, Col, Toks, Ncs) ->
+ white_space_end(Cs, St, Line, Col, Toks, length(Ncs), lists:reverse(Ncs));
+scan_white_space([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
+ scan_white_space(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_white_space([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_white_space/6}};
+scan_white_space(Cs, St, Line, Col, Toks, Ncs) ->
+ white_space_end(Cs, St, Line, Col, Toks, length(Ncs), lists:reverse(Ncs)).
+
+-compile({inline,[white_space_end/7]}).
+
+white_space_end(Cs, St, Line, Col, Toks, N, Ncs) ->
+ tok3(Cs, St, Line, Col, Toks, white_space, Ncs, Ncs, N).
+
+scan_char([$\\|Cs]=Cs0, St, Line, Col, Toks) ->
+ case scan_escape(Cs, incr_column(Col, 2)) of
+ more ->
+ {more,{[$$|Cs0],Col,Toks,Line,[],fun scan/6}};
+ {error,Ncs,Error,Ncol} ->
+ scan_error(Error, Line, Col, Line, Ncol, Ncs);
+ {eof,Ncol} ->
+ scan_error(char, Line, Col, Line, Ncol, eof);
+ {nl,Val,Str,Ncs,Ncol} ->
+ Attrs = attributes(Line, Col, St, ?STR(St, "$\\"++Str)), %"
+ Ntoks = [{char,Attrs,Val}|Toks],
+ scan1(Ncs, St, Line+1, Ncol, Ntoks);
+ {Val,Str,Ncs,Ncol} ->
+ Attrs = attributes(Line, Col, St, ?STR(St, "$\\"++Str)), %"
+ Ntoks = [{char,Attrs,Val}|Toks],
+ scan1(Ncs, St, Line, Ncol, Ntoks)
+ end;
+scan_char([$\n=C|Cs], St, Line, Col, Toks) ->
+ Attrs = attributes(Line, Col, St, ?STR(St, [$$,C])),
+ scan1(Cs, St, Line+1, new_column(Col, 1), [{char,Attrs,C}|Toks]);
+scan_char([C|Cs], St, Line, Col, Toks) when ?UNICODE(C) ->
+ Attrs = attributes(Line, Col, St, ?STR(St, [$$,C])),
+ scan1(Cs, St, Line, incr_column(Col, 2), [{char,Attrs,C}|Toks]);
+scan_char([C|_Cs], _St, Line, Col, _Toks) when ?CHAR(C) ->
+ scan_error({illegal,character}, Line, Col, Line, incr_column(Col, 1), eof);
+scan_char([], _St, Line, Col, Toks) ->
+ {more,{[$$],Col,Toks,Line,[],fun scan/6}};
+scan_char(eof, _St, Line, Col, _Toks) ->
+ scan_error(char, Line, Col, Line, incr_column(Col, 1), eof).
+
+scan_string(Cs, St, Line, Col, Toks, {Wcs,Str,Line0,Col0}) ->
+ case scan_string0(Cs, St, Line, Col, $\", Str, Wcs) of %"
+ {more,Ncs,Nline,Ncol,Nstr,Nwcs} ->
+ State = {Nwcs,Nstr,Line0,Col0},
+ {more,{Ncs,Ncol,Toks,Nline,State,fun scan_string/6}};
+ {char_error,Ncs,Error,Nline,Ncol,EndCol} ->
+ scan_error(Error, Nline, Ncol, Nline, EndCol, Ncs);
+ {error,Nline,Ncol,Nwcs,Ncs} ->
+ Estr = string:substr(Nwcs, 1, 16), % Expanded escape chars.
+ scan_error({string,$\",Estr}, Line0, Col0, Nline, Ncol, Ncs); %"
+ {Ncs,Nline,Ncol,Nstr,Nwcs} ->
+ Attrs = attributes(Line0, Col0, St, Nstr),
+ scan1(Ncs, St, Nline, Ncol, [{string,Attrs,Nwcs}|Toks])
+ end.
+
+scan_qatom(Cs, St, Line, Col, Toks, {Wcs,Str,Line0,Col0}) ->
+ case scan_string0(Cs, St, Line, Col, $\', Str, Wcs) of %'
+ {more,Ncs,Nline,Ncol,Nstr,Nwcs} ->
+ State = {Nwcs,Nstr,Line0,Col0},
+ {more,{Ncs,Ncol,Toks,Nline,State,fun scan_qatom/6}};
+ {char_error,Ncs,Error,Nline,Ncol,EndCol} ->
+ scan_error(Error, Nline, Ncol, Nline, EndCol, Ncs);
+ {error,Nline,Ncol,Nwcs,Ncs} ->
+ Estr = string:substr(Nwcs, 1, 16), % Expanded escape chars.
+ scan_error({string,$\',Estr}, Line0, Col0, Nline, Ncol, Ncs); %'
+ {Ncs,Nline,Ncol,Nstr,Nwcs} ->
+ case catch list_to_atom(Nwcs) of
+ A when is_atom(A) ->
+ Attrs = attributes(Line0, Col0, St, Nstr),
+ scan1(Ncs, St, Nline, Ncol, [{atom,Attrs,A}|Toks]);
+ _ ->
+ scan_error({illegal,atom}, Line0, Col0, Nline, Ncol, Ncs)
+ end
+ end.
+
+scan_string0(Cs, #erl_scan{text=false}, Line, no_col=Col, Q, [], Wcs) ->
+ scan_string_no_col(Cs, Line, Col, Q, Wcs);
+scan_string0(Cs, #erl_scan{text=true}, Line, no_col=Col, Q, Str, Wcs) ->
+ scan_string1(Cs, Line, Col, Q, Str, Wcs);
+scan_string0(Cs, St, Line, Col, Q, [], Wcs) ->
+ scan_string_col(Cs, St, Line, Col, Q, Wcs);
+scan_string0(Cs, _St, Line, Col, Q, Str, Wcs) ->
+ scan_string1(Cs, Line, Col, Q, Str, Wcs).
+
+%% Optimization. Col =:= no_col.
+scan_string_no_col([Q|Cs], Line, Col, Q, Wcs) ->
+ {Cs,Line,Col,_DontCare=[],lists:reverse(Wcs)};
+scan_string_no_col([$\n=C|Cs], Line, Col, Q, Wcs) ->
+ scan_string_no_col(Cs, Line+1, Col, Q, [C|Wcs]);
+scan_string_no_col([C|Cs], Line, Col, Q, Wcs) when C =/= $\\, ?UNICODE(C) ->
+ scan_string_no_col(Cs, Line, Col, Q, [C|Wcs]);
+scan_string_no_col(Cs, Line, Col, Q, Wcs) ->
+ scan_string1(Cs, Line, Col, Q, Wcs, Wcs).
+
+%% Optimization. Col =/= no_col.
+scan_string_col([Q|Cs], St, Line, Col, Q, Wcs0) ->
+ Wcs = lists:reverse(Wcs0),
+ Str = ?STR(St, [Q|Wcs++[Q]]),
+ {Cs,Line,Col+1,Str,Wcs};
+scan_string_col([$\n=C|Cs], St, Line, _xCol, Q, Wcs) ->
+ scan_string_col(Cs, St, Line+1, 1, Q, [C|Wcs]);
+scan_string_col([C|Cs], St, Line, Col, Q, Wcs) when C =/= $\\, ?UNICODE(C) ->
+ scan_string_col(Cs, St, Line, Col+1, Q, [C|Wcs]);
+scan_string_col(Cs, _St, Line, Col, Q, Wcs) ->
+ scan_string1(Cs, Line, Col, Q, Wcs, Wcs).
+
+%% Note: in those cases when a 'char_error' tuple is returned below it
+%% is tempting to skip over characters up to the first Q character,
+%% but then the end location of the error tuple would not correspond
+%% to the start location of the returned Rest string. (Maybe the end
+%% location could be modified, but that too is ugly.)
+scan_string1([Q|Cs], Line, Col, Q, Str0, Wcs0) ->
+ Wcs = lists:reverse(Wcs0),
+ Str = [Q|lists:reverse(Str0, [Q])],
+ {Cs,Line,incr_column(Col, 1),Str,Wcs};
+scan_string1([$\n=C|Cs], Line, Col, Q, Str, Wcs) ->
+ Ncol = new_column(Col, 1),
+ scan_string1(Cs, Line+1, Ncol, Q, [C|Str], [C|Wcs]);
+scan_string1([$\\|Cs]=Cs0, Line, Col, Q, Str, Wcs) ->
+ case scan_escape(Cs, Col) of
+ more ->
+ {more,Cs0,Line,Col,Str,Wcs};
+ {error,Ncs,Error,Ncol} ->
+ {char_error,Ncs,Error,Line,Col,incr_column(Ncol, 1)};
+ {eof,Ncol} ->
+ {error,Line,incr_column(Ncol, 1),lists:reverse(Wcs),eof};
+ {nl,Val,ValStr,Ncs,Ncol} ->
+ Nstr = lists:reverse(ValStr, [$\\|Str]),
+ Nwcs = [Val|Wcs],
+ scan_string1(Ncs, Line+1, Ncol, Q, Nstr, Nwcs);
+ {Val,ValStr,Ncs,Ncol} ->
+ Nstr = lists:reverse(ValStr, [$\\|Str]),
+ Nwcs = [Val|Wcs],
+ scan_string1(Ncs, Line, incr_column(Ncol, 1), Q, Nstr, Nwcs)
+ end;
+scan_string1([C|Cs], Line, no_col=Col, Q, Str, Wcs) when ?UNICODE(C) ->
+ scan_string1(Cs, Line, Col, Q, [C|Str], [C|Wcs]);
+scan_string1([C|Cs], Line, Col, Q, Str, Wcs) when ?UNICODE(C) ->
+ scan_string1(Cs, Line, Col+1, Q, [C|Str], [C|Wcs]);
+scan_string1([C|Cs], Line, Col, _Q, _Str, _Wcs) when ?CHAR(C) ->
+ {char_error,Cs,{illegal,character},Line,Col,incr_column(Col, 1)};
+scan_string1([]=Cs, Line, Col, _Q, Str, Wcs) ->
+ {more,Cs,Line,Col,Str,Wcs};
+scan_string1(eof, Line, Col, _Q, _Str, Wcs) ->
+ {error,Line,Col,lists:reverse(Wcs),eof}.
+
+-define(OCT(C), C >= $0, C =< $7).
+-define(HEX(C), C >= $0 andalso C =< $9 orelse
+ C >= $A andalso C =< $F orelse
+ C >= $a andalso C =< $f).
+
+%% \<1-3> octal digits
+scan_escape([O1,O2,O3|Cs], Col) when ?OCT(O1), ?OCT(O2), ?OCT(O3) ->
+ Val = (O1*8 + O2)*8 + O3 - 73*$0,
+ {Val,[O1,O2,O3],Cs,incr_column(Col, 3)};
+scan_escape([O1,O2], _Col) when ?OCT(O1), ?OCT(O2) ->
+ more;
+scan_escape([O1,O2|Cs], Col) when ?OCT(O1), ?OCT(O2) ->
+ Val = (O1*8 + O2) - 9*$0,
+ {Val,[O1,O2],Cs,incr_column(Col, 2)};
+scan_escape([O1], _Col) when ?OCT(O1) ->
+ more;
+scan_escape([O1|Cs], Col) when ?OCT(O1) ->
+ {O1 - $0,[O1],Cs,incr_column(Col, 1)};
+%% \x{<hex digits>}
+scan_escape([$x,${|Cs], Col) ->
+ scan_hex(Cs, incr_column(Col, 2), []);
+scan_escape([$x], _Col) ->
+ more;
+scan_escape([$x|eof], Col) ->
+ {eof,incr_column(Col, 1)};
+%% \x<2> hexadecimal digits
+scan_escape([$x,H1,H2|Cs], Col) when ?HEX(H1), ?HEX(H2) ->
+ Val = erlang:list_to_integer([H1,H2], 16),
+ {Val,[$x,H1,H2],Cs,incr_column(Col, 3)};
+scan_escape([$x,H1], _Col) when ?HEX(H1) ->
+ more;
+scan_escape([$x|Cs], Col) ->
+ {error,Cs,{illegal,character},incr_column(Col, 1)};
+%% \^X -> CTL-X
+scan_escape([$^=C0,$\n=C|Cs], Col) ->
+ {nl,C,[C0,C],Cs,new_column(Col, 1)};
+scan_escape([$^=C0,C|Cs], Col) when ?CHAR(C) ->
+ Val = C band 31,
+ {Val,[C0,C],Cs,incr_column(Col, 2)};
+scan_escape([$^], _Col) ->
+ more;
+scan_escape([$^|eof], Col) ->
+ {eof,incr_column(Col, 1)};
+scan_escape([$\n=C|Cs], Col) ->
+ {nl,C,[C],Cs,new_column(Col, 1)};
+scan_escape([C0|Cs], Col) when ?UNICODE(C0) ->
+ C = escape_char(C0),
+ {C,[C0],Cs,incr_column(Col, 1)};
+scan_escape([C|Cs], Col) when ?CHAR(C) ->
+ {error,Cs,{illegal,character},incr_column(Col, 1)};
+scan_escape([], _Col) ->
+ more;
+scan_escape(eof, Col) ->
+ {eof,Col}.
+
+scan_hex([C|Cs], no_col=Col, Wcs) when ?HEX(C) ->
+ scan_hex(Cs, Col, [C|Wcs]);
+scan_hex([C|Cs], Col, Wcs) when ?HEX(C) ->
+ scan_hex(Cs, Col+1, [C|Wcs]);
+scan_hex(Cs, Col, Wcs) ->
+ scan_esc_end(Cs, Col, Wcs, 16, "x{").
+
+scan_esc_end([$}|Cs], Col, Wcs0, B, Str0) ->
+ Wcs = lists:reverse(Wcs0),
+ case catch erlang:list_to_integer(Wcs, B) of
+ Val when ?UNICODE(Val) ->
+ {Val,Str0++Wcs++[$}],Cs,incr_column(Col, 1)};
+ _ ->
+ {error,Cs,{illegal,character},incr_column(Col, 1)}
+ end;
+scan_esc_end([], _Col, _Wcs, _B, _Str0) ->
+ more;
+scan_esc_end(eof, Col, _Wcs, _B, _Str0) ->
+ {eof,Col};
+scan_esc_end(Cs, Col, _Wcs, _B, _Str0) ->
+ {error,Cs,{illegal,character},Col}.
+
+escape_char($n) -> $\n; % \n = LF
+escape_char($r) -> $\r; % \r = CR
+escape_char($t) -> $\t; % \t = TAB
+escape_char($v) -> $\v; % \v = VT
+escape_char($b) -> $\b; % \b = BS
+escape_char($f) -> $\f; % \f = FF
+escape_char($e) -> $\e; % \e = ESC
+escape_char($s) -> $\s; % \s = SPC
+escape_char($d) -> $\d; % \d = DEL
+escape_char(C) -> C.
+
+scan_number([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
+ scan_number(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_number([$.,C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
+ scan_fraction(Cs, St, Line, Col, Toks, [C,$.|Ncs]);
+scan_number([$.]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_number/6}};
+scan_number([$#|Cs]=Cs0, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ case catch list_to_integer(Ncs) of
+ B when B >= 2, B =< 1+$Z-$A+10 ->
+ Bcs = ?STR(St, Ncs++[$#]),
+ scan_based_int(Cs, St, Line, Col, Toks, {B,[],Bcs});
+ B ->
+ Len = length(Ncs),
+ scan_error({base,B}, Line, Col, Line, incr_column(Col, Len), Cs0)
+ end;
+scan_number([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_number/6}};
+scan_number(Cs, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ case catch list_to_integer(Ncs) of
+ N when is_integer(N) ->
+ tok3(Cs, St, Line, Col, Toks, integer, Ncs, N);
+ _ ->
+ Ncol = incr_column(Col, length(Ncs)),
+ scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
+ end.
+
+scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
+ when ?DIGIT(C), C < $0+B ->
+ scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
+scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
+ when C >= $A, B > 10, C < $A+B-10 ->
+ scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
+scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
+ when C >= $a, B > 10, C < $a+B-10 ->
+ scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
+scan_based_int([]=Cs, _St, Line, Col, Toks, State) ->
+ {more,{Cs,Col,Toks,Line,State,fun scan_based_int/6}};
+scan_based_int(Cs, St, Line, Col, Toks, {B,Ncs0,Bcs}) ->
+ Ncs = lists:reverse(Ncs0),
+ case catch erlang:list_to_integer(Ncs, B) of
+ N when is_integer(N) ->
+ tok3(Cs, St, Line, Col, Toks, integer, ?STR(St, Bcs++Ncs), N);
+ _ ->
+ Len = length(Bcs)+length(Ncs),
+ Ncol = incr_column(Col, Len),
+ scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
+ end.
+
+scan_fraction([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
+ scan_fraction(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_fraction([E|Cs], St, Line, Col, Toks, Ncs) when E =:= $e; E =:= $E ->
+ scan_exponent_sign(Cs, St, Line, Col, Toks, [E|Ncs]);
+scan_fraction([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_fraction/6}};
+scan_fraction(Cs, St, Line, Col, Toks, Ncs) ->
+ float_end(Cs, St, Line, Col, Toks, Ncs).
+
+scan_exponent_sign([C|Cs], St, Line, Col, Toks, Ncs) when C =:= $+; C =:= $- ->
+ scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_exponent_sign([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_exponent_sign/6}};
+scan_exponent_sign(Cs, St, Line, Col, Toks, Ncs) ->
+ scan_exponent(Cs, St, Line, Col, Toks, Ncs).
+
+scan_exponent([C|Cs], St, Line, Col, Toks, Ncs) when ?DIGIT(C) ->
+ scan_exponent(Cs, St, Line, Col, Toks, [C|Ncs]);
+scan_exponent([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_exponent/6}};
+scan_exponent(Cs, St, Line, Col, Toks, Ncs) ->
+ float_end(Cs, St, Line, Col, Toks, Ncs).
+
+float_end(Cs, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ case catch list_to_float(Ncs) of
+ F when is_float(F) ->
+ tok3(Cs, St, Line, Col, Toks, float, Ncs, F);
+ _ ->
+ Ncol = incr_column(Col, length(Ncs)),
+ scan_error({illegal,float}, Line, Col, Line, Ncol, Cs)
+ end.
+
+skip_comment([C|Cs], St, Line, Col, Toks, N) when C =/= $\n, ?CHAR(C) ->
+ case ?UNICODE(C) of
+ true ->
+ skip_comment(Cs, St, Line, Col, Toks, N+1);
+ false ->
+ Ncol = incr_column(Col, N+1),
+ scan_error({illegal,character}, Line, Col, Line, Ncol, Cs)
+ end;
+skip_comment([]=Cs, _St, Line, Col, Toks, N) ->
+ {more,{Cs,Col,Toks,Line,N,fun skip_comment/6}};
+skip_comment(Cs, St, Line, Col, Toks, N) ->
+ scan1(Cs, St, Line, incr_column(Col, N), Toks).
+
+scan_comment([C|Cs], St, Line, Col, Toks, Ncs) when C =/= $\n, ?CHAR(C) ->
+ case ?UNICODE(C) of
+ true ->
+ scan_comment(Cs, St, Line, Col, Toks, [C|Ncs]);
+ false ->
+ Ncol = incr_column(Col, length(Ncs)+1),
+ scan_error({illegal,character}, Line, Col, Line, Ncol, Cs)
+ end;
+scan_comment([]=Cs, _St, Line, Col, Toks, Ncs) ->
+ {more,{Cs,Col,Toks,Line,Ncs,fun scan_comment/6}};
+scan_comment(Cs, St, Line, Col, Toks, Ncs0) ->
+ Ncs = lists:reverse(Ncs0),
+ tok3(Cs, St, Line, Col, Toks, comment, Ncs, Ncs).
+
+tok2(Cs, #erl_scan{text = false}=St, Line, no_col=Col, Toks, _Wcs, P) ->
+ scan1(Cs, St, Line, Col, [{P,Line}|Toks]);
+tok2(Cs, St, Line, Col, Toks, Wcs, P) ->
+ Attrs = attributes(Line, Col, St, Wcs),
+ scan1(Cs, St, Line, incr_column(Col, length(Wcs)), [{P,Attrs}|Toks]).
+
+tok2(Cs, #erl_scan{text = false}=St, Line, no_col=Col, Toks, _Wcs, P, _N) ->
+ scan1(Cs, St, Line, Col, [{P,Line}|Toks]);
+tok2(Cs, St, Line, Col, Toks, Wcs, P, N) ->
+ Attrs = attributes(Line, Col, St, Wcs),
+ scan1(Cs, St, Line, incr_column(Col, N), [{P,Attrs}|Toks]).
+
+tok3(Cs, #erl_scan{text = false}=St, Line, no_col=Col, Toks, Item, _S, Sym) ->
+ scan1(Cs, St, Line, Col, [{Item,Line,Sym}|Toks]);
+tok3(Cs, St, Line, Col, Toks, Item, String, Sym) ->
+ Token = {Item,attributes(Line, Col, St, String),Sym},
+ scan1(Cs, St, Line, incr_column(Col, length(String)), [Token|Toks]).
+
+tok3(Cs, #erl_scan{text = false}=St, Line, no_col=Col, Toks, Item,
+ _String, Sym, _Length) ->
+ scan1(Cs, St, Line, Col, [{Item,Line,Sym}|Toks]);
+tok3(Cs, St, Line, Col, Toks, Item, String, Sym, Length) ->
+ Token = {Item,attributes(Line, Col, St, String),Sym},
+ scan1(Cs, St, Line, incr_column(Col, Length), [Token|Toks]).
+
+scan_error(Error, Line, Col, EndLine, EndCol, Rest) ->
+ Loc = location(Line, Col),
+ EndLoc = location(EndLine, EndCol),
+ scan_error(Error, Loc, EndLoc, Rest).
+
+scan_error(Error, ErrorLoc, EndLoc, Rest) ->
+ {{error,{ErrorLoc,?MODULE,Error},EndLoc},Rest}.
+
+-compile({inline,[attributes/4]}).
+
+attributes(Line, no_col, #erl_scan{text = false}, _String) ->
+ Line;
+attributes(Line, no_col, #erl_scan{text = true}, String) ->
+ [{line,Line},{text,String}];
+attributes(Line, Col, #erl_scan{text = false}, _String) ->
+ {Line,Col};
+attributes(Line, Col, #erl_scan{text = true}, String) ->
+ [{line,Line},{column,Col},{text,String}].
+
+location(Line, no_col) ->
+ Line;
+location(Line, Col) when is_integer(Col) ->
+ {Line,Col}.
+
+-compile({inline,[incr_column/2,new_column/2]}).
+
+incr_column(no_col=Col, _N) ->
+ Col;
+incr_column(Col, N) when is_integer(Col) ->
+ Col + N.
+
+new_column(no_col=Col, _Ncol) ->
+ Col;
+new_column(Col, Ncol) when is_integer(Col) ->
+ Ncol.
+
+nl_spcs(2) -> "\n ";
+nl_spcs(3) -> "\n ";
+nl_spcs(4) -> "\n ";
+nl_spcs(5) -> "\n ";
+nl_spcs(6) -> "\n ";
+nl_spcs(7) -> "\n ";
+nl_spcs(8) -> "\n ";
+nl_spcs(9) -> "\n ";
+nl_spcs(10) -> "\n ";
+nl_spcs(11) -> "\n ";
+nl_spcs(12) -> "\n ";
+nl_spcs(13) -> "\n ";
+nl_spcs(14) -> "\n ";
+nl_spcs(15) -> "\n ";
+nl_spcs(16) -> "\n ";
+nl_spcs(17) -> "\n ".
+
+spcs(1) -> " ";
+spcs(2) -> " ";
+spcs(3) -> " ";
+spcs(4) -> " ";
+spcs(5) -> " ";
+spcs(6) -> " ";
+spcs(7) -> " ";
+spcs(8) -> " ";
+spcs(9) -> " ";
+spcs(10) -> " ";
+spcs(11) -> " ";
+spcs(12) -> " ";
+spcs(13) -> " ";
+spcs(14) -> " ";
+spcs(15) -> " ";
+spcs(16) -> " ".
+
+nl_tabs(2) -> "\n\t";
+nl_tabs(3) -> "\n\t\t";
+nl_tabs(4) -> "\n\t\t\t";
+nl_tabs(5) -> "\n\t\t\t\t";
+nl_tabs(6) -> "\n\t\t\t\t\t";
+nl_tabs(7) -> "\n\t\t\t\t\t\t";
+nl_tabs(8) -> "\n\t\t\t\t\t\t\t";
+nl_tabs(9) -> "\n\t\t\t\t\t\t\t\t";
+nl_tabs(10) -> "\n\t\t\t\t\t\t\t\t\t";
+nl_tabs(11) -> "\n\t\t\t\t\t\t\t\t\t\t".
+
+tabs(1) -> "\t";
+tabs(2) -> "\t\t";
+tabs(3) -> "\t\t\t";
+tabs(4) -> "\t\t\t\t";
+tabs(5) -> "\t\t\t\t\t";
+tabs(6) -> "\t\t\t\t\t\t";
+tabs(7) -> "\t\t\t\t\t\t\t";
+tabs(8) -> "\t\t\t\t\t\t\t\t";
+tabs(9) -> "\t\t\t\t\t\t\t\t\t";
+tabs(10) -> "\t\t\t\t\t\t\t\t\t\t".
+
+-spec reserved_word(Atom :: atom()) -> boolean().
+reserved_word('after') -> true;
+reserved_word('begin') -> true;
+reserved_word('case') -> true;
+reserved_word('try') -> true;
+reserved_word('cond') -> true;
+reserved_word('catch') -> true;
+reserved_word('andalso') -> true;
+reserved_word('orelse') -> true;
+reserved_word('end') -> true;
+reserved_word('fun') -> true;
+reserved_word('if') -> true;
+reserved_word('let') -> true;
+reserved_word('of') -> true;
+reserved_word('receive') -> true;
+reserved_word('when') -> true;
+reserved_word('bnot') -> true;
+reserved_word('not') -> true;
+reserved_word('div') -> true;
+reserved_word('rem') -> true;
+reserved_word('band') -> true;
+reserved_word('and') -> true;
+reserved_word('bor') -> true;
+reserved_word('bxor') -> true;
+reserved_word('bsl') -> true;
+reserved_word('bsr') -> true;
+reserved_word('or') -> true;
+reserved_word('xor') -> true;
+reserved_word(_) -> false.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl b/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl
index 9e695cec1d..e9f7ad825b 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl
@@ -2,7 +2,7 @@
-export([weird/1]).
--spec weird(dict() | gb_tree()) -> 42.
+-spec weird(dict:dict() | gb_trees:tree()) -> 42.
weird(gazonk) -> 42.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_adt.erl
index 3456f0e9c6..cdcaa5f9e8 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_adt.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_adt.erl
@@ -3,6 +3,8 @@
-opaque abc() :: 'a' | 'b' | 'c'.
+-spec atom_or_list(_) -> abc() | list().
+
atom_or_list(1) -> a;
atom_or_list(2) -> b;
atom_or_list(3) -> c;
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_bug5.erl b/lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_bug5.erl
new file mode 100644
index 0000000000..28d739de8e
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/opaque/opaque_bug5.erl
@@ -0,0 +1,10 @@
+%% Second arg of is_record call wasn't checked properly
+
+-module(opaque_bug5).
+
+-export([b/0]).
+
+b() ->
+ is_record(id({a}), id(a)).
+
+id(I) -> I.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/myqueue.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/myqueue.erl
new file mode 100644
index 0000000000..0f76680464
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/myqueue.erl
@@ -0,0 +1,17 @@
+-module(myqueue).
+
+-export([new/0, in/2]).
+
+-record(myqueue, {queue = queue:new() :: queue:queue({integer(), _})}).
+
+-opaque myqueue(Item) :: #myqueue{queue :: queue:queue({integer(), Item})}.
+
+-export_type([myqueue/1]).
+
+-spec new() -> myqueue(_).
+new() ->
+ #myqueue{queue=queue:new()}.
+
+-spec in(Item, myqueue(Item)) -> myqueue(Item).
+in(Item, #myqueue{queue=Q}) ->
+ #myqueue{queue=queue:in({1, Item}, Q)}.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/myqueue_params.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/myqueue_params.erl
new file mode 100644
index 0000000000..8d766b7804
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/myqueue_params.erl
@@ -0,0 +1,15 @@
+-module(myqueue_params).
+
+-export([new/0, in/2]).
+
+-record(myqueue_params, {myqueue = myqueue:new() :: myqueue:myqueue(integer())}).
+
+-type myqueue_params() :: #myqueue_params{myqueue ::
+ myqueue:myqueue(integer())}.
+-spec new() -> myqueue_params().
+new() ->
+ #myqueue_params{myqueue=myqueue:new()}.
+
+-spec in(integer(), myqueue_params()) -> myqueue_params().
+in(Item, #myqueue_params{myqueue=Q} = P) when is_integer(Item) ->
+ P#myqueue_params{myqueue=myqueue:in(Item, Q)}.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para1.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para1.erl
new file mode 100644
index 0000000000..68e2c60368
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para1.erl
@@ -0,0 +1,93 @@
+-module(para1).
+
+-compile(export_all).
+
+%% Parameterized opaque types
+
+-export_type([t/0, t/1]).
+
+-opaque t() :: {integer(), integer()}.
+
+-opaque t(A) :: {A, A}.
+
+-type y(A) :: {A, A}.
+
+tt1() ->
+ I = t1(),
+ A = t2(),
+ A =:= I. % never 'true'
+
+tt2() ->
+ I = t0(),
+ A = t2(),
+ A =:= I. % never 'true'
+
+tt3() ->
+ I1 = t0(),
+ I2 = t1(),
+ I1 =:= I2. % never true
+
+tt4() ->
+ I1 = y1(),
+ I2 = y2(),
+ I1 =:= I2. % cannot evaluate to true
+
+adt_tt1() ->
+ I = adt_t1(),
+ A = adt_t2(),
+ A =:= I. % opaque attempt
+
+adt_tt2() ->
+ I = adt_t0(),
+ A = adt_t2(),
+ A =:= I. % opaque attempt
+
+adt_tt3() ->
+ I1 = adt_t0(),
+ I2 = adt_t1(),
+ I1 =:= I2. % opaque attempt
+
+adt_tt4() ->
+ I1 = adt_y1(),
+ I2 = adt_y2(),
+ I1 =:= I2. % cannot evaluate to true
+
+-spec t0() -> t().
+
+t0() ->
+ {3, 2}.
+
+-spec t1() -> t(integer()).
+
+t1() ->
+ {3, 3}.
+
+-spec t2() -> t(atom()).
+
+t2() ->
+ {a, b}.
+
+-spec y1() -> y(integer()).
+
+y1() ->
+ {3, 2}.
+
+-spec y2() -> y(atom()).
+
+y2() ->
+ {a, b}.
+
+adt_t0() ->
+ para1_adt:t0().
+
+adt_t1() ->
+ para1_adt:t1().
+
+adt_t2() ->
+ para1_adt:t2().
+
+adt_y1() ->
+ para1_adt:y1().
+
+adt_y2() ->
+ para1_adt:y2().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para1_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para1_adt.erl
new file mode 100644
index 0000000000..95ac6b7982
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para1_adt.erl
@@ -0,0 +1,36 @@
+-module(para1_adt).
+
+-export([t0/0, t1/0, t2/0, y1/0, y2/0]).
+
+-export_type([t/0, t/1, y/1]).
+
+-opaque t() :: {integer(), integer()}.
+
+-opaque t(A) :: {A, A}.
+
+-type y(A) :: {A, A}.
+
+-spec t0() -> t().
+
+t0() ->
+ {3, 2}.
+
+-spec t1() -> t(integer()).
+
+t1() ->
+ {3, 3}.
+
+-spec t2() -> t(atom()).
+
+t2() ->
+ {a, b}.
+
+-spec y1() -> y(integer()).
+
+y1() ->
+ {3, 2}.
+
+-spec y2() -> y(atom()).
+
+y2() ->
+ {a, b}.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para2.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para2.erl
new file mode 100644
index 0000000000..4461ff291c
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para2.erl
@@ -0,0 +1,123 @@
+-module(para2).
+
+-compile(export_all).
+
+%% More parameterized opaque types
+
+-export_type([strange/1]).
+
+-export_type([c1/0, c2/0]).
+
+-export_type([circ/1, circ/2]).
+
+-opaque strange(A) :: {B, B, A}.
+
+-spec t(strange(integer())) -> strange(atom()).
+
+t({3, 4, 5}) ->
+ {a, b, c}.
+
+-opaque c1() :: c2().
+-opaque c2() :: c1().
+
+c() ->
+ A = c1(),
+ B = c2(),
+ A =:= B.
+
+t() ->
+ A = ct1(),
+ B = ct2(),
+ A =:= B. % can never evaluate to 'true'
+
+-spec c1() -> c1().
+
+c1() ->
+ a.
+
+-spec c2() -> c2().
+
+c2() ->
+ a.
+
+-type ct1() :: ct2().
+-type ct2() :: ct1().
+
+-spec ct1() -> ct1().
+
+ct1() ->
+ a.
+
+-spec ct2() -> ct2().
+
+ct2() ->
+ b.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+c_adt() ->
+ A = c1_adt(),
+ B = c2_adt(),
+ A =:= B. % opaque attempt
+
+t_adt() ->
+ A = ct1_adt(),
+ B = ct2_adt(),
+ A =:= B. % can never evaluate to true
+
+c1_adt() ->
+ para2_adt:c1().
+
+c2_adt() ->
+ para2_adt:c2().
+
+ct1_adt() ->
+ para2_adt:ct1().
+
+ct2_adt() ->
+ para2_adt:ct2().
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-opaque circ(A) :: circ(A, A).
+-opaque circ(A, B) :: circ({A, B}).
+
+tcirc() ->
+ A = circ1(),
+ B = circ2(),
+ A =:= B. % can never evaluate to 'true'
+
+-spec circ1() -> circ(integer()).
+
+circ1() ->
+ 3.
+
+-spec circ2() -> circ(integer(), integer()).
+
+circ2() ->
+ {3, 3}.
+
+tcirc_adt() ->
+ A = circ1_adt(),
+ B = circ2_adt(),
+ A =:= B. % opaque attempt (number of parameters differs)
+
+circ1_adt() ->
+ para2_adt:circ1().
+
+circ2_adt() ->
+ para2_adt:circ2().
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+u_adt() ->
+ A = u1_adt(),
+ B = u2_adt(),
+ %% The resulting types are equal, but not the parameters:
+ A =:= B. % opaque attempt
+
+u1_adt() ->
+ para2_adt:u1().
+
+u2_adt() ->
+ para2_adt:u2().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para2_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para2_adt.erl
new file mode 100644
index 0000000000..96df437c67
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para2_adt.erl
@@ -0,0 +1,64 @@
+-module(para2_adt).
+
+%% More parameterized opaque types
+
+-export_type([c1/0, c2/0]).
+
+-export_type([ct1/0, ct2/0]).
+
+-export_type([circ/1, circ/2]).
+
+-export_type([un/2]).
+
+-export([c1/0, c2/0, ct1/0, ct2/0, circ1/0, circ2/0, u1/0, u2/0]).
+
+-opaque c1() :: c2().
+-opaque c2() :: c1().
+
+-spec c1() -> c1().
+
+c1() ->
+ a.
+
+-spec c2() -> c2().
+
+c2() ->
+ a.
+
+-type ct1() :: ct2().
+-type ct2() :: ct1().
+
+-spec ct1() -> ct1().
+
+ct1() ->
+ a.
+
+-spec ct2() -> ct2().
+
+ct2() ->
+ b.
+
+-opaque circ(A) :: circ(A, A).
+-opaque circ(A, B) :: circ({A, B}).
+
+-spec circ1() -> circ(integer()).
+
+circ1() ->
+ 3.
+
+-spec circ2() -> circ(integer(), integer()).
+
+circ2() ->
+ {3, 3}.
+
+-opaque un(A, B) :: A | B.
+
+-spec u1() -> un(integer(), atom()).
+
+u1() ->
+ 3.
+
+-spec u2() -> un(atom(), integer()).
+
+u2() ->
+ 3.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para3.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para3.erl
new file mode 100644
index 0000000000..102215b28d
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para3.erl
@@ -0,0 +1,77 @@
+-module(para3).
+
+-export([t/0, t1/1, t2/0, ot1/1, ot2/0, t1_adt/0, t2_adt/0]).
+
+-export([exp_adt/0]).
+
+%% More opaque tests.
+
+-export_type([ot1/0, ot1/1, ot1/2, ot1/3, ot1/4, ot1/5]).
+
+-opaque ot1() :: {ot1(_)}.
+
+-opaque ot1(A) :: {ot1(A, A)}.
+
+-opaque ot1(A, B) :: {ot1(A, B, A)}.
+
+-opaque ot1(A, B, C) :: {ot1(A, B, C, A)}.
+
+-opaque ot1(A, B, C, D) :: {ot1(A, B, C, D, A)}.
+
+-opaque ot1(A, B, C, D, E) :: {A, B, C, D, E}.
+
+-spec ot1(_) -> ot1().
+
+ot1(A) ->
+ {{{{{A, A, A, A, A}}}}}.
+
+-spec ot2() -> ot1(). % invalid type spec
+
+ot2() ->
+ foo.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+t() ->
+ {{{17}}} = t1(3). %% pattern can never match
+
+-type t1() :: {t1(_)}.
+
+-type t1(A) :: {t1(A, A)}.
+
+-type t1(A, B) :: {t1(A, B, A)}.
+
+-type t1(A, B, C) :: {t1(A, B, C, A)}.
+
+-type t1(A, B, C, D) :: {t1(A, B, C, D, A)}.
+
+-type t1(A, B, C, D, E) :: {A, B, C, D, E}.
+
+-spec t1(_) -> t1().
+
+t1(A) ->
+ {{{{{A, A, A, A, A}}}}}.
+
+-spec t2() -> t1(). % invalid type spec
+
+t2() ->
+ foo.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Shows that the list TypeNames in t_from_form must include ArgsLen.
+
+t1_adt() ->
+ {{{{{17}}}}} = para3_adt:t1(3). % breaks the opaqueness
+
+t2_adt() ->
+ {{{{17}}}} = para3_adt:t1(3). % can never match
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-type exp() :: para3_adt:exp1(para3_adt:exp2()).
+
+-spec exp_adt() -> exp(). % invalid type spec
+
+exp_adt() ->
+ 3.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para3_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para3_adt.erl
new file mode 100644
index 0000000000..3919b846e6
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para3_adt.erl
@@ -0,0 +1,27 @@
+-module(para3_adt).
+
+-export([t1/1]).
+
+-export_type([t1/0, t1/1, t1/2, t1/3, t1/4, ot1/5]).
+
+-export_type([exp1/1, exp2/0]).
+
+-type t1() :: {t1(_)}.
+
+-type t1(A) :: {t1(A, A)}.
+
+-type t1(A, B) :: {t1(A, B, A)}.
+
+-type t1(A, B, C) :: {t1(A, B, C, A)}.
+
+-type t1(A, B, C, D) :: {ot1(A, B, C, D, A)}.
+
+-opaque ot1(A, B, C, D, E) :: {A, B, C, D, E}.
+
+-spec t1(_) -> t1().
+
+t1(A) ->
+ {{{{{A, A, A, A, A}}}}}.
+
+-opaque exp1(T) :: T.
+-opaque exp2() :: integer().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl
new file mode 100644
index 0000000000..b9794672a9
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para4.erl
@@ -0,0 +1,134 @@
+-module(para4).
+
+-compile(export_all).
+
+-export_type([d_atom/0, d_integer/0, d_tuple/0, d_all/0]).
+
+-export_type([t/1]).
+
+-type ai() :: atom() | integer().
+
+-type d(T) :: dict:dict(T, T).
+
+-opaque d_atom() :: d(atom()).
+-opaque d_integer() :: d(integer()).
+-opaque d_tuple() :: d(tuple()).
+-opaque d_all() :: d(ai()).
+
+b(D) ->
+ a(D) ++ i(D).
+
+-spec a(d_atom()) -> [{atom(), atom()}]. % Invalid type spec
+
+a(D) ->
+ c(D).
+
+-spec i(d_integer()) -> [{integer(), integer()}]. % Invalid type spec
+
+i(D) ->
+ c(D).
+
+-spec t(d_tuple()) -> [{tuple(), tuple()}]. % Invalid type spec.
+
+t(D) ->
+ c(D).
+
+-spec c(d_all()) -> [{ai(), ai()}].
+
+c(D) ->
+ dict:to_list(D).
+
+
+
+
+-opaque t(A) :: {A, A}.
+
+adt_tt5() ->
+ I1 = adt_y1(),
+ I2 = adt_y3(),
+ I1 =:= I2.
+
+adt_tt6() ->
+ I1 = adt_y2(),
+ I2 = adt_y3(),
+ I1 =:= I2.
+
+adt_tt7() ->
+ I1 = adt_t1(),
+ I2 = adt_t3(),
+ I1 =:= I2. % opaque attempt
+
+adt_tt8() ->
+ I1 = adt_t2(),
+ I2 = adt_t3(),
+ I1 =:= I2. % opaque attempt
+
+adt_tt9() ->
+ I1 = adt_int2(),
+ I2 = adt_int4(),
+ I1 =:= I2. % opaque attempt
+
+adt_tt10() ->
+ I1 = adt_int2(),
+ I2 = adt_int2_4(),
+ I1 =:= I2. % opaque attempt
+
+adt_tt11() ->
+ I1 = adt_int5_7(),
+ I2 = adt_int2_4(),
+ I1 =:= I2. % opaque attempt
+
+adt_tt12() ->
+ I1 = adt_un1_2(),
+ I2 = adt_un3_4(),
+ I1 =:= I2. % opaque attempt
+
+adt_tt13() ->
+ I1 = adt_tup(),
+ I2 = adt_tup2(),
+ I1 =:= I2. % opaque attempt
+
+y3() ->
+ {a, 3}.
+
+adt_t1() ->
+ para4_adt:t1().
+
+adt_t2() ->
+ para4_adt:t2().
+
+adt_t3() ->
+ para4_adt:t3().
+
+adt_y1() ->
+ para4_adt:y1().
+
+adt_y2() ->
+ para4_adt:y2().
+
+adt_y3() ->
+ para4_adt:y3().
+
+adt_int2() ->
+ para4_adt:int2().
+
+adt_int4() ->
+ para4_adt:int4().
+
+adt_int2_4() ->
+ para4_adt:int2_4().
+
+adt_int5_7() ->
+ para4_adt:int5_7().
+
+adt_un1_2() ->
+ para4_adt:un1_2().
+
+adt_un3_4() ->
+ para4_adt:un3_4().
+
+adt_tup() ->
+ para4_adt:tup().
+
+adt_tup2() ->
+ para4_adt:tup2().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl
new file mode 100644
index 0000000000..407dd198a7
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para4_adt.erl
@@ -0,0 +1,108 @@
+-module(para4_adt).
+
+-export([t1/0, t2/0, t3/0, y1/0, y2/0, y3/0]).
+
+-export([int2/0, int4/0, int2_4/0, int5_7/0]).
+
+-export([un1_2/0, un3_4/0]).
+
+-export([tup/0, tup2/0]).
+
+-export_type([t/1, y/1, int/1, tup/1, un/1]).
+
+-type ai() :: atom() | integer().
+
+-opaque t(A) :: {A, A}.
+
+-type y(A) :: {A, A}.
+
+-opaque int(I) :: I.
+
+-opaque un(I) :: atom() | I.
+
+-opaque tup(T) :: T.
+
+-spec t1() -> t(integer()).
+
+t1() ->
+ {i(), i()}.
+
+-spec t2() -> t(atom()).
+
+t2() ->
+ {a(), a()}.
+
+-spec t3() -> t(ai()).
+
+t3() ->
+ {ai(), ai()}.
+
+-spec y1() -> y(integer()).
+
+y1() ->
+ {i(), i()}.
+
+-spec y2() -> y(atom()).
+
+y2() ->
+ {a(), a()}.
+
+-spec y3() -> y(ai()).
+
+y3() ->
+ {ai(), ai()}.
+
+-spec a() -> atom().
+
+a() ->
+ foo:a().
+
+-spec i() -> integer().
+
+i() ->
+ foo:i().
+
+-spec ai() -> ai().
+
+ai() ->
+ foo:ai().
+
+-spec int2() -> int(1..2).
+
+int2() ->
+ foo:int2().
+
+-spec int4() -> int(1..4).
+
+int4() ->
+ foo:int4().
+
+-spec int2_4() -> int(2..4).
+
+int2_4() ->
+ foo:int2_4().
+
+-spec int5_7() -> int(5..7).
+
+int5_7() ->
+ foo:int5_7().
+
+-spec un1_2() -> un(1..2).
+
+un1_2() ->
+ foo:un1_2().
+
+-spec un3_4() -> un(3..4).
+
+un3_4() ->
+ foo:un3_4().
+
+-spec tup() -> tup(tuple()).
+
+tup() ->
+ foo:tup().
+
+-spec tup2() -> tup({_, _}).
+
+tup2() ->
+ foo:tup2().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para5.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para5.erl
new file mode 100644
index 0000000000..76ea3e76b5
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para5.erl
@@ -0,0 +1,33 @@
+-module(para5).
+
+-export([d/0, dd/0, da1/0]).
+
+d() ->
+ I1 = adt_d1(),
+ I2 = adt_d2(),
+ I1 =:= I2. % can never evaluate to true
+
+dd() ->
+ I1 = adt_d1(),
+ I2 = adt_dd(),
+ I1 =/= I2. % incompatible opaque types
+
+da1() ->
+ I1 = adt_da1(),
+ I2 = adt_da2(),
+ I1 =:= I2.
+
+adt_d1() ->
+ para5_adt:d1().
+
+adt_d2() ->
+ para5_adt:d2().
+
+adt_dd() ->
+ para5_adt:dd().
+
+adt_da1() ->
+ para5_adt:da1().
+
+adt_da2() ->
+ para5_adt:da2().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/para/para5_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/para/para5_adt.erl
new file mode 100644
index 0000000000..a62e0488e0
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/para/para5_adt.erl
@@ -0,0 +1,36 @@
+-module(para5_adt).
+
+-export([d1/0, d2/0, dd/0, da1/0, da2/0]).
+
+-export_type([d/0, dd/1, da/2]).
+
+-opaque d() :: 1 | 2.
+
+-spec d1() -> d().
+
+d1() ->
+ 1.
+
+-spec d2() -> d().
+
+d2() ->
+ 2.
+
+-opaque dd(A) :: A.
+
+-spec dd() -> dd(atom()).
+
+dd() ->
+ foo:atom().
+
+-opaque da(A, B) :: {A, B}.
+
+-spec da1() -> da(any(), atom()).
+
+da1() ->
+ {3, a}.
+
+-spec da2() -> da(integer(), any()).
+
+da2() ->
+ {3, a}.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_common.hrl b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_common.hrl
new file mode 100644
index 0000000000..c10626c5cc
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_common.hrl
@@ -0,0 +1,55 @@
+%%% Copyright 2010-2013 Manolis Papadakis <manopapad@gmail.com>,
+%%% Eirini Arvaniti <eirinibob@gmail.com>
+%%% and Kostis Sagonas <kostis@cs.ntua.gr>
+%%%
+%%% This file is part of PropEr.
+%%%
+%%% PropEr is free software: you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation, either version 3 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% PropEr is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.
+
+%%% @copyright 2010-2013 Manolis Papadakis, Eirini Arvaniti and Kostis Sagonas
+%%% @version {@version}
+%%% @author Manolis Papadakis
+%%% @doc Common parts of user and internal header files
+
+
+%%------------------------------------------------------------------------------
+%% Test generation macros
+%%------------------------------------------------------------------------------
+
+-define(FORALL(X,RawType,Prop), proper:forall(RawType,fun(X) -> Prop end)).
+-define(IMPLIES(Pre,Prop), proper:implies(Pre,?DELAY(Prop))).
+-define(WHENFAIL(Action,Prop), proper:whenfail(?DELAY(Action),?DELAY(Prop))).
+-define(TRAPEXIT(Prop), proper:trapexit(?DELAY(Prop))).
+-define(TIMEOUT(Limit,Prop), proper:timeout(Limit,?DELAY(Prop))).
+%% TODO: -define(ALWAYS(Tests,Prop), proper:always(Tests,?DELAY(Prop))).
+%% TODO: -define(SOMETIMES(Tests,Prop), proper:sometimes(Tests,?DELAY(Prop))).
+
+
+%%------------------------------------------------------------------------------
+%% Generator macros
+%%------------------------------------------------------------------------------
+
+-define(FORCE(X), (X)()).
+-define(DELAY(X), fun() -> X end).
+-define(LAZY(X), proper_types:lazy(?DELAY(X))).
+-define(SIZED(SizeArg,Gen), proper_types:sized(fun(SizeArg) -> Gen end)).
+-define(LET(X,RawType,Gen), proper_types:bind(RawType,fun(X) -> Gen end,false)).
+-define(SHRINK(Gen,AltGens),
+ proper_types:shrinkwith(?DELAY(Gen),?DELAY(AltGens))).
+-define(LETSHRINK(Xs,RawType,Gen),
+ proper_types:bind(RawType,fun(Xs) -> Gen end,true)).
+-define(SUCHTHAT(X,RawType,Condition),
+ proper_types:add_constraint(RawType,fun(X) -> Condition end,true)).
+-define(SUCHTHATMAYBE(X,RawType,Condition),
+ proper_types:add_constraint(RawType,fun(X) -> Condition end,false)).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_gen.erl b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_gen.erl
new file mode 100644
index 0000000000..bf627d1373
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_gen.erl
@@ -0,0 +1,624 @@
+%%% Copyright 2010-2013 Manolis Papadakis <manopapad@gmail.com>,
+%%% Eirini Arvaniti <eirinibob@gmail.com>
+%%% and Kostis Sagonas <kostis@cs.ntua.gr>
+%%%
+%%% This file is part of PropEr.
+%%%
+%%% PropEr is free software: you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation, either version 3 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% PropEr is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.
+
+%%% @copyright 2010-2013 Manolis Papadakis, Eirini Arvaniti and Kostis Sagonas
+%%% @version {@version}
+%%% @author Manolis Papadakis
+
+%%% @doc Generator subsystem and generators for basic types.
+%%%
+%%% You can use <a href="#index">these</a> functions to try out the random
+%%% instance generation and shrinking subsystems.
+%%%
+%%% CAUTION: These functions should never be used inside properties. They are
+%%% meant for demonstration purposes only.
+
+-module(proper_gen).
+-export([pick/1, pick/2, pick/3, sample/1, sample/3, sampleshrink/1, sampleshrink/2]).
+
+-export([safe_generate/1]).
+-export([generate/1, normal_gen/1, alt_gens/1, clean_instance/1,
+ get_ret_type/1]).
+-export([integer_gen/3, float_gen/3, atom_gen/1, atom_rev/1, binary_gen/1,
+ binary_rev/1, binary_len_gen/1, bitstring_gen/1, bitstring_rev/1,
+ bitstring_len_gen/1, list_gen/2, distlist_gen/3, vector_gen/2,
+ union_gen/1, weighted_union_gen/1, tuple_gen/1, loose_tuple_gen/2,
+ loose_tuple_rev/2, exactly_gen/1, fixed_list_gen/1, function_gen/2,
+ any_gen/1, native_type_gen/2, safe_weighted_union_gen/1,
+ safe_union_gen/1]).
+
+-export_type([instance/0, imm_instance/0, sized_generator/0, nosize_generator/0,
+ generator/0, reverse_gen/0, combine_fun/0, alt_gens/0]).
+
+-include("proper_internal.hrl").
+
+
+%%-----------------------------------------------------------------------------
+%% Types
+%%-----------------------------------------------------------------------------
+
+%% TODO: update imm_instance() when adding more types: be careful when reading
+%% anything that returns it
+%% @private_type
+-type imm_instance() :: proper_types:raw_type()
+ | instance()
+ | {'$used', imm_instance(), imm_instance()}
+ | {'$to_part', imm_instance()}.
+-type instance() :: term().
+%% A value produced by the random instance generator.
+-type error_reason() :: 'arity_limit' | 'cant_generate' | {'typeserver',term()}.
+
+%% @private_type
+-type sized_generator() :: fun((size()) -> imm_instance()).
+%% @private_type
+-type typed_sized_generator() :: {'typed',
+ fun((proper_types:type(),size()) ->
+ imm_instance())}.
+%% @private_type
+-type nosize_generator() :: fun(() -> imm_instance()).
+%% @private_type
+-type typed_nosize_generator() :: {'typed',
+ fun((proper_types:type()) ->
+ imm_instance())}.
+%% @private_type
+-type generator() :: sized_generator()
+ | typed_sized_generator()
+ | nosize_generator()
+ | typed_nosize_generator().
+%% @private_type
+-type plain_reverse_gen() :: fun((instance()) -> imm_instance()).
+%% @private_type
+-type typed_reverse_gen() :: {'typed',
+ fun((proper_types:type(),instance()) ->
+ imm_instance())}.
+%% @private_type
+-type reverse_gen() :: plain_reverse_gen() | typed_reverse_gen().
+%% @private_type
+-type combine_fun() :: fun((instance()) -> imm_instance()).
+%% @private_type
+-type alt_gens() :: fun(() -> [imm_instance()]).
+%% @private_type
+-type fun_seed() :: {non_neg_integer(),non_neg_integer()}.
+
+
+%%-----------------------------------------------------------------------------
+%% Instance generation functions
+%%-----------------------------------------------------------------------------
+
+%% @private
+-spec safe_generate(proper_types:raw_type()) ->
+ {'ok',imm_instance()} | {'error',error_reason()}.
+safe_generate(RawType) ->
+ try generate(RawType) of
+ ImmInstance -> {ok, ImmInstance}
+ catch
+ throw:'$arity_limit' -> {error, arity_limit};
+ throw:'$cant_generate' -> {error, cant_generate};
+ throw:{'$typeserver',SubReason} -> {error, {typeserver,SubReason}}
+ end.
+
+%% @private
+-spec generate(proper_types:raw_type()) -> imm_instance().
+generate(RawType) ->
+ Type = proper_types:cook_outer(RawType),
+ ok = add_parameters(Type),
+ Instance = generate(Type, get('$constraint_tries'), none),
+ ok = remove_parameters(Type),
+ Instance.
+
+-spec add_parameters(proper_types:type()) -> 'ok'.
+add_parameters(Type) ->
+ case proper_types:find_prop(parameters, Type) of
+ {ok, Params} ->
+ OldParams = erlang:get('$parameters'),
+ case OldParams of
+ undefined ->
+ erlang:put('$parameters', Params);
+ _ ->
+ erlang:put('$parameters', Params ++ OldParams)
+ end,
+ ok;
+ _ ->
+ ok
+ end.
+
+-spec remove_parameters(proper_types:type()) -> 'ok'.
+remove_parameters(Type) ->
+ case proper_types:find_prop(parameters, Type) of
+ {ok, Params} ->
+ AllParams = erlang:get('$parameters'),
+ case AllParams of
+ Params->
+ erlang:erase('$parameters');
+ _ ->
+ erlang:put('$parameters', AllParams -- Params)
+ end,
+ ok;
+ _ ->
+ ok
+ end.
+
+-spec generate(proper_types:type(), non_neg_integer(),
+ 'none' | {'ok',imm_instance()}) -> imm_instance().
+generate(_Type, 0, none) ->
+ throw('$cant_generate');
+generate(_Type, 0, {ok,Fallback}) ->
+ Fallback;
+generate(Type, TriesLeft, Fallback) ->
+ ImmInstance =
+ case proper_types:get_prop(kind, Type) of
+ constructed ->
+ PartsType = proper_types:get_prop(parts_type, Type),
+ Combine = proper_types:get_prop(combine, Type),
+ ImmParts = generate(PartsType),
+ Parts = clean_instance(ImmParts),
+ ImmInstance1 = Combine(Parts),
+ %% TODO: We can just generate the internal type: if it's not
+ %% a type, it will turn into an exactly.
+ ImmInstance2 =
+ case proper_types:is_raw_type(ImmInstance1) of
+ true -> generate(ImmInstance1);
+ false -> ImmInstance1
+ end,
+ {'$used',ImmParts,ImmInstance2};
+ _ ->
+ ImmInstance1 = normal_gen(Type),
+ case proper_types:is_raw_type(ImmInstance1) of
+ true -> generate(ImmInstance1);
+ false -> ImmInstance1
+ end
+ end,
+ case proper_types:satisfies_all(clean_instance(ImmInstance), Type) of
+ {_,true} -> ImmInstance;
+ {true,false} -> generate(Type, TriesLeft - 1, {ok,ImmInstance});
+ {false,false} -> generate(Type, TriesLeft - 1, Fallback)
+ end.
+
+%% @equiv pick(Type, 10)
+-spec pick(Type::proper_types:raw_type()) -> {'ok',instance()} | 'error'.
+pick(RawType) ->
+ pick(RawType, 10).
+
+%% @equiv pick(Type, Size, now())
+-spec pick(Type::proper_types:raw_type(), size()) -> {'ok', instance()} | 'error'.
+pick(RawType, Size) ->
+ pick(RawType, Size, now()).
+
+%% @doc Generates a random instance of `Type', of size `Size' with seed `Seed'.
+-spec pick(Type::proper_types:raw_type(), size(), seed()) ->
+ {'ok',instance()} | 'error'.
+pick(RawType, Size, Seed) ->
+ proper:global_state_init_size_seed(Size, Seed),
+ case clean_instance(safe_generate(RawType)) of
+ {ok,Instance} = Result ->
+ Msg = "WARNING: Some garbage has been left in the process registry "
+ "and the code server~n"
+ "to allow for the returned function(s) to run normally.~n"
+ "Please run proper:global_state_erase() when done.~n",
+ case contains_fun(Instance) of
+ true -> io:format(Msg, []);
+ false -> proper:global_state_erase()
+ end,
+ Result;
+ {error,Reason} ->
+ proper:report_error(Reason, fun io:format/2),
+ proper:global_state_erase(),
+ error
+ end.
+
+%% @equiv sample(Type, 10, 20)
+-spec sample(Type::proper_types:raw_type()) -> 'ok'.
+sample(RawType) ->
+ sample(RawType, 10, 20).
+
+%% @doc Generates and prints one random instance of `Type' for each size from
+%% `StartSize' up to `EndSize'.
+-spec sample(Type::proper_types:raw_type(), size(), size()) -> 'ok'.
+sample(RawType, StartSize, EndSize) when StartSize =< EndSize ->
+ Tests = EndSize - StartSize + 1,
+ Prop = ?FORALL(X, RawType, begin io:format("~p~n",[X]), true end),
+ Opts = [quiet,{start_size,StartSize},{max_size,EndSize},{numtests,Tests}],
+ _ = proper:quickcheck(Prop, Opts),
+ ok.
+
+%% @equiv sampleshrink(Type, 10)
+-spec sampleshrink(Type::proper_types:raw_type()) -> 'ok'.
+sampleshrink(RawType) ->
+ sampleshrink(RawType, 10).
+
+%% @doc Generates a random instance of `Type', of size `Size', then shrinks it
+%% as far as it goes. The value produced on each step of the shrinking process
+%% is printed on the screen.
+-spec sampleshrink(Type::proper_types:raw_type(), size()) -> 'ok'.
+sampleshrink(RawType, Size) ->
+ proper:global_state_init_size(Size),
+ Type = proper_types:cook_outer(RawType),
+ case safe_generate(Type) of
+ {ok,ImmInstance} ->
+ Shrunk = keep_shrinking(ImmInstance, [], Type),
+ PrintInst = fun(I) -> io:format("~p~n",[clean_instance(I)]) end,
+ lists:foreach(PrintInst, Shrunk);
+ {error,Reason} ->
+ proper:report_error(Reason, fun io:format/2)
+ end,
+ proper:global_state_erase(),
+ ok.
+
+-spec keep_shrinking(imm_instance(), [imm_instance()], proper_types:type()) ->
+ [imm_instance(),...].
+keep_shrinking(ImmInstance, Acc, Type) ->
+ case proper_shrink:shrink(ImmInstance, Type, init) of
+ {[], _NewState} ->
+ lists:reverse([ImmInstance|Acc]);
+ {[Shrunk|_Rest], _NewState} ->
+ keep_shrinking(Shrunk, [ImmInstance|Acc], Type)
+ end.
+
+-spec contains_fun(term()) -> boolean().
+contains_fun(List) when is_list(List) ->
+ proper_arith:safe_any(fun contains_fun/1, List);
+contains_fun(Tuple) when is_tuple(Tuple) ->
+ contains_fun(tuple_to_list(Tuple));
+contains_fun(Fun) when is_function(Fun) ->
+ true;
+contains_fun(_Term) ->
+ false.
+
+
+%%-----------------------------------------------------------------------------
+%% Utility functions
+%%-----------------------------------------------------------------------------
+
+%% @private
+-spec normal_gen(proper_types:type()) -> imm_instance().
+normal_gen(Type) ->
+ case proper_types:get_prop(generator, Type) of
+ {typed, Gen} ->
+ if
+ is_function(Gen, 1) -> Gen(Type);
+ is_function(Gen, 2) -> Gen(Type, proper:get_size(Type))
+ end;
+ Gen ->
+ if
+ is_function(Gen, 0) -> Gen();
+ is_function(Gen, 1) -> Gen(proper:get_size(Type))
+ end
+ end.
+
+%% @private
+-spec alt_gens(proper_types:type()) -> [imm_instance()].
+alt_gens(Type) ->
+ case proper_types:find_prop(alt_gens, Type) of
+ {ok, AltGens} -> ?FORCE(AltGens);
+ error -> []
+ end.
+
+%% @private
+-spec clean_instance(imm_instance()) -> instance().
+clean_instance({'$used',_ImmParts,ImmInstance}) ->
+ clean_instance(ImmInstance);
+clean_instance({'$to_part',ImmInstance}) ->
+ clean_instance(ImmInstance);
+clean_instance(ImmInstance) ->
+ if
+ is_list(ImmInstance) ->
+ %% CAUTION: this must handle improper lists
+ proper_arith:safe_map(fun clean_instance/1, ImmInstance);
+ is_tuple(ImmInstance) ->
+ proper_arith:tuple_map(fun clean_instance/1, ImmInstance);
+ true ->
+ ImmInstance
+ end.
+
+
+%%-----------------------------------------------------------------------------
+%% Basic type generators
+%%-----------------------------------------------------------------------------
+
+%% @private
+-spec integer_gen(size(), proper_types:extint(), proper_types:extint()) ->
+ integer().
+integer_gen(Size, inf, inf) ->
+ proper_arith:rand_int(Size);
+integer_gen(Size, inf, High) ->
+ High - proper_arith:rand_non_neg_int(Size);
+integer_gen(Size, Low, inf) ->
+ Low + proper_arith:rand_non_neg_int(Size);
+integer_gen(Size, Low, High) ->
+ proper_arith:smart_rand_int(Size, Low, High).
+
+%% @private
+-spec float_gen(size(), proper_types:extnum(), proper_types:extnum()) ->
+ float().
+float_gen(Size, inf, inf) ->
+ proper_arith:rand_float(Size);
+float_gen(Size, inf, High) ->
+ High - proper_arith:rand_non_neg_float(Size);
+float_gen(Size, Low, inf) ->
+ Low + proper_arith:rand_non_neg_float(Size);
+float_gen(_Size, Low, High) ->
+ proper_arith:rand_float(Low, High).
+
+%% @private
+-spec atom_gen(size()) -> proper_types:type().
+%% We make sure we never clash with internal atoms by checking that the first
+%% character is not '$'.
+atom_gen(Size) ->
+ ?LET(Str,
+ ?SUCHTHAT(X,
+ proper_types:resize(Size,
+ proper_types:list(proper_types:byte())),
+ X =:= [] orelse hd(X) =/= $$),
+ list_to_atom(Str)).
+
+%% @private
+-spec atom_rev(atom()) -> imm_instance().
+atom_rev(Atom) ->
+ {'$used', atom_to_list(Atom), Atom}.
+
+%% @private
+-spec binary_gen(size()) -> proper_types:type().
+binary_gen(Size) ->
+ ?LET(Bytes,
+ proper_types:resize(Size,
+ proper_types:list(proper_types:byte())),
+ list_to_binary(Bytes)).
+
+%% @private
+-spec binary_rev(binary()) -> imm_instance().
+binary_rev(Binary) ->
+ {'$used', binary_to_list(Binary), Binary}.
+
+%% @private
+-spec binary_len_gen(length()) -> proper_types:type().
+binary_len_gen(Len) ->
+ ?LET(Bytes,
+ proper_types:vector(Len, proper_types:byte()),
+ list_to_binary(Bytes)).
+
+%% @private
+-spec bitstring_gen(size()) -> proper_types:type().
+bitstring_gen(Size) ->
+ ?LET({BytesHead, NumBits, TailByte},
+ {proper_types:resize(Size,proper_types:binary()),
+ proper_types:range(0,7), proper_types:range(0,127)},
+ <<BytesHead/binary, TailByte:NumBits>>).
+
+%% @private
+-spec bitstring_rev(bitstring()) -> imm_instance().
+bitstring_rev(BitString) ->
+ List = bitstring_to_list(BitString),
+ {BytesList, BitsTail} = lists:splitwith(fun erlang:is_integer/1, List),
+ {NumBits, TailByte} = case BitsTail of
+ [] -> {0, 0};
+ [Bits] -> N = bit_size(Bits),
+ <<Byte:N>> = Bits,
+ {N, Byte}
+ end,
+ {'$used',
+ {{'$used',BytesList,list_to_binary(BytesList)}, NumBits, TailByte},
+ BitString}.
+
+%% @private
+-spec bitstring_len_gen(length()) -> proper_types:type().
+bitstring_len_gen(Len) ->
+ BytesLen = Len div 8,
+ BitsLen = Len rem 8,
+ ?LET({BytesHead, NumBits, TailByte},
+ {proper_types:binary(BytesLen), BitsLen,
+ proper_types:range(0, 1 bsl BitsLen - 1)},
+ <<BytesHead/binary, TailByte:NumBits>>).
+
+%% @private
+-spec list_gen(size(), proper_types:type()) -> [imm_instance()].
+list_gen(Size, ElemType) ->
+ Len = proper_arith:rand_int(0, Size),
+ vector_gen(Len, ElemType).
+
+%% @private
+-spec distlist_gen(size(), sized_generator(), boolean()) -> [imm_instance()].
+distlist_gen(RawSize, Gen, NonEmpty) ->
+ Len = case NonEmpty of
+ true -> proper_arith:rand_int(1, erlang:max(1,RawSize));
+ false -> proper_arith:rand_int(0, RawSize)
+ end,
+ Size = case Len of
+ 1 -> RawSize - 1;
+ _ -> RawSize
+ end,
+ %% TODO: this produces a lot of types: maybe a simple 'div' is sufficient?
+ Sizes = proper_arith:distribute(Size, Len),
+ InnerTypes = [Gen(S) || S <- Sizes],
+ fixed_list_gen(InnerTypes).
+
+%% @private
+-spec vector_gen(length(), proper_types:type()) -> [imm_instance()].
+vector_gen(Len, ElemType) ->
+ vector_gen_tr(Len, ElemType, []).
+
+-spec vector_gen_tr(length(), proper_types:type(), [imm_instance()]) ->
+ [imm_instance()].
+vector_gen_tr(0, _ElemType, AccList) ->
+ AccList;
+vector_gen_tr(Left, ElemType, AccList) ->
+ vector_gen_tr(Left - 1, ElemType, [generate(ElemType) | AccList]).
+
+%% @private
+-spec union_gen([proper_types:type(),...]) -> imm_instance().
+union_gen(Choices) ->
+ {_Choice,Type} = proper_arith:rand_choose(Choices),
+ generate(Type).
+
+%% @private
+-spec weighted_union_gen([{frequency(),proper_types:type()},...]) ->
+ imm_instance().
+weighted_union_gen(FreqChoices) ->
+ {_Choice,Type} = proper_arith:freq_choose(FreqChoices),
+ generate(Type).
+
+%% @private
+-spec safe_union_gen([proper_types:type(),...]) -> imm_instance().
+safe_union_gen(Choices) ->
+ {Choice,Type} = proper_arith:rand_choose(Choices),
+ try generate(Type)
+ catch
+ error:_ ->
+ safe_union_gen(proper_arith:list_remove(Choice, Choices))
+ end.
+
+%% @private
+-spec safe_weighted_union_gen([{frequency(),proper_types:type()},...]) ->
+ imm_instance().
+safe_weighted_union_gen(FreqChoices) ->
+ {Choice,Type} = proper_arith:freq_choose(FreqChoices),
+ try generate(Type)
+ catch
+ error:_ ->
+ safe_weighted_union_gen(proper_arith:list_remove(Choice,
+ FreqChoices))
+ end.
+
+%% @private
+-spec tuple_gen([proper_types:type()]) -> tuple().
+tuple_gen(Fields) ->
+ list_to_tuple(fixed_list_gen(Fields)).
+
+%% @private
+-spec loose_tuple_gen(size(), proper_types:type()) -> proper_types:type().
+loose_tuple_gen(Size, ElemType) ->
+ ?LET(L,
+ proper_types:resize(Size, proper_types:list(ElemType)),
+ list_to_tuple(L)).
+
+%% @private
+-spec loose_tuple_rev(tuple(), proper_types:type()) -> imm_instance().
+loose_tuple_rev(Tuple, ElemType) ->
+ CleanList = tuple_to_list(Tuple),
+ List = case proper_types:find_prop(reverse_gen, ElemType) of
+ {ok,{typed, ReverseGen}} ->
+ [ReverseGen(ElemType,X) || X <- CleanList];
+ {ok,ReverseGen} -> [ReverseGen(X) || X <- CleanList];
+ error -> CleanList
+ end,
+ {'$used', List, Tuple}.
+
+%% @private
+-spec exactly_gen(T) -> T.
+exactly_gen(X) ->
+ X.
+
+%% @private
+-spec fixed_list_gen([proper_types:type()]) -> imm_instance()
+ ; ({[proper_types:type()],proper_types:type()}) ->
+ maybe_improper_list(imm_instance(), imm_instance() | []).
+fixed_list_gen({ProperHead,ImproperTail}) ->
+ [generate(F) || F <- ProperHead] ++ generate(ImproperTail);
+fixed_list_gen(ProperFields) ->
+ [generate(F) || F <- ProperFields].
+
+%% @private
+-spec function_gen(arity(), proper_types:type()) -> function().
+function_gen(Arity, RetType) ->
+ FunSeed = {proper_arith:rand_int(0, ?SEED_RANGE - 1),
+ proper_arith:rand_int(0, ?SEED_RANGE - 1)},
+ create_fun(Arity, RetType, FunSeed).
+
+%% @private
+-spec any_gen(size()) -> imm_instance().
+any_gen(Size) ->
+ case get('$any_type') of
+ undefined -> real_any_gen(Size);
+ {type,AnyType} -> generate(proper_types:resize(Size, AnyType))
+ end.
+
+-spec real_any_gen(size()) -> imm_instance().
+real_any_gen(0) ->
+ SimpleTypes = [proper_types:integer(), proper_types:float(),
+ proper_types:atom()],
+ union_gen(SimpleTypes);
+real_any_gen(Size) ->
+ FreqChoices = [{?ANY_SIMPLE_PROB,simple}, {?ANY_BINARY_PROB,binary},
+ {?ANY_EXPAND_PROB,expand}],
+ case proper_arith:freq_choose(FreqChoices) of
+ {_,simple} ->
+ real_any_gen(0);
+ {_,binary} ->
+ generate(proper_types:resize(Size, proper_types:bitstring()));
+ {_,expand} ->
+ %% TODO: statistics of produced terms?
+ NumElems = proper_arith:rand_int(0, Size - 1),
+ ElemSizes = proper_arith:distribute(Size - 1, NumElems),
+ ElemTypes = [?LAZY(real_any_gen(S)) || S <- ElemSizes],
+ case proper_arith:rand_int(1,2) of
+ 1 -> fixed_list_gen(ElemTypes);
+ 2 -> tuple_gen(ElemTypes)
+ end
+ end.
+
+%% @private
+-spec native_type_gen(mod_name(), string()) -> proper_types:type().
+native_type_gen(Mod, TypeStr) ->
+ case proper_typeserver:translate_type({Mod,TypeStr}) of
+ {ok,Type} -> Type;
+ {error,Reason} -> throw({'$typeserver',Reason})
+ end.
+
+
+%%------------------------------------------------------------------------------
+%% Function-generation functions
+%%------------------------------------------------------------------------------
+
+-spec create_fun(arity(), proper_types:type(), fun_seed()) -> function().
+create_fun(Arity, RetType, FunSeed) ->
+ Handler = fun(Args) -> function_body(Args, RetType, FunSeed) end,
+ Err = fun() -> throw('$arity_limit') end,
+ case Arity of
+ 0 -> fun() -> Handler([]) end;
+ _ -> Err()
+ end.
+
+%% @private
+-spec get_ret_type(function()) -> proper_types:type().
+get_ret_type(Fun) ->
+ {arity,Arity} = erlang:fun_info(Fun, arity),
+ put('$get_ret_type', true),
+ RetType = apply(Fun, lists:duplicate(Arity,dummy)),
+ erase('$get_ret_type'),
+ RetType.
+-spec function_body([term()], proper_types:type(), fun_seed()) ->
+ proper_types:type() | instance().
+function_body(Args, RetType, {Seed1,Seed2}) ->
+ case get('$get_ret_type') of
+ true ->
+ RetType;
+ _ ->
+ SavedSeed = get(?SEED_NAME),
+ update_seed({Seed1,Seed2,erlang:phash2(Args,?SEED_RANGE)}),
+ Ret = clean_instance(generate(RetType)),
+ put(?SEED_NAME, SavedSeed),
+ proper_symb:internal_eval(Ret)
+ end.
+
+-ifdef(USE_SFMT).
+update_seed(Seed) ->
+ sfmt:seed(Seed).
+-else.
+update_seed(Seed) ->
+ put(random_seed, Seed).
+-endif.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_internal.hrl b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_internal.hrl
new file mode 100644
index 0000000000..c790d7d4db
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_internal.hrl
@@ -0,0 +1,92 @@
+%%% Copyright 2010-2013 Manolis Papadakis <manopapad@gmail.com>,
+%%% Eirini Arvaniti <eirinibob@gmail.com>
+%%% and Kostis Sagonas <kostis@cs.ntua.gr>
+%%%
+%%% This file is part of PropEr.
+%%%
+%%% PropEr is free software: you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation, either version 3 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% PropEr is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.
+
+%%% @copyright 2010-2013 Manolis Papadakis, Eirini Arvaniti and Kostis Sagonas
+%%% @version {@version}
+%%% @author Manolis Papadakis
+%%% @doc Internal header file: This header is included in all PropEr source
+%%% files.
+
+-include("proper_common.hrl").
+
+
+%%------------------------------------------------------------------------------
+%% Activate strip_types parse transform
+%%------------------------------------------------------------------------------
+
+-ifdef(NO_TYPES).
+-compile({parse_transform, strip_types}).
+-endif.
+
+%%------------------------------------------------------------------------------
+%% Random generator selection
+%%------------------------------------------------------------------------------
+
+-ifdef(USE_SFMT).
+-define(RANDOM_MOD, sfmt).
+-define(SEED_NAME, sfmt_seed).
+-else.
+-define(RANDOM_MOD, random).
+-define(SEED_NAME, random_seed).
+-endif.
+
+%%------------------------------------------------------------------------------
+%% Macros
+%%------------------------------------------------------------------------------
+
+-define(PROPERTY_PREFIX, "prop_").
+
+
+%%------------------------------------------------------------------------------
+%% Constants
+%%------------------------------------------------------------------------------
+
+-define(SEED_RANGE, 4294967296).
+-define(MAX_ARITY, 20).
+-define(MAX_TRIES_FACTOR, 5).
+-define(ANY_SIMPLE_PROB, 3).
+-define(ANY_BINARY_PROB, 1).
+-define(ANY_EXPAND_PROB, 8).
+-define(SMALL_RANGE_THRESHOLD, 16#FFFF).
+
+
+%%------------------------------------------------------------------------------
+%% Common type aliases
+%%------------------------------------------------------------------------------
+
+%% TODO: Perhaps these should be moved inside modules.
+-type mod_name() :: atom().
+-type fun_name() :: atom().
+-type size() :: non_neg_integer().
+-type length() :: non_neg_integer().
+-type position() :: pos_integer().
+-type frequency() :: pos_integer().
+-type seed() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
+
+-type abs_form() :: erl_parse:abstract_form().
+-type abs_expr() :: erl_parse:abstract_expr().
+-type abs_clause() :: erl_parse:abstract_clause().
+
+%% TODO: Replace these with the appropriate types from stdlib.
+-type abs_type() :: term().
+-type abs_rec_field() :: term().
+
+-type loose_tuple(T) :: {} | {T} | {T,T} | {T,T,T} | {T,T,T,T} | {T,T,T,T,T}
+ | {T,T,T,T,T,T} | {T,T,T,T,T,T,T} | {T,T,T,T,T,T,T,T}
+ | {T,T,T,T,T,T,T,T,T} | {T,T,T,T,T,T,T,T,T,T} | tuple().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_types.erl b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_types.erl
new file mode 100644
index 0000000000..fe83a0ba11
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_types.erl
@@ -0,0 +1,1349 @@
+%%% Copyright 2010-2013 Manolis Papadakis <manopapad@gmail.com>,
+%%% Eirini Arvaniti <eirinibob@gmail.com>
+%%% and Kostis Sagonas <kostis@cs.ntua.gr>
+%%%
+%%% This file is part of PropEr.
+%%%
+%%% PropEr is free software: you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation, either version 3 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% PropEr is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.
+
+%%% @copyright 2010-2013 Manolis Papadakis, Eirini Arvaniti and Kostis Sagonas
+%%% @version {@version}
+%%% @author Manolis Papadakis
+
+%%% @doc Type manipulation functions and predefined types.
+%%%
+%%% == Basic types ==
+%%% This module defines all the basic types of the PropEr type system as
+%%% functions. See the <a href="#index">function index</a> for an overview.
+%%%
+%%% Types can be combined in tuples or lists to produce other types. Exact
+%%% values (such as exact numbers, atoms, binaries and strings) can be combined
+%%% with types inside such structures, like in this example of the type of a
+%%% tagged tuple: ``{'result', integer()}''.
+%%%
+%%% When including the PropEr header file, all
+%%% <a href="#index">API functions</a> of this module are automatically
+%%% imported, unless `PROPER_NO_IMPORTS' is defined.
+%%%
+%%% == Customized types ==
+%%% The following operators can be applied to basic types in order to produce
+%%% new ones:
+%%%
+%%% <dl>
+%%% <dt>`?LET(<Xs>, <Xs_type>, <In>)'</dt>
+%%% <dd>To produce an instance of this type, all appearances of the variables
+%%% in `<Xs>' are replaced inside `<In>' by their corresponding values in a
+%%% randomly generated instance of `<Xs_type>'. It's OK for the `<In>' part to
+%%% evaluate to a type - in that case, an instance of the inner type is
+%%% generated recursively.</dd>
+%%% <dt>`?SUCHTHAT(<X>, <Type>, <Condition>)'</dt>
+%%% <dd>This produces a specialization of `<Type>', which only includes those
+%%% members of `<Type>' that satisfy the constraint `<Condition>' - that is,
+%%% those members for which the function `fun(<X>) -> <Condition> end' returns
+%%% `true'. If the constraint is very strict - that is, only a small
+%%% percentage of instances of `<Type>' pass the test - it will take a lot of
+%%% tries for the instance generation subsystem to randomly produce a valid
+%%% instance. This will result in slower testing, and testing may even be
+%%% stopped short, in case the `constraint_tries' limit is reached (see the
+%%% "Options" section in the documentation of the {@link proper} module). If
+%%% this is the case, it would be more appropriate to generate valid instances
+%%% of the specialized type using the `?LET' macro. Also make sure that even
+%%% small instances can satisfy the constraint, since PropEr will only try
+%%% small instances at the start of testing. If this is not possible, you can
+%%% instruct PropEr to start at a larger size, by supplying a suitable value
+%%% for the `start_size' option (see the "Options" section in the
+%%% documentation of the {@link proper} module).</dd>
+%%% <dt>`?SUCHTHATMAYBE(<X>, <Type>, <Condition>)'</dt>
+%%% <dd>Equivalent to the `?SUCHTHAT' macro, but the constraint `<Condition>'
+%%% is considered non-strict: if the `constraint_tries' limit is reached, the
+%%% generator will just return an instance of `<Type>' instead of failing,
+%%% even if that instance doesn't satisfy the constraint.</dd>
+%%% <dt>`?SHRINK(<Generator>, <List_of_alt_gens>)'</dt>
+%%% <dd>This creates a type whose instances are generated by evaluating the
+%%% statement block `<Generator>' (this may evaluate to a type, which will
+%%% then be generated recursively). If an instance of such a type is to be
+%%% shrunk, the generators in `<List_of_alt_gens>' are first run to produce
+%%% hopefully simpler instances of the type. Thus, the generators in the
+%%% second argument should be simpler than the default. The simplest ones
+%%% should be at the front of the list, since those are the generators
+%%% preferred by the shrinking subsystem. Like the main `<Generator>', the
+%%% alternatives may also evaluate to a type, which is generated recursively.
+%%% </dd>
+%%% <dt>`?LETSHRINK(<List_of_variables>, <List_of_types>, <Generator>)'</dt>
+%%% <dd>This is created by combining a `?LET' and a `?SHRINK' macro. Instances
+%%% are generated by applying a randomly generated list of values inside
+%%% `<Generator>' (just like a `?LET', with the added constraint that the
+%%% variables and types must be provided in a list - alternatively,
+%%% `<List_of_types>' may be a list or vector type). When shrinking instances
+%%% of such a type, the sub-instances that were combined to produce it are
+%%% first tried in place of the failing instance.</dd>
+%%% <dt>`?LAZY(<Generator>)'</dt>
+%%% <dd>This construct returns a type whose only purpose is to delay the
+%%% evaluation of `<Generator>' (`<Generator>' can return a type, which will
+%%% be generated recursively). Using this, you can simulate the lazy
+%%% generation of instances:
+%%% ``` stream() -> ?LAZY(frequency([ {1,[]}, {3,[0|stream()]} ])). '''
+%%% The above type produces lists of zeroes with an average length of 3. Note
+%%% that, had we not enclosed the generator with a `?LAZY' macro, the
+%%% evaluation would continue indefinitely, due to the eager evaluation of
+%%% the Erlang language.</dd>
+%%% <dt>`non_empty(<List_or_binary_type>)'</dt>
+%%% <dd>See the documentation for {@link non_empty/1}.</dd>
+%%% <dt>`noshrink(<Type>)'</dt>
+%%% <dd>See the documentation for {@link noshrink/1}.</dd>
+%%% <dt>`default(<Default_value>, <Type>)'</dt>
+%%% <dd>See the documentation for {@link default/2}.</dd>
+%%% <dt>`with_parameter(<Parameter>, <Value>, <Type>)'</dt>
+%%% <dd>See the documentation for {@link with_parameter/3}.</dd>
+%%% <dt>`with_parameters(<Param_value_pairs>, <Type>)'</dt>
+%%% <dd>See the documentation for {@link with_parameters/2}.</dd>
+%%% </dl>
+%%%
+%%% == Size manipulation ==
+%%% The following operators are related to the `size' parameter, which controls
+%%% the maximum size of produced instances. The actual size of a produced
+%%% instance is chosen randomly, but can never exceed the value of the `size'
+%%% parameter at the moment of generation. A more accurate definition is the
+%%% following: the maximum instance of `size S' can never be smaller than the
+%%% maximum instance of `size S-1'. The actual size of an instance is measured
+%%% differently for each type: the actual size of a list is its length, while
+%%% the actual size of a tree may be the number of its internal nodes. Some
+%%% types, e.g. unions, have no notion of size, thus their generation is not
+%%% influenced by the value of `size'. The `size' parameter starts at 1 and
+%%% grows automatically during testing.
+%%%
+%%% <dl>
+%%% <dt>`?SIZED(<S>, <Generator>)'</dt>
+%%% <dd>Creates a new type, whose instances are produced by replacing all
+%%% appearances of the `<S>' parameter inside the statement block
+%%% `<Generator>' with the value of the `size' parameter. It's OK for the
+%%% `<Generator>' to return a type - in that case, an instance of the inner
+%%% type is generated recursively.</dd>
+%%% <dt>`resize(<New_size>, <Type>)'</dt>
+%%% <dd>See the documentation for {@link resize/2}.</dd>
+%%% </dl>
+
+-module(proper_types).
+-export([is_inst/2, is_inst/3]).
+
+-export([integer/2, float/2, atom/0, binary/0, binary/1, bitstring/0,
+ bitstring/1, list/1, vector/2, union/1, weighted_union/1, tuple/1,
+ loose_tuple/1, exactly/1, fixed_list/1, function/2, any/0,
+ shrink_list/1, safe_union/1, safe_weighted_union/1]).
+-export([integer/0, non_neg_integer/0, pos_integer/0, neg_integer/0, range/2,
+ float/0, non_neg_float/0, number/0, boolean/0, byte/0, char/0,
+ list/0, tuple/0, string/0, wunion/1, term/0, timeout/0, arity/0]).
+-export([int/0, nat/0, largeint/0, real/0, bool/0, choose/2, elements/1,
+ oneof/1, frequency/1, return/1, default/2, orderedlist/1, function0/1,
+ function1/1, function2/1, function3/1, function4/1,
+ weighted_default/2]).
+-export([resize/2, non_empty/1, noshrink/1]).
+
+-export([cook_outer/1, is_type/1, equal_types/2, is_raw_type/1, to_binary/1,
+ from_binary/1, get_prop/2, find_prop/2, safe_is_instance/2,
+ is_instance/2, unwrap/1, weakly/1, strongly/1, satisfies_all/2,
+ new_type/2, subtype/2]).
+-export([lazy/1, sized/1, bind/3, shrinkwith/2, add_constraint/3,
+ native_type/2, distlist/3, with_parameter/3, with_parameters/2,
+ parameter/1, parameter/2]).
+-export([le/2]).
+
+-export_type([type/0, raw_type/0, extint/0, extnum/0]).
+
+-include("proper_internal.hrl").
+
+
+%%------------------------------------------------------------------------------
+%% Comparison with erl_types
+%%------------------------------------------------------------------------------
+
+%% Missing types
+%% -------------------
+%% will do:
+%% records, maybe_improper_list(T,S), nonempty_improper_list(T,S)
+%% maybe_improper_list(), maybe_improper_list(T), iolist, iodata
+%% don't need:
+%% nonempty_{list,string,maybe_improper_list}
+%% won't do:
+%% pid, port, ref, identifier, none, no_return, module, mfa, node
+%% array, dict, digraph, set, gb_tree, gb_set, queue, tid
+
+%% Missing type information
+%% ------------------------
+%% bin types:
+%% other unit sizes? what about size info?
+%% functions:
+%% generally some fun, unspecified number of arguments but specified
+%% return type
+%% any:
+%% doesn't cover functions and improper lists
+
+
+%%------------------------------------------------------------------------------
+%% Type declaration macros
+%%------------------------------------------------------------------------------
+
+-define(BASIC(PropList), new_type(PropList,basic)).
+-define(WRAPPER(PropList), new_type(PropList,wrapper)).
+-define(CONSTRUCTED(PropList), new_type(PropList,constructed)).
+-define(CONTAINER(PropList), new_type(PropList,container)).
+-define(SUBTYPE(Type,PropList), subtype(PropList,Type)).
+
+
+%%------------------------------------------------------------------------------
+%% Types
+%%------------------------------------------------------------------------------
+
+-type type_kind() :: 'basic' | 'wrapper' | 'constructed' | 'container' | atom().
+-type instance_test() :: fun((proper_gen:imm_instance()) -> boolean())
+ | {'typed',
+ fun((proper_types:type(),
+ proper_gen:imm_instance()) -> boolean())}.
+-type index() :: pos_integer().
+%% @alias
+-type value() :: term().
+%% @private_type
+%% @alias
+-type extint() :: integer() | 'inf'.
+%% @private_type
+%% @alias
+-type extnum() :: number() | 'inf'.
+-type constraint_fun() :: fun((proper_gen:instance()) -> boolean()).
+
+-opaque type() :: {'$type', [type_prop()]}.
+%% A type of the PropEr type system
+%% @type raw_type(). You can consider this as an equivalent of {@type type()}.
+-type raw_type() :: type() | [raw_type()] | loose_tuple(raw_type()) | term().
+-type type_prop_name() :: 'kind' | 'generator' | 'reverse_gen' | 'parts_type'
+ | 'combine' | 'alt_gens' | 'shrink_to_parts'
+ | 'size_transform' | 'is_instance' | 'shrinkers'
+ | 'noshrink' | 'internal_type' | 'internal_types'
+ | 'get_length' | 'split' | 'join' | 'get_indices'
+ | 'remove' | 'retrieve' | 'update' | 'constraints'
+ | 'parameters' | 'env' | 'subenv'.
+
+-type type_prop_value() :: term().
+-type type_prop() ::
+ {'kind', type_kind()}
+ | {'generator', proper_gen:generator()}
+ | {'reverse_gen', proper_gen:reverse_gen()}
+ | {'parts_type', type()}
+ | {'combine', proper_gen:combine_fun()}
+ | {'alt_gens', proper_gen:alt_gens()}
+ | {'shrink_to_parts', boolean()}
+ | {'size_transform', fun((size()) -> size())}
+ | {'is_instance', instance_test()}
+ | {'shrinkers', [proper_shrink:shrinker()]}
+ | {'noshrink', boolean()}
+ | {'internal_type', raw_type()}
+ | {'internal_types', tuple() | maybe_improper_list(type(),type() | [])}
+ %% The items returned by 'remove' must be of this type.
+ | {'get_length', fun((proper_gen:imm_instance()) -> length())}
+ %% If this is a container type, this should return the number of elements
+ %% it contains.
+ | {'split', fun((proper_gen:imm_instance()) -> [proper_gen:imm_instance()])
+ | fun((length(),proper_gen:imm_instance()) ->
+ {proper_gen:imm_instance(),proper_gen:imm_instance()})}
+ %% If present, the appropriate form depends on whether get_length is
+ %% defined: if get_length is undefined, this must be in the one-argument
+ %% form (e.g. a tree should be split into its subtrees), else it must be
+ %% in the two-argument form (e.g. a list should be split in two at the
+ %% index provided).
+ | {'join', fun((proper_gen:imm_instance(),proper_gen:imm_instance()) ->
+ proper_gen:imm_instance())}
+ | {'get_indices', fun((proper_types:type(),
+ proper_gen:imm_instance()) -> [index()])}
+ %% If this is a container type, this should return a list of indices we
+ %% can use to remove or insert elements from the given instance.
+ | {'remove', fun((index(),proper_gen:imm_instance()) ->
+ proper_gen:imm_instance())}
+ | {'retrieve', fun((index(), proper_gen:imm_instance() | tuple()
+ | maybe_improper_list(type(),type() | [])) ->
+ value() | type())}
+ | {'update', fun((index(),value(),proper_gen:imm_instance()) ->
+ proper_gen:imm_instance())}
+ | {'constraints', [{constraint_fun(), boolean()}]}
+ %% A list of constraints on instances of this type: each constraint is a
+ %% tuple of a fun that must return 'true' for each valid instance and a
+ %% boolean field that specifies whether the condition is strict.
+ | {'parameters', [{atom(),value()}]}
+ | {'env', term()}
+ | {'subenv', term()}.
+
+
+%%------------------------------------------------------------------------------
+%% Type manipulation functions
+%%------------------------------------------------------------------------------
+
+%% TODO: We shouldn't need the fully qualified type name in the range of these
+%% functions.
+
+%% @private
+%% TODO: just cook/1 ?
+-spec cook_outer(raw_type()) -> proper_types:type().
+cook_outer(Type = {'$type',_Props}) ->
+ Type;
+cook_outer(RawType) ->
+ if
+ is_tuple(RawType) -> tuple(tuple_to_list(RawType));
+ %% CAUTION: this must handle improper lists
+ is_list(RawType) -> fixed_list(RawType);
+ %% default case (covers integers, floats, atoms, binaries, ...):
+ true -> exactly(RawType)
+ end.
+
+%% @private
+-spec is_type(term()) -> boolean().
+is_type({'$type',_Props}) ->
+ true;
+is_type(_) ->
+ false.
+
+%% @private
+-spec equal_types(proper_types:type(), proper_types:type()) -> boolean().
+equal_types(SameType, SameType) ->
+ true;
+equal_types(_, _) ->
+ false.
+
+%% @private
+-spec is_raw_type(term()) -> boolean().
+is_raw_type({'$type',_TypeProps}) ->
+ true;
+is_raw_type(X) ->
+ if
+ is_tuple(X) -> is_raw_type_list(tuple_to_list(X));
+ is_list(X) -> is_raw_type_list(X);
+ true -> false
+ end.
+
+-spec is_raw_type_list(maybe_improper_list()) -> boolean().
+%% CAUTION: this must handle improper lists
+is_raw_type_list(List) ->
+ proper_arith:safe_any(fun is_raw_type/1, List).
+
+%% @private
+-spec to_binary(proper_types:type()) -> binary().
+to_binary(Type) ->
+ term_to_binary(Type).
+
+%% @private
+%% TODO: restore: -spec from_binary(binary()) -> proper_types:type().
+from_binary(Binary) ->
+ binary_to_term(Binary).
+
+-spec type_from_list([type_prop()]) -> proper_types:type().
+type_from_list(KeyValueList) ->
+ {'$type',KeyValueList}.
+
+-spec add_prop(type_prop_name(), type_prop_value(), proper_types:type()) ->
+ proper_types:type().
+add_prop(PropName, Value, {'$type',Props}) ->
+ {'$type',lists:keystore(PropName, 1, Props, {PropName, Value})}.
+
+-spec add_props([type_prop()], proper_types:type()) -> proper_types:type().
+add_props(PropList, {'$type',OldProps}) ->
+ {'$type', lists:foldl(fun({N,_}=NV,Acc) ->
+ lists:keystore(N, 1, Acc, NV)
+ end, OldProps, PropList)}.
+
+-spec append_to_prop(type_prop_name(), type_prop_value(),
+ proper_types:type()) -> proper_types:type().
+append_to_prop(PropName, Value, {'$type',Props}) ->
+ Val = case lists:keyfind(PropName, 1, Props) of
+ {PropName, V} ->
+ V;
+ _ ->
+ []
+ end,
+ {'$type', lists:keystore(PropName, 1, Props,
+ {PropName, lists:reverse([Value|Val])})}.
+
+-spec append_list_to_prop(type_prop_name(), [type_prop_value()],
+ proper_types:type()) -> proper_types:type().
+append_list_to_prop(PropName, List, {'$type',Props}) ->
+ {PropName, Val} = lists:keyfind(PropName, 1, Props),
+ {'$type', lists:keystore(PropName, 1, Props, {PropName, Val++List})}.
+
+%% @private
+-spec get_prop(type_prop_name(), proper_types:type()) -> type_prop_value().
+get_prop(PropName, {'$type',Props}) ->
+ {_PropName, Val} = lists:keyfind(PropName, 1, Props),
+ Val.
+
+%% @private
+-spec find_prop(type_prop_name(), proper_types:type()) ->
+ {'ok',type_prop_value()} | 'error'.
+find_prop(PropName, {'$type',Props}) ->
+ case lists:keyfind(PropName, 1, Props) of
+ {PropName, Value} ->
+ {ok, Value};
+ _ ->
+ error
+ end.
+
+%% @private
+-spec new_type([type_prop()], type_kind()) -> proper_types:type().
+new_type(PropList, Kind) ->
+ Type = type_from_list(PropList),
+ add_prop(kind, Kind, Type).
+
+%% @private
+-spec subtype([type_prop()], proper_types:type()) -> proper_types:type().
+%% TODO: should the 'is_instance' function etc. be reset for subtypes?
+subtype(PropList, Type) ->
+ add_props(PropList, Type).
+
+%% @private
+-spec is_inst(proper_gen:instance(), raw_type()) ->
+ boolean() | {'error',{'typeserver',term()}}.
+is_inst(Instance, RawType) ->
+ is_inst(Instance, RawType, 10).
+
+%% @private
+-spec is_inst(proper_gen:instance(), raw_type(), size()) ->
+ boolean() | {'error',{'typeserver',term()}}.
+is_inst(Instance, RawType, Size) ->
+ proper:global_state_init_size(Size),
+ Result = safe_is_instance(Instance, RawType),
+ proper:global_state_erase(),
+ Result.
+
+%% @private
+-spec safe_is_instance(proper_gen:imm_instance(), raw_type()) ->
+ boolean() | {'error',{'typeserver',term()}}.
+safe_is_instance(ImmInstance, RawType) ->
+ try is_instance(ImmInstance, RawType) catch
+ throw:{'$typeserver',SubReason} -> {error, {typeserver,SubReason}}
+ end.
+
+%% @private
+-spec is_instance(proper_gen:imm_instance(), raw_type()) -> boolean().
+%% TODO: If the second argument is not a type, let it pass (don't even check for
+%% term equality?) - if it's a raw type, don't cook it, instead recurse
+%% into it.
+is_instance(ImmInstance, RawType) ->
+ CleanInstance = proper_gen:clean_instance(ImmInstance),
+ Type = cook_outer(RawType),
+ (case get_prop(kind, Type) of
+ wrapper -> wrapper_test(ImmInstance, Type);
+ constructed -> constructed_test(ImmInstance, Type);
+ _ -> false
+ end
+ orelse
+ case find_prop(is_instance, Type) of
+ {ok,{typed, IsInstance}} -> IsInstance(Type, ImmInstance);
+ {ok,IsInstance} -> IsInstance(ImmInstance);
+ error -> false
+ end)
+ andalso weakly(satisfies_all(CleanInstance, Type)).
+
+-spec wrapper_test(proper_gen:imm_instance(), proper_types:type()) -> boolean().
+wrapper_test(ImmInstance, Type) ->
+ %% TODO: check if it's actually a raw type that's returned?
+ lists:any(fun(T) -> is_instance(ImmInstance, T) end, unwrap(Type)).
+
+%% @private
+%% TODO: restore:-spec unwrap(proper_types:type()) -> [proper_types:type(),...].
+%% TODO: check if it's actually a raw type that's returned?
+unwrap(Type) ->
+ RawInnerTypes = proper_gen:alt_gens(Type) ++ [proper_gen:normal_gen(Type)],
+ [cook_outer(T) || T <- RawInnerTypes].
+
+-spec constructed_test(proper_gen:imm_instance(), proper_types:type()) ->
+ boolean().
+constructed_test({'$used',ImmParts,ImmInstance}, Type) ->
+ PartsType = get_prop(parts_type, Type),
+ Combine = get_prop(combine, Type),
+ is_instance(ImmParts, PartsType) andalso
+ begin
+ %% TODO: check if it's actually a raw type that's returned?
+ %% TODO: move construction code to proper_gen
+ %% TODO: non-type => should we check for strict term equality?
+ RawInnerType = Combine(proper_gen:clean_instance(ImmParts)),
+ is_instance(ImmInstance, RawInnerType)
+ end;
+constructed_test({'$to_part',ImmInstance}, Type) ->
+ PartsType = get_prop(parts_type, Type),
+ get_prop(shrink_to_parts, Type) =:= true andalso
+ %% TODO: we reject non-container types
+ get_prop(kind, PartsType) =:= container andalso
+ case {find_prop(internal_type,PartsType),
+ find_prop(internal_types,PartsType)} of
+ {{ok,EachPartType},error} ->
+ %% The parts are in a list or a vector.
+ is_instance(ImmInstance, EachPartType);
+ {error,{ok,PartTypesList}} ->
+ %% The parts are in a fixed list.
+ %% TODO: It should always be a proper list.
+ lists:any(fun(T) -> is_instance(ImmInstance,T) end, PartTypesList)
+ end;
+constructed_test(_CleanInstance, _Type) ->
+ %% TODO: can we do anything better?
+ false.
+
+%% @private
+-spec weakly({boolean(),boolean()}) -> boolean().
+weakly({B1,_B2}) -> B1.
+
+%% @private
+-spec strongly({boolean(),boolean()}) -> boolean().
+strongly({_B1,B2}) -> B2.
+
+-spec satisfies(proper_gen:instance(), {constraint_fun(),boolean()})
+ -> {boolean(),boolean()}.
+satisfies(Instance, {Test,false}) ->
+ {true,Test(Instance)};
+satisfies(Instance, {Test,true}) ->
+ Result = Test(Instance),
+ {Result,Result}.
+
+%% @private
+-spec satisfies_all(proper_gen:instance(), proper_types:type()) ->
+ {boolean(),boolean()}.
+satisfies_all(Instance, Type) ->
+ case find_prop(constraints, Type) of
+ {ok, Constraints} ->
+ L = [satisfies(Instance, C) || C <- Constraints],
+ {L1,L2} = lists:unzip(L),
+ {lists:all(fun(B) -> B end, L1), lists:all(fun(B) -> B end, L2)};
+ error ->
+ {true,true}
+ end.
+
+
+%%------------------------------------------------------------------------------
+%% Type definition functions
+%%------------------------------------------------------------------------------
+
+%% @private
+-spec lazy(proper_gen:nosize_generator()) -> proper_types:type().
+lazy(Gen) ->
+ ?WRAPPER([
+ {generator, Gen}
+ ]).
+
+%% @private
+-spec sized(proper_gen:sized_generator()) -> proper_types:type().
+sized(Gen) ->
+ ?WRAPPER([
+ {generator, Gen}
+ ]).
+
+%% @private
+-spec bind(raw_type(), proper_gen:combine_fun(), boolean()) ->
+ proper_types:type().
+bind(RawPartsType, Combine, ShrinkToParts) ->
+ PartsType = cook_outer(RawPartsType),
+ ?CONSTRUCTED([
+ {parts_type, PartsType},
+ {combine, Combine},
+ {shrink_to_parts, ShrinkToParts}
+ ]).
+
+%% @private
+-spec shrinkwith(proper_gen:nosize_generator(), proper_gen:alt_gens()) ->
+ proper_types:type().
+shrinkwith(Gen, DelaydAltGens) ->
+ ?WRAPPER([
+ {generator, Gen},
+ {alt_gens, DelaydAltGens}
+ ]).
+
+%% @private
+-spec add_constraint(raw_type(), constraint_fun(), boolean()) ->
+ proper_types:type().
+add_constraint(RawType, Condition, IsStrict) ->
+ Type = cook_outer(RawType),
+ append_to_prop(constraints, {Condition,IsStrict}, Type).
+
+%% @private
+-spec native_type(mod_name(), string()) -> proper_types:type().
+native_type(Mod, TypeStr) ->
+ ?WRAPPER([
+ {generator, fun() -> proper_gen:native_type_gen(Mod,TypeStr) end}
+ ]).
+
+
+%%------------------------------------------------------------------------------
+%% Basic types
+%%------------------------------------------------------------------------------
+
+%% @doc All integers between `Low' and `High', bounds included.
+%% `Low' and `High' must be Erlang expressions that evaluate to integers, with
+%% `Low =< High'. Additionally, `Low' and `High' may have the value `inf', in
+%% which case they represent minus infinity and plus infinity respectively.
+%% Instances shrink towards 0 if `Low =< 0 =< High', or towards the bound with
+%% the smallest absolute value otherwise.
+-spec integer(extint(), extint()) -> proper_types:type().
+integer(Low, High) ->
+ ?BASIC([
+ {env, {Low, High}},
+ {generator, {typed, fun integer_gen/2}},
+ {is_instance, {typed, fun integer_is_instance/2}},
+ {shrinkers, [fun number_shrinker/3]}
+ ]).
+
+integer_gen(Type, Size) ->
+ {Low, High} = get_prop(env, Type),
+ proper_gen:integer_gen(Size, Low, High).
+
+integer_is_instance(Type, X) ->
+ {Low, High} = get_prop(env, Type),
+ is_integer(X) andalso le(Low, X) andalso le(X, High).
+
+number_shrinker(X, Type, S) ->
+ {Low, High} = get_prop(env, Type),
+ proper_shrink:number_shrinker(X, Low, High, S).
+
+%% @doc All floats between `Low' and `High', bounds included.
+%% `Low' and `High' must be Erlang expressions that evaluate to floats, with
+%% `Low =< High'. Additionally, `Low' and `High' may have the value `inf', in
+%% which case they represent minus infinity and plus infinity respectively.
+%% Instances shrink towards 0.0 if `Low =< 0.0 =< High', or towards the bound
+%% with the smallest absolute value otherwise.
+-spec float(extnum(), extnum()) -> proper_types:type().
+float(Low, High) ->
+ ?BASIC([
+ {env, {Low, High}},
+ {generator, {typed, fun float_gen/2}},
+ {is_instance, {typed, fun float_is_instance/2}},
+ {shrinkers, [fun number_shrinker/3]}
+ ]).
+
+float_gen(Type, Size) ->
+ {Low, High} = get_prop(env, Type),
+ proper_gen:float_gen(Size, Low, High).
+
+float_is_instance(Type, X) ->
+ {Low, High} = get_prop(env, Type),
+ is_float(X) andalso le(Low, X) andalso le(X, High).
+
+%% @private
+-spec le(extnum(), extnum()) -> boolean().
+le(inf, _B) -> true;
+le(_A, inf) -> true;
+le(A, B) -> A =< B.
+
+%% @doc All atoms. All atoms used internally by PropEr start with a '`$'', so
+%% such atoms will never be produced as instances of this type. You should also
+%% refrain from using such atoms in your code, to avoid a potential clash.
+%% Instances shrink towards the empty atom, ''.
+-spec atom() -> proper_types:type().
+atom() ->
+ ?WRAPPER([
+ {generator, fun proper_gen:atom_gen/1},
+ {reverse_gen, fun proper_gen:atom_rev/1},
+ {size_transform, fun(Size) -> erlang:min(Size,255) end},
+ {is_instance, fun atom_is_instance/1}
+ ]).
+
+atom_is_instance(X) ->
+ is_atom(X)
+ %% We return false for atoms starting with '$', since these are
+ %% atoms used internally and never produced by the atom generator.
+ andalso (X =:= '' orelse hd(atom_to_list(X)) =/= $$).
+
+%% @doc All binaries. Instances shrink towards the empty binary, `<<>>'.
+-spec binary() -> proper_types:type().
+binary() ->
+ ?WRAPPER([
+ {generator, fun proper_gen:binary_gen/1},
+ {reverse_gen, fun proper_gen:binary_rev/1},
+ {is_instance, fun erlang:is_binary/1}
+ ]).
+
+%% @doc All binaries with a byte size of `Len'.
+%% `Len' must be an Erlang expression that evaluates to a non-negative integer.
+%% Instances shrink towards binaries of zeroes.
+-spec binary(length()) -> proper_types:type().
+binary(Len) ->
+ ?WRAPPER([
+ {env, Len},
+ {generator, {typed, fun binary_len_gen/1}},
+ {reverse_gen, fun proper_gen:binary_rev/1},
+ {is_instance, {typed, fun binary_len_is_instance/2}}
+ ]).
+
+binary_len_gen(Type) ->
+ Len = get_prop(env, Type),
+ proper_gen:binary_len_gen(Len).
+
+binary_len_is_instance(Type, X) ->
+ Len = get_prop(env, Type),
+ is_binary(X) andalso byte_size(X) =:= Len.
+
+%% @doc All bitstrings. Instances shrink towards the empty bitstring, `<<>>'.
+-spec bitstring() -> proper_types:type().
+bitstring() ->
+ ?WRAPPER([
+ {generator, fun proper_gen:bitstring_gen/1},
+ {reverse_gen, fun proper_gen:bitstring_rev/1},
+ {is_instance, fun erlang:is_bitstring/1}
+ ]).
+
+%% @doc All bitstrings with a bit size of `Len'.
+%% `Len' must be an Erlang expression that evaluates to a non-negative integer.
+%% Instances shrink towards bitstrings of zeroes
+-spec bitstring(length()) -> proper_types:type().
+bitstring(Len) ->
+ ?WRAPPER([
+ {env, Len},
+ {generator, {typed, fun bitstring_len_gen/1}},
+ {reverse_gen, fun proper_gen:bitstring_rev/1},
+ {is_instance, {typed, fun bitstring_len_is_instance/2}}
+ ]).
+
+bitstring_len_gen(Type) ->
+ Len = get_prop(env, Type),
+ proper_gen:bitstring_len_gen(Len).
+
+bitstring_len_is_instance(Type, X) ->
+ Len = get_prop(env, Type),
+ is_bitstring(X) andalso bit_size(X) =:= Len.
+
+%% @doc All lists containing elements of type `ElemType'.
+%% Instances shrink towards the empty list, `[]'.
+-spec list(ElemType::raw_type()) -> proper_types:type().
+% TODO: subtyping would be useful here (list, vector, fixed_list)
+list(RawElemType) ->
+ ElemType = cook_outer(RawElemType),
+ ?CONTAINER([
+ {generator, {typed, fun list_gen/2}},
+ {is_instance, {typed, fun list_is_instance/2}},
+ {internal_type, ElemType},
+ {get_length, fun erlang:length/1},
+ {split, fun lists:split/2},
+ {join, fun lists:append/2},
+ {get_indices, fun list_get_indices/2},
+ {remove, fun proper_arith:list_remove/2},
+ {retrieve, fun lists:nth/2},
+ {update, fun proper_arith:list_update/3}
+ ]).
+
+list_gen(Type, Size) ->
+ ElemType = get_prop(internal_type, Type),
+ proper_gen:list_gen(Size, ElemType).
+
+list_is_instance(Type, X) ->
+ ElemType = get_prop(internal_type, Type),
+ list_test(X, ElemType).
+
+%% @doc A type that generates exactly the list `List'. Instances shrink towards
+%% shorter sublists of the original list.
+-spec shrink_list([term()]) -> proper_types:type().
+shrink_list(List) ->
+ ?CONTAINER([
+ {env, List},
+ {generator, {typed, fun shrink_list_gen/1}},
+ {is_instance, {typed, fun shrink_list_is_instance/2}},
+ {get_length, fun erlang:length/1},
+ {split, fun lists:split/2},
+ {join, fun lists:append/2},
+ {get_indices, fun list_get_indices/2},
+ {remove, fun proper_arith:list_remove/2}
+ ]).
+
+shrink_list_gen(Type) ->
+ get_prop(env, Type).
+
+shrink_list_is_instance(Type, X) ->
+ List = get_prop(env, Type),
+ is_sublist(X, List).
+
+-spec is_sublist([term()], [term()]) -> boolean().
+is_sublist([], _) -> true;
+is_sublist(_, []) -> false;
+is_sublist([H|T1], [H|T2]) -> is_sublist(T1, T2);
+is_sublist(Slice, [_|T2]) -> is_sublist(Slice, T2).
+
+-spec list_test(proper_gen:imm_instance(), proper_types:type()) -> boolean().
+list_test(X, ElemType) ->
+ is_list(X) andalso lists:all(fun(E) -> is_instance(E, ElemType) end, X).
+
+%% @private
+-spec list_get_indices(proper_gen:generator(), list()) -> [position()].
+list_get_indices(_, List) ->
+ lists:seq(1, length(List)).
+
+%% @private
+%% This assumes that:
+%% - instances of size S are always valid instances of size >S
+%% - any recursive calls inside Gen are lazy
+-spec distlist(size(), proper_gen:sized_generator(), boolean()) ->
+ proper_types:type().
+distlist(Size, Gen, NonEmpty) ->
+ ParentType = case NonEmpty of
+ true -> non_empty(list(Gen(Size)));
+ false -> list(Gen(Size))
+ end,
+ ?SUBTYPE(ParentType, [
+ {subenv, {Size, Gen, NonEmpty}},
+ {generator, {typed, fun distlist_gen/1}}
+ ]).
+
+distlist_gen(Type) ->
+ {Size, Gen, NonEmpty} = get_prop(subenv, Type),
+ proper_gen:distlist_gen(Size, Gen, NonEmpty).
+
+%% @doc All lists of length `Len' containing elements of type `ElemType'.
+%% `Len' must be an Erlang expression that evaluates to a non-negative integer.
+-spec vector(length(), ElemType::raw_type()) -> proper_types:type().
+vector(Len, RawElemType) ->
+ ElemType = cook_outer(RawElemType),
+ ?CONTAINER([
+ {env, Len},
+ {generator, {typed, fun vector_gen/1}},
+ {is_instance, {typed, fun vector_is_instance/2}},
+ {internal_type, ElemType},
+ {get_indices, fun vector_get_indices/2},
+ {retrieve, fun lists:nth/2},
+ {update, fun proper_arith:list_update/3}
+ ]).
+
+vector_gen(Type) ->
+ Len = get_prop(env, Type),
+ ElemType = get_prop(internal_type, Type),
+ proper_gen:vector_gen(Len, ElemType).
+
+vector_is_instance(Type, X) ->
+ Len = get_prop(env, Type),
+ ElemType = get_prop(internal_type, Type),
+ is_list(X)
+ andalso length(X) =:= Len
+ andalso lists:all(fun(E) -> is_instance(E, ElemType) end, X).
+
+vector_get_indices(Type, _X) ->
+ lists:seq(1, get_prop(env, Type)).
+
+%% @doc The union of all types in `ListOfTypes'. `ListOfTypes' can't be empty.
+%% The random instance generator is equally likely to choose any one of the
+%% types in `ListOfTypes'. The shrinking subsystem will always try to shrink an
+%% instance of a type union to an instance of the first type in `ListOfTypes',
+%% thus you should write the simplest case first.
+-spec union(ListOfTypes::[raw_type(),...]) -> proper_types:type().
+union(RawChoices) ->
+ Choices = [cook_outer(C) || C <- RawChoices],
+ ?BASIC([
+ {env, Choices},
+ {generator, {typed, fun union_gen/1}},
+ {is_instance, {typed, fun union_is_instance/2}},
+ {shrinkers, [fun union_shrinker_1/3, fun union_shrinker_2/3]}
+ ]).
+
+union_gen(Type) ->
+ Choices = get_prop(env,Type),
+ proper_gen:union_gen(Choices).
+
+union_is_instance(Type, X) ->
+ Choices = get_prop(env, Type),
+ lists:any(fun(C) -> is_instance(X, C) end, Choices).
+
+union_shrinker_1(X, Type, S) ->
+ Choices = get_prop(env, Type),
+ proper_shrink:union_first_choice_shrinker(X, Choices, S).
+
+union_shrinker_2(X, Type, S) ->
+ Choices = get_prop(env, Type),
+ proper_shrink:union_recursive_shrinker(X, Choices, S).
+
+%% @doc A specialization of {@link union/1}, where each type in `ListOfTypes' is
+%% assigned a frequency. Frequencies must be Erlang expressions that evaluate to
+%% positive integers. Types with larger frequencies are more likely to be chosen
+%% by the random instance generator. The shrinking subsystem will ignore the
+%% frequencies and try to shrink towards the first type in the list.
+-spec weighted_union(ListOfTypes::[{frequency(),raw_type()},...]) ->
+ proper_types:type().
+weighted_union(RawFreqChoices) ->
+ CookFreqType = fun({Freq,RawType}) -> {Freq,cook_outer(RawType)} end,
+ FreqChoices = lists:map(CookFreqType, RawFreqChoices),
+ Choices = [T || {_F,T} <- FreqChoices],
+ ?SUBTYPE(union(Choices), [
+ {subenv, FreqChoices},
+ {generator, {typed, fun weighted_union_gen/1}}
+ ]).
+
+weighted_union_gen(Gen) ->
+ FreqChoices = get_prop(subenv, Gen),
+ proper_gen:weighted_union_gen(FreqChoices).
+
+%% @private
+-spec safe_union([raw_type(),...]) -> proper_types:type().
+safe_union(RawChoices) ->
+ Choices = [cook_outer(C) || C <- RawChoices],
+ subtype(
+ [{subenv, Choices},
+ {generator, {typed, fun safe_union_gen/1}}],
+ union(Choices)).
+
+safe_union_gen(Type) ->
+ Choices = get_prop(subenv, Type),
+ proper_gen:safe_union_gen(Choices).
+
+%% @private
+-spec safe_weighted_union([{frequency(),raw_type()},...]) ->
+ proper_types:type().
+safe_weighted_union(RawFreqChoices) ->
+ CookFreqType = fun({Freq,RawType}) ->
+ {Freq,cook_outer(RawType)} end,
+ FreqChoices = lists:map(CookFreqType, RawFreqChoices),
+ Choices = [T || {_F,T} <- FreqChoices],
+ subtype([{subenv, FreqChoices},
+ {generator, {typed, fun safe_weighted_union_gen/1}}],
+ union(Choices)).
+
+safe_weighted_union_gen(Type) ->
+ FreqChoices = get_prop(subenv, Type),
+ proper_gen:safe_weighted_union_gen(FreqChoices).
+
+%% @doc All tuples whose i-th element is an instance of the type at index i of
+%% `ListOfTypes'. Also written simply as a tuple of types.
+-spec tuple(ListOfTypes::[raw_type()]) -> proper_types:type().
+tuple(RawFields) ->
+ Fields = [cook_outer(F) || F <- RawFields],
+ ?CONTAINER([
+ {env, Fields},
+ {generator, {typed, fun tuple_gen/1}},
+ {is_instance, {typed, fun tuple_is_instance/2}},
+ {internal_types, list_to_tuple(Fields)},
+ {get_indices, fun tuple_get_indices/2},
+ {retrieve, fun erlang:element/2},
+ {update, fun tuple_update/3}
+ ]).
+
+tuple_gen(Type) ->
+ Fields = get_prop(env, Type),
+ proper_gen:tuple_gen(Fields).
+
+tuple_is_instance(Type, X) ->
+ Fields = get_prop(env, Type),
+ is_tuple(X) andalso fixed_list_test(tuple_to_list(X), Fields).
+
+tuple_get_indices(Type, _X) ->
+ lists:seq(1, length(get_prop(env, Type))).
+
+-spec tuple_update(index(), value(), tuple()) -> tuple().
+tuple_update(Index, NewElem, Tuple) ->
+ setelement(Index, Tuple, NewElem).
+
+%% @doc Tuples whose elements are all of type `ElemType'.
+%% Instances shrink towards the 0-size tuple, `{}'.
+-spec loose_tuple(ElemType::raw_type()) -> proper_types:type().
+loose_tuple(RawElemType) ->
+ ElemType = cook_outer(RawElemType),
+ ?WRAPPER([
+ {env, ElemType},
+ {generator, {typed, fun loose_tuple_gen/2}},
+ {reverse_gen, {typed, fun loose_tuple_rev/2}},
+ {is_instance, {typed, fun loose_tuple_is_instance/2}}
+ ]).
+
+loose_tuple_gen(Type, Size) ->
+ ElemType = get_prop(env, Type),
+ proper_gen:loose_tuple_gen(Size, ElemType).
+
+loose_tuple_rev(Type, X) ->
+ ElemType = get_prop(env, Type),
+ proper_gen:loose_tuple_rev(X, ElemType).
+
+loose_tuple_is_instance(Type, X) ->
+ ElemType = get_prop(env, Type),
+ is_tuple(X) andalso list_test(tuple_to_list(X), ElemType).
+
+%% @doc Singleton type consisting only of `E'. `E' must be an evaluated term.
+%% Also written simply as `E'.
+-spec exactly(term()) -> proper_types:type().
+exactly(E) ->
+ ?BASIC([
+ {env, E},
+ {generator, {typed, fun exactly_gen/1}},
+ {is_instance, {typed, fun exactly_is_instance/2}}
+ ]).
+
+exactly_gen(Type) ->
+ E = get_prop(env, Type),
+ proper_gen:exactly_gen(E).
+
+exactly_is_instance(Type, X) ->
+ E = get_prop(env, Type),
+ X =:= E.
+
+%% @doc All lists whose i-th element is an instance of the type at index i of
+%% `ListOfTypes'. Also written simply as a list of types.
+-spec fixed_list(ListOfTypes::maybe_improper_list(raw_type(),raw_type()|[])) ->
+ proper_types:type().
+fixed_list(MaybeImproperRawFields) ->
+ %% CAUTION: must handle improper lists
+ {Fields, Internal, Len, Retrieve, Update} =
+ case proper_arith:cut_improper_tail(MaybeImproperRawFields) of
+ % TODO: have cut_improper_tail return the length and use it in test?
+ {ProperRawHead, ImproperRawTail} ->
+ HeadLen = length(ProperRawHead),
+ CookedHead = [cook_outer(F) || F <- ProperRawHead],
+ CookedTail = cook_outer(ImproperRawTail),
+ {{CookedHead,CookedTail},
+ CookedHead ++ CookedTail,
+ HeadLen + 1,
+ fun(I,L) -> improper_list_retrieve(I, L, HeadLen) end,
+ fun(I,V,L) -> improper_list_update(I, V, L, HeadLen) end};
+ ProperRawFields ->
+ LocalFields = [cook_outer(F) || F <- ProperRawFields],
+ {LocalFields,
+ LocalFields,
+ length(ProperRawFields),
+ fun lists:nth/2,
+ fun proper_arith:list_update/3}
+ end,
+ ?CONTAINER([
+ {env, {Fields, Len}},
+ {generator, {typed, fun fixed_list_gen/1}},
+ {is_instance, {typed, fun fixed_list_is_instance/2}},
+ {internal_types, Internal},
+ {get_indices, fun fixed_list_get_indices/2},
+ {retrieve, Retrieve},
+ {update, Update}
+ ]).
+
+fixed_list_gen(Type) ->
+ {Fields, _} = get_prop(env, Type),
+ proper_gen:fixed_list_gen(Fields).
+
+fixed_list_is_instance(Type, X) ->
+ {Fields, _} = get_prop(env, Type),
+ fixed_list_test(X, Fields).
+
+fixed_list_get_indices(Type, _X) ->
+ {_, Len} = get_prop(env, Type),
+ lists:seq(1, Len).
+
+-spec fixed_list_test(proper_gen:imm_instance(),
+ [proper_types:type()] | {[proper_types:type()],
+ proper_types:type()}) ->
+ boolean().
+fixed_list_test(X, {ProperHead,ImproperTail}) ->
+ is_list(X) andalso
+ begin
+ ProperHeadLen = length(ProperHead),
+ proper_arith:head_length(X) >= ProperHeadLen andalso
+ begin
+ {XHead,XTail} = lists:split(ProperHeadLen, X),
+ fixed_list_test(XHead, ProperHead)
+ andalso is_instance(XTail, ImproperTail)
+ end
+ end;
+fixed_list_test(X, ProperFields) ->
+ is_list(X)
+ andalso length(X) =:= length(ProperFields)
+ andalso lists:all(fun({E,T}) -> is_instance(E, T) end,
+ lists:zip(X, ProperFields)).
+
+%% TODO: Move these 2 functions to proper_arith?
+-spec improper_list_retrieve(index(), nonempty_improper_list(value(),value()),
+ pos_integer()) -> value().
+improper_list_retrieve(Index, List, HeadLen) ->
+ case Index =< HeadLen of
+ true -> lists:nth(Index, List);
+ false -> lists:nthtail(HeadLen, List)
+ end.
+
+-spec improper_list_update(index(), value(),
+ nonempty_improper_list(value(),value()),
+ pos_integer()) ->
+ nonempty_improper_list(value(),value()).
+improper_list_update(Index, Value, List, HeadLen) ->
+ case Index =< HeadLen of
+ %% TODO: This happens to work, but is not implied by list_update's spec.
+ true -> proper_arith:list_update(Index, Value, List);
+ false -> lists:sublist(List, HeadLen) ++ Value
+ end.
+
+%% @doc All pure functions that map instances of `ArgTypes' to instances of
+%% `RetType'. The syntax `function(Arity, RetType)' is also acceptable.
+-spec function(ArgTypes::[raw_type()] | arity(), RetType::raw_type()) ->
+ proper_types:type().
+function(Arity, RawRetType) when is_integer(Arity), Arity >= 0, Arity =< 255 ->
+ RetType = cook_outer(RawRetType),
+ ?BASIC([
+ {env, {Arity, RetType}},
+ {generator, {typed, fun function_gen/1}},
+ {is_instance, {typed, fun function_is_instance/2}}
+ ]);
+function(RawArgTypes, RawRetType) ->
+ function(length(RawArgTypes), RawRetType).
+
+function_gen(Type) ->
+ {Arity, RetType} = get_prop(env, Type),
+ proper_gen:function_gen(Arity, RetType).
+
+function_is_instance(Type, X) ->
+ {Arity, RetType} = get_prop(env, Type),
+ is_function(X, Arity)
+ %% TODO: what if it's not a function we produced?
+ andalso equal_types(RetType, proper_gen:get_ret_type(X)).
+
+%% @doc All Erlang terms (that PropEr can produce). For reasons of efficiency,
+%% functions are never produced as instances of this type.<br />
+%% CAUTION: Instances of this type are expensive to produce, shrink and instance-
+%% check, both in terms of processing time and consumed memory. Only use this
+%% type if you are certain that you need it.
+-spec any() -> proper_types:type().
+any() ->
+ AllTypes = [integer(),float(),atom(),bitstring(),?LAZY(loose_tuple(any())),
+ ?LAZY(list(any()))],
+ ?SUBTYPE(union(AllTypes), [
+ {generator, fun proper_gen:any_gen/1}
+ ]).
+
+
+%%------------------------------------------------------------------------------
+%% Type aliases
+%%------------------------------------------------------------------------------
+
+%% @equiv integer(inf, inf)
+-spec integer() -> proper_types:type().
+integer() -> integer(inf, inf).
+
+%% @equiv integer(0, inf)
+-spec non_neg_integer() -> proper_types:type().
+non_neg_integer() -> integer(0, inf).
+
+%% @equiv integer(1, inf)
+-spec pos_integer() -> proper_types:type().
+pos_integer() -> integer(1, inf).
+
+%% @equiv integer(inf, -1)
+-spec neg_integer() -> proper_types:type().
+neg_integer() -> integer(inf, -1).
+
+%% @equiv integer(Low, High)
+-spec range(extint(), extint()) -> proper_types:type().
+range(Low, High) -> integer(Low, High).
+
+%% @equiv float(inf, inf)
+-spec float() -> proper_types:type().
+float() -> float(inf, inf).
+
+%% @equiv float(0.0, inf)
+-spec non_neg_float() -> proper_types:type().
+non_neg_float() -> float(0.0, inf).
+
+%% @equiv union([integer(), float()])
+-spec number() -> proper_types:type().
+number() -> union([integer(), float()]).
+
+%% @doc The atoms `true' and `false'. Instances shrink towards `false'.
+-spec boolean() -> proper_types:type().
+boolean() -> union(['false', 'true']).
+
+%% @equiv integer(0, 255)
+-spec byte() -> proper_types:type().
+byte() -> integer(0, 255).
+
+%% @equiv integer(0, 16#10ffff)
+-spec char() -> proper_types:type().
+char() -> integer(0, 16#10ffff).
+
+%% @equiv list(any())
+-spec list() -> proper_types:type().
+list() -> list(any()).
+
+%% @equiv loose_tuple(any())
+-spec tuple() -> proper_types:type().
+tuple() -> loose_tuple(any()).
+
+%% @equiv list(char())
+-spec string() -> proper_types:type().
+string() -> list(char()).
+
+%% @equiv weighted_union(FreqChoices)
+-spec wunion([{frequency(),raw_type()},...]) -> proper_types:type().
+wunion(FreqChoices) -> weighted_union(FreqChoices).
+
+%% @equiv any()
+-spec term() -> proper_types:type().
+term() -> any().
+
+%% @equiv union([non_neg_integer() | infinity])
+-spec timeout() -> proper_types:type().
+timeout() -> union([non_neg_integer(), 'infinity']).
+
+%% @equiv integer(0, 255)
+-spec arity() -> proper_types:type().
+arity() -> integer(0, 255).
+
+
+%%------------------------------------------------------------------------------
+%% QuickCheck compatibility types
+%%------------------------------------------------------------------------------
+
+%% @doc Small integers (bound by the current value of the `size' parameter).
+%% Instances shrink towards `0'.
+-spec int() -> proper_types:type().
+int() -> ?SIZED(Size, integer(-Size,Size)).
+
+%% @doc Small non-negative integers (bound by the current value of the `size'
+%% parameter). Instances shrink towards `0'.
+-spec nat() -> proper_types:type().
+nat() -> ?SIZED(Size, integer(0,Size)).
+
+%% @equiv integer()
+-spec largeint() -> proper_types:type().
+largeint() -> integer().
+
+%% @equiv float()
+-spec real() -> proper_types:type().
+real() -> float().
+
+%% @equiv boolean()
+-spec bool() -> proper_types:type().
+bool() -> boolean().
+
+%% @equiv integer(Low, High)
+-spec choose(extint(), extint()) -> proper_types:type().
+choose(Low, High) -> integer(Low, High).
+
+%% @equiv union(Choices)
+-spec elements([raw_type(),...]) -> proper_types:type().
+elements(Choices) -> union(Choices).
+
+%% @equiv union(Choices)
+-spec oneof([raw_type(),...]) -> proper_types:type().
+oneof(Choices) -> union(Choices).
+
+%% @equiv weighted_union(Choices)
+-spec frequency([{frequency(),raw_type()},...]) -> proper_types:type().
+frequency(FreqChoices) -> weighted_union(FreqChoices).
+
+%% @equiv exactly(E)
+-spec return(term()) -> proper_types:type().
+return(E) -> exactly(E).
+
+%% @doc Adds a default value, `Default', to `Type'.
+%% The default serves as a primary shrinking target for instances, while it
+%% is also chosen by the random instance generation subsystem half the time.
+-spec default(raw_type(), raw_type()) -> proper_types:type().
+default(Default, Type) ->
+ union([Default, Type]).
+
+%% @doc All sorted lists containing elements of type `ElemType'.
+%% Instances shrink towards the empty list, `[]'.
+-spec orderedlist(ElemType::raw_type()) -> proper_types:type().
+orderedlist(RawElemType) ->
+ ?LET(L, list(RawElemType), lists:sort(L)).
+
+%% @equiv function(0, RetType)
+-spec function0(raw_type()) -> proper_types:type().
+function0(RetType) ->
+ function(0, RetType).
+
+%% @equiv function(1, RetType)
+-spec function1(raw_type()) -> proper_types:type().
+function1(RetType) ->
+ function(1, RetType).
+
+%% @equiv function(2, RetType)
+-spec function2(raw_type()) -> proper_types:type().
+function2(RetType) ->
+ function(2, RetType).
+
+%% @equiv function(3, RetType)
+-spec function3(raw_type()) -> proper_types:type().
+function3(RetType) ->
+ function(3, RetType).
+
+%% @equiv function(4, RetType)
+-spec function4(raw_type()) -> proper_types:type().
+function4(RetType) ->
+ function(4, RetType).
+
+%% @doc A specialization of {@link default/2}, where `Default' and `Type' are
+%% assigned weights to be considered by the random instance generator. The
+%% shrinking subsystem will ignore the weights and try to shrink using the
+%% default value.
+-spec weighted_default({frequency(),raw_type()}, {frequency(),raw_type()}) ->
+ proper_types:type().
+weighted_default(Default, Type) ->
+ weighted_union([Default, Type]).
+
+
+%%------------------------------------------------------------------------------
+%% Additional type specification functions
+%%------------------------------------------------------------------------------
+
+%% @doc Overrides the `size' parameter used when generating instances of
+%% `Type' with `NewSize'. Has no effect on size-less types, such as unions.
+%% Also, this will not affect the generation of any internal types contained in
+%% `Type', such as the elements of a list - those will still be generated
+%% using the test-wide value of `size'. One use of this function is to modify
+%% types to produce instances that grow faster or slower, like so:
+%% ```?SIZED(Size, resize(Size * 2, list(integer()))'''
+%% The above specifies a list type that grows twice as fast as normal lists.
+-spec resize(size(), Type::raw_type()) -> proper_types:type().
+resize(NewSize, RawType) ->
+ Type = cook_outer(RawType),
+ case find_prop(size_transform, Type) of
+ {ok,Transform} ->
+ add_prop(size_transform, fun(_S) -> Transform(NewSize) end, Type);
+ error ->
+ add_prop(size_transform, fun(_S) -> NewSize end, Type)
+ end.
+
+%% @doc This is a predefined constraint that can be applied to random-length
+%% list and binary types to ensure that the produced values are never empty.
+%%
+%% e.g. {@link list/0}, {@link string/0}, {@link binary/0})
+-spec non_empty(ListType::raw_type()) -> proper_types:type().
+non_empty(RawListType) ->
+ ?SUCHTHAT(L, RawListType, L =/= [] andalso L =/= <<>>).
+
+%% @doc Creates a new type which is equivalent to `Type', but whose instances
+%% are never shrunk by the shrinking subsystem.
+-spec noshrink(Type::raw_type()) -> proper_types:type().
+noshrink(RawType) ->
+ add_prop(noshrink, true, cook_outer(RawType)).
+
+%% @doc Associates the atom key `Parameter' with the value `Value' while
+%% generating instances of `Type'.
+-spec with_parameter(atom(), value(), Type::raw_type()) -> proper_types:type().
+with_parameter(Parameter, Value, RawType) ->
+ with_parameters([{Parameter,Value}], RawType).
+
+%% @doc Similar to {@link with_parameter/3}, but accepts a list of
+%% `{Parameter, Value}' pairs.
+-spec with_parameters([{atom(),value()}], Type::raw_type()) ->
+ proper_types:type().
+with_parameters(PVlist, RawType) ->
+ Type = cook_outer(RawType),
+ case find_prop(parameters, Type) of
+ {ok,Params} when is_list(Params) ->
+ append_list_to_prop(parameters, PVlist, Type);
+ error ->
+ add_prop(parameters, PVlist, Type)
+ end.
+
+%% @doc Returns the value associated with `Parameter', or `Default' in case
+%% `Parameter' is not associated with any value.
+-spec parameter(atom(), value()) -> value().
+parameter(Parameter, Default) ->
+ Parameters =
+ case erlang:get('$parameters') of
+ undefined -> [];
+ List -> List
+ end,
+ proplists:get_value(Parameter, Parameters, Default).
+
+%% @equiv parameter(Parameter, undefined)
+-spec parameter(atom()) -> value().
+parameter(Parameter) ->
+ parameter(Parameter, undefined).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_typeserver.erl b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_typeserver.erl
new file mode 100644
index 0000000000..1677b4efb8
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/proper/proper_typeserver.erl
@@ -0,0 +1,2402 @@
+%%% Copyright 2010-2015 Manolis Papadakis <manopapad@gmail.com>,
+%%% Eirini Arvaniti <eirinibob@gmail.com>
+%%% and Kostis Sagonas <kostis@cs.ntua.gr>
+%%%
+%%% This file is part of PropEr.
+%%%
+%%% PropEr is free software: you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation, either version 3 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% PropEr is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.
+
+%%% @copyright 2010-2015 Manolis Papadakis, Eirini Arvaniti and Kostis Sagonas
+%%% @version {@version}
+%%% @author Manolis Papadakis
+
+%%% @doc Erlang type system - PropEr type system integration module.
+%%%
+%%% PropEr can parse types expressed in Erlang's type language and convert them
+%%% to its own type format. Such expressions can be used instead of regular type
+%%% constructors in the second argument of `?FORALL's. No extra notation is
+%%% required; PropEr will detect which calls correspond to native types by
+%%% applying a parse transform during compilation. This parse transform is
+%%% automatically applied to any module that includes the `proper.hrl' header
+%%% file. You can disable this feature by compiling your modules with
+%%% `-DPROPER_NO_TRANS'. Note that this will currently also disable the
+%%% automatic exporting of properties.
+%%%
+%%% The use of native types in properties is subject to the following usage
+%%% rules:
+%%% <ul>
+%%% <li>Native types cannot be used outside of `?FORALL's.</li>
+%%% <li>Inside `?FORALL's, native types can be combined with other native
+%%% types, and even with PropEr types, inside tuples and lists (the constructs
+%%% `[...]', `{...}' and `++' are all allowed).</li>
+%%% <li>All other constructs of Erlang's built-in type system (e.g. `|' for
+%%% union, `_' as an alias of `any()', `<<_:_>>' binary type syntax and
+%%% `fun((...) -> ...)' function type syntax) are not allowed in `?FORALL's,
+%%% because they are rejected by the Erlang parser.</li>
+%%% <li>Anything other than a tuple constructor, list constructor, `++'
+%%% application, local or remote call will automatically be considered a
+%%% PropEr type constructor and not be processed further by the parse
+%%% transform.</li>
+%%% <li>Parametric native types are fully supported; of course, they can only
+%%% appear instantiated in a `?FORALL'. The arguments of parametric native
+%%% types are always interpreted as native types.</li>
+%%% <li>Parametric PropEr types, on the other hand, can take any kind of
+%%% argument. You can even mix native and PropEr types in the arguments of a
+%%% PropEr type. For example, assuming that the following declarations are
+%%% present:
+%%% ``` my_proper_type() -> ?LET(...).
+%%% -type my_native_type() :: ... .'''
+%%% Then the following expressions are all legal:
+%%% ``` vector(2, my_native_type())
+%%% function(0, my_native_type())
+%%% union([my_proper_type(), my_native_type()])''' </li>
+%%% <li>Some type constructors can take native types as arguments (but only
+%%% inside `?FORALL's):
+%%% <ul>
+%%% <li>`?SUCHTHAT', `?SUCHTHATMAYBE', `non_empty', `noshrink': these work
+%%% with native types too</li>
+%%% <li>`?LAZY', `?SHRINK', `resize', `?SIZED': these don't work with native
+%%% types</li>
+%%% <li>`?LET', `?LETSHRINK': only the top-level base type can be a native
+%%% type</li>
+%%% </ul></li>
+%%% <li>Native type declarations in the `?FORALL's of a module can reference any
+%%% custom type declared in a `-type' or `-opaque' attribute of the same
+%%% module, as long as no module identifier is used.</li>
+%%% <li>Typed records cannot be referenced inside `?FORALL's using the
+%%% `#rec_name{}' syntax. To use a typed record in a `?FORALL', enclose the
+%%% record in a custom type like so:
+%%% ``` -type rec_name() :: #rec_name{}. '''
+%%% and use the custom type instead.</li>
+%%% <li>`?FORALL's may contain references to self-recursive or mutually
+%%% recursive native types, so long as each type in the hierarchy has a clear
+%%% base case.
+%%% Currently, PropEr requires that the toplevel of any recursive type
+%%% declaration is either a (maybe empty) list or a union containing at least
+%%% one choice that doesn't reference the type directly (it may, however,
+%%% reference any of the types that are mutually recursive with it). This
+%%% means, for example, that some valid recursive type declarations, such as
+%%% this one:
+%%% ``` ?FORALL(..., a(), ...) '''
+%%% where:
+%%% ``` -type a() :: {'a','none' | a()}. '''
+%%% are not accepted by PropEr. However, such types can be rewritten in a way
+%%% that allows PropEr to parse them:
+%%% ``` ?FORALL(..., a(), ...) '''
+%%% where:
+%%% ``` -type a() :: {'a','none'} | {'a',a()}. '''
+%%% This also means that recursive record declarations are not allowed:
+%%% ``` ?FORALL(..., rec(), ...) '''
+%%% where:
+%%% ``` -type rec() :: #rec{}.
+%%% -record(rec, {a = 0 :: integer(), b = 'nil' :: 'nil' | #rec{}}). '''
+%%% A little rewritting can usually remedy this problem as well:
+%%% ``` ?FORALL(..., rec(), ...) '''
+%%% where:
+%%% ``` -type rec() :: #rec{b :: 'nil'} | #rec{b :: rec()}.
+%%% -record(rec, {a = 0 :: integer(), b = 'nil' :: 'nil' | #rec{}}). '''
+%%% </li>
+%%% <li>Remote types may be referenced in a `?FORALL', so long as they are
+%%% exported from the remote module. Currently, PropEr requires that any
+%%% remote modules whose types are directly referenced from within properties
+%%% are present in the code path at compile time, either compiled with
+%%% `debug_info' enabled or in source form. If PropEr cannot find a remote
+%%% module at all, finds only a compiled object file with no debug
+%%% information or fails to compile the source file, all calls to that module
+%%% will automatically be considered calls to PropEr type constructors.</li>
+%%% <li>For native types to be translated correctly, both the module that
+%%% contains the `?FORALL' declaration as well as any module that contains
+%%% the declaration of a type referenced (directly or indirectly) from inside
+%%% a `?FORALL' must be present in the code path at runtime, either compiled
+%%% with `debug_info' enabled or in source form.</li>
+%%% <li>Local types with the same name as an auto-imported BIF are not accepted
+%%% by PropEr, unless the BIF in question has been declared in a
+%%% `no_auto_import' option.</li>
+%%% <li>When an expression can be interpreted both as a PropEr type and as a
+%%% native type, the former takes precedence. This means that a function
+%%% `foo()' will shadow a type `foo()' if they are both present in the module.
+%%% The same rule applies to remote functions and types as well.</li>
+%%% <li>The above may cause some confusion when list syntax is used:
+%%% <ul>
+%%% <li>The expression `[integer()]' can be interpreted both ways, so the
+%%% PropEr way applies. Therefore, instances of this type will always be
+%%% lists of length 1, not arbitrary integer lists, as would be expected
+%%% when interpreting the expression as a native type.</li>
+%%% <li>Assuming that a custom type foo/1 has been declared, the expression
+%%% `foo([integer()])' can only be interpreted as a native type declaration,
+%%% which means that the generic type of integer lists will be passed to
+%%% `foo/1'.</li>
+%%% </ul></li>
+%%% <li>Currently, PropEr does not detect the following mistakes:
+%%% <ul>
+%%% <li>inline record-field specializations that reference non-existent
+%%% fields</li>
+%%% <li>type parameters that are not present in the RHS of a `-type'
+%%% declaration</li>
+%%% <li>using `_' as a type variable in the LHS of a `-type' declaration</li>
+%%% <li>using the same variable in more than one position in the LHS of a
+%%% `-type' declaration</li>
+%%% </ul>
+%%% </li>
+%%% </ul>
+%%%
+%%% You can use <a href="#index">these</a> functions to try out the type
+%%% translation subsystem.
+%%%
+%%% CAUTION: These functions should never be used inside properties. They are
+%%% meant for demonstration purposes only.
+
+-module(proper_typeserver).
+-behaviour(gen_server).
+-export([demo_translate_type/2, demo_is_instance/3]).
+
+-export([start/0, restart/0, stop/0, create_spec_test/3, get_exp_specced/1,
+ is_instance/3, translate_type/1]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+-export([get_exp_info/1, match/2]).
+
+-export_type([imm_type/0, mod_exp_types/0, mod_exp_funs/0]).
+
+-include("proper_internal.hrl").
+
+
+%%------------------------------------------------------------------------------
+%% Macros
+%%------------------------------------------------------------------------------
+
+-define(SRC_FILE_EXT, ".erl").
+
+%% CAUTION: all these must be sorted
+-define(STD_TYPES_0,
+ [any,arity,atom,binary,bitstring,bool,boolean,byte,char,float,integer,
+ list,neg_integer,non_neg_integer,number,pos_integer,string,term,
+ timeout]).
+-define(HARD_ADTS,
+ %% gb_trees:iterator and gb_sets:iterator are NOT hardcoded
+ [{{array,0},array}, {{array,1},proper_array},
+ {{dict,0},dict}, {{dict,2},proper_dict},
+ {{gb_set,0},gb_sets}, {{gb_set,1},proper_gb_sets},
+ {{gb_tree,0},gb_trees}, {{gb_tree,2},proper_gb_trees},
+ {{orddict,2},proper_orddict},
+ {{ordset,1},proper_ordsets},
+ {{queue,0},queue}, {{queue,1},proper_queue},
+ {{set,0},sets}, {{set,1},proper_sets}]).
+-define(HARD_ADT_MODS,
+ [{array, [{{array,0},
+ {{type,0,record,[{atom,0,array}]},[]}}]},
+ {dict, [{{dict,0},
+ {{type,0,record,[{atom,0,dict}]},[]}}]},
+ {gb_sets, [{{gb_set,0},
+ {{type,0,tuple,[{type,0,non_neg_integer,[]},
+ {type,0,gb_set_node,[]}]},[]}}]},
+ {gb_trees, [{{gb_tree,0},
+ {{type,0,tuple,[{type,0,non_neg_integer,[]},
+ {type,0,gb_tree_node,[]}]},[]}}]},
+ %% Our parametric ADTs are already declared as normal types, we just
+ %% need to change them to opaques.
+ {proper_array, [{{array,1},already_declared}]},
+ {proper_dict, [{{dict,2},already_declared}]},
+ {proper_gb_sets, [{{gb_set,1},already_declared},
+ {{iterator,1},already_declared}]},
+ {proper_gb_trees, [{{gb_tree,2},already_declared},
+ {{iterator,2},already_declared}]},
+ {proper_orddict, [{{orddict,2},already_declared}]},
+ {proper_ordsets, [{{ordset,1},already_declared}]},
+ {proper_queue, [{{queue,1},already_declared}]},
+ {proper_sets, [{{set,1},already_declared}]},
+ {queue, [{{queue,0},
+ {{type,0,tuple,[{type,0,list,[]},{type,0,list,[]}]},[]}}]},
+ {sets, [{{set,0},
+ {{type,0,record,[{atom,0,set}]},[]}}]}]).
+
+
+%%------------------------------------------------------------------------------
+%% Types
+%%------------------------------------------------------------------------------
+
+-type type_name() :: atom().
+-type var_name() :: atom(). %% TODO: also integers?
+-type field_name() :: atom().
+
+-type type_kind() :: 'type' | 'record'.
+-type type_ref() :: {type_kind(),type_name(),arity()}.
+-ifdef(NO_MODULES_IN_OPAQUES).
+-type substs_dict() :: dict(). %% dict(field_name(),ret_type())
+-else.
+-type substs_dict() :: dict:dict(field_name(),ret_type()).
+-endif.
+-type full_type_ref() :: {mod_name(),type_kind(),type_name(),
+ [ret_type()] | substs_dict()}.
+-type symb_info() :: 'not_symb' | {'orig_abs',abs_type()}.
+-type type_repr() :: {'abs_type',abs_type(),[var_name()],symb_info()}
+ | {'cached',fin_type(),abs_type(),symb_info()}
+ | {'abs_record',[{field_name(),abs_type()}]}.
+-type gen_fun() :: fun((size()) -> fin_type()).
+-type rec_fun() :: fun(([gen_fun()],size()) -> fin_type()).
+-type rec_arg() :: {boolean() | {'list',boolean(),rec_fun()},full_type_ref()}.
+-type rec_args() :: [rec_arg()].
+-type ret_type() :: {'simple',fin_type()} | {'rec',rec_fun(),rec_args()}.
+-type rec_fun_info() :: {pos_integer(),pos_integer(),[arity(),...],
+ [rec_fun(),...]}.
+
+-type imm_type_ref() :: {type_name(),arity()}.
+-type hard_adt_repr() :: {abs_type(),[var_name()]} | 'already_declared'.
+-type fun_ref() :: {fun_name(),arity()}.
+-type fun_repr() :: fun_clause_repr().
+-type fun_clause_repr() :: {[abs_type()],abs_type()}.
+-type proc_fun_ref() :: {fun_name(),[abs_type()],abs_type()}.
+-type full_imm_type_ref() :: {mod_name(),type_name(),arity()}.
+-type imm_stack() :: [full_imm_type_ref()].
+-type pat_field() :: 0 | 1 | atom().
+-type pattern() :: loose_tuple(pat_field()).
+-type next_step() :: 'none' | 'take_head' | {'match_with',pattern()}.
+
+-ifdef(NO_MODULES_IN_OPAQUES).
+%% @private_type
+-type mod_exp_types() :: set(). %% set(imm_type_ref())
+-type mod_types() :: dict(). %% dict(type_ref(),type_repr())
+%% @private_type
+-type mod_exp_funs() :: set(). %% set(fun_ref())
+-type mod_specs() :: dict(). %% dict(fun_ref(),fun_repr())
+-else.
+%% @private_type
+-type mod_exp_types() :: sets:set(imm_type_ref()).
+-type mod_types() :: dict:dict(type_ref(),type_repr()).
+%% @private_type
+-type mod_exp_funs() :: sets:set(fun_ref()).
+-type mod_specs() :: dict:dict(fun_ref(),fun_repr()).
+-endif.
+
+-ifdef(NO_MODULES_IN_OPAQUES).
+-record(state,
+ {cached = dict:new() :: dict(), %% dict(imm_type(),fin_type())
+ exp_types = dict:new() :: dict(), %% dict(mod_name(),mod_exp_types())
+ types = dict:new() :: dict(), %% dict(mod_name(),mod_types())
+ exp_specs = dict:new() :: dict()}). %% dict(mod_name(),mod_specs())
+-else.
+-record(state,
+ {cached = dict:new() :: dict:dict(), %% dict(imm_type(),fin_type())
+ exp_types = dict:new() :: dict:dict(), %% dict(mod_name(),mod_exp_types())
+ types = dict:new() :: dict:dict(), %% dict(mod_name(),mod_types())
+ exp_specs = dict:new() :: dict:dict()}). %% dict(mod_name(),mod_specs())
+%% {cached = dict:new() :: dict:dict(imm_type(),fin_type()),
+%% exp_types = dict:new() :: dict:dict(mod_name(),mod_exp_types()),
+%% types = dict:new() :: dict:dict(mod_name(),mod_types()),
+%% exp_specs = dict:new() :: dict:dict(mod_name(),mod_specs())}).
+-endif.
+-type state() :: #state{}.
+
+-record(mod_info,
+ {mod_exp_types = sets:new() :: mod_exp_types(),
+ mod_types = dict:new() :: mod_types(),
+ mod_opaques = sets:new() :: mod_exp_types(),
+ mod_exp_funs = sets:new() :: mod_exp_funs(),
+ mod_specs = dict:new() :: mod_specs()}).
+-type mod_info() :: #mod_info{}.
+
+-type stack() :: [full_type_ref() | 'tuple' | 'list' | 'union' | 'fun'].
+-ifdef(NO_MODULES_IN_OPAQUES).
+-type var_dict() :: dict(). %% dict(var_name(),ret_type())
+-else.
+-type var_dict() :: dict:dict(var_name(),ret_type()).
+-endif.
+%% @private_type
+-type imm_type() :: {mod_name(),string()}.
+%% @alias
+-type fin_type() :: proper_types:type().
+-type tagged_result(T) :: {'ok',T} | 'error'.
+-type tagged_result2(T,S) :: {'ok',T,S} | 'error'.
+%% @alias
+-type rich_result(T) :: {'ok',T} | {'error',term()}.
+-type rich_result2(T,S) :: {'ok',T,S} | {'error',term()}.
+-type false_positive_mfas() :: proper:false_positive_mfas().
+
+-type server_call() :: {'create_spec_test',mfa(),timeout(),false_positive_mfas()}
+ | {'get_exp_specced',mod_name()}
+ | {'get_type_repr',mod_name(),type_ref(),boolean()}
+ | {'translate_type',imm_type()}.
+-type server_response() :: rich_result(proper:test())
+ | rich_result([mfa()])
+ | rich_result(type_repr())
+ | rich_result(fin_type()).
+
+
+%%------------------------------------------------------------------------------
+%% Server interface functions
+%%------------------------------------------------------------------------------
+
+%% @private
+-spec start() -> 'ok'.
+start() ->
+ {ok,TypeserverPid} = gen_server:start_link(?MODULE, dummy, []),
+ put('$typeserver_pid', TypeserverPid),
+ ok.
+
+%% @private
+-spec restart() -> 'ok'.
+restart() ->
+ TypeserverPid = get('$typeserver_pid'),
+ case (TypeserverPid =:= undefined orelse not is_process_alive(TypeserverPid)) of
+ true -> start();
+ false -> ok
+ end.
+
+%% @private
+-spec stop() -> 'ok'.
+stop() ->
+ TypeserverPid = get('$typeserver_pid'),
+ erase('$typeserver_pid'),
+ gen_server:cast(TypeserverPid, stop).
+
+%% @private
+-spec create_spec_test(mfa(), timeout(), false_positive_mfas()) -> rich_result(proper:test()).
+create_spec_test(MFA, SpecTimeout, FalsePositiveMFAs) ->
+ TypeserverPid = get('$typeserver_pid'),
+ gen_server:call(TypeserverPid, {create_spec_test,MFA,SpecTimeout,FalsePositiveMFAs}).
+
+%% @private
+-spec get_exp_specced(mod_name()) -> rich_result([mfa()]).
+get_exp_specced(Mod) ->
+ TypeserverPid = get('$typeserver_pid'),
+ gen_server:call(TypeserverPid, {get_exp_specced,Mod}).
+
+-spec get_type_repr(mod_name(), type_ref(), boolean()) ->
+ rich_result(type_repr()).
+get_type_repr(Mod, TypeRef, IsRemote) ->
+ TypeserverPid = get('$typeserver_pid'),
+ gen_server:call(TypeserverPid, {get_type_repr,Mod,TypeRef,IsRemote}).
+
+%% @private
+-spec translate_type(imm_type()) -> rich_result(fin_type()).
+translate_type(ImmType) ->
+ TypeserverPid = get('$typeserver_pid'),
+ gen_server:call(TypeserverPid, {translate_type,ImmType}).
+
+%% @doc Translates the native type expression `TypeExpr' (which should be
+%% provided inside a string) into a PropEr type, which can then be passed to any
+%% of the demo functions defined in the {@link proper_gen} module. PropEr acts
+%% as if it found this type expression inside the code of module `Mod'.
+-spec demo_translate_type(mod_name(), string()) -> rich_result(fin_type()).
+demo_translate_type(Mod, TypeExpr) ->
+ start(),
+ Result = translate_type({Mod,TypeExpr}),
+ stop(),
+ Result.
+
+%% @doc Checks if `Term' is a valid instance of native type `TypeExpr' (which
+%% should be provided inside a string). PropEr acts as if it found this type
+%% expression inside the code of module `Mod'.
+-spec demo_is_instance(term(), mod_name(), string()) ->
+ boolean() | {'error',term()}.
+demo_is_instance(Term, Mod, TypeExpr) ->
+ case parse_type(TypeExpr) of
+ {ok,TypeForm} ->
+ start(),
+ Result =
+ %% Force the typeserver to load the module.
+ case translate_type({Mod,"integer()"}) of
+ {ok,_FinType} ->
+ try is_instance(Term, Mod, TypeForm)
+ catch
+ throw:{'$typeserver',Reason} -> {error, Reason}
+ end;
+ {error,_Reason} = Error ->
+ Error
+ end,
+ stop(),
+ Result;
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+
+%%------------------------------------------------------------------------------
+%% Implementation of gen_server interface
+%%------------------------------------------------------------------------------
+
+%% @private
+-spec init(_) -> {'ok',state()}.
+init(_) ->
+ {ok, #state{}}.
+
+%% @private
+-spec handle_call(server_call(), _, state()) ->
+ {'reply',server_response(),state()}.
+handle_call({create_spec_test,MFA,SpecTimeout,FalsePositiveMFAs}, _From, State) ->
+ case create_spec_test(MFA, SpecTimeout, FalsePositiveMFAs, State) of
+ {ok,Test,NewState} ->
+ {reply, {ok,Test}, NewState};
+ {error,_Reason} = Error ->
+ {reply, Error, State}
+ end;
+handle_call({get_exp_specced,Mod}, _From, State) ->
+ case get_exp_specced(Mod, State) of
+ {ok,MFAs,NewState} ->
+ {reply, {ok,MFAs}, NewState};
+ {error,_Reason} = Error ->
+ {reply, Error, State}
+ end;
+handle_call({get_type_repr,Mod,TypeRef,IsRemote}, _From, State) ->
+ case get_type_repr(Mod, TypeRef, IsRemote, State) of
+ {ok,TypeRepr,NewState} ->
+ {reply, {ok,TypeRepr}, NewState};
+ {error,_Reason} = Error ->
+ {reply, Error, State}
+ end;
+handle_call({translate_type,ImmType}, _From, State) ->
+ case translate_type(ImmType, State) of
+ {ok,FinType,NewState} ->
+ {reply, {ok,FinType}, NewState};
+ {error,_Reason} = Error ->
+ {reply, Error, State}
+ end.
+
+%% @private
+-spec handle_cast('stop', state()) -> {'stop','normal',state()}.
+handle_cast(stop, State) ->
+ {stop, normal, State}.
+
+%% @private
+-spec handle_info(term(), state()) -> {'stop',{'received_info',term()},state()}.
+handle_info(Info, State) ->
+ {stop, {received_info,Info}, State}.
+
+%% @private
+-spec terminate(term(), state()) -> 'ok'.
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+-spec code_change(term(), state(), _) -> {'ok',state()}.
+code_change(_OldVsn, State, _) ->
+ {ok, State}.
+
+
+%%------------------------------------------------------------------------------
+%% Top-level interface
+%%------------------------------------------------------------------------------
+
+-spec create_spec_test(mfa(), timeout(), false_positive_mfas(), state()) ->
+ rich_result2(proper:test(),state()).
+create_spec_test(MFA, SpecTimeout, FalsePositiveMFAs, State) ->
+ case get_exp_spec(MFA, State) of
+ {ok,FunRepr,NewState} ->
+ make_spec_test(MFA, FunRepr, SpecTimeout, FalsePositiveMFAs, NewState);
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec get_exp_spec(mfa(), state()) -> rich_result2(fun_repr(),state()).
+get_exp_spec({Mod,Fun,Arity} = MFA, State) ->
+ case add_module(Mod, State) of
+ {ok,#state{exp_specs = ExpSpecs} = NewState} ->
+ ModExpSpecs = dict:fetch(Mod, ExpSpecs),
+ case dict:find({Fun,Arity}, ModExpSpecs) of
+ {ok,FunRepr} ->
+ {ok, FunRepr, NewState};
+ error ->
+ {error, {function_not_exported_or_specced,MFA}}
+ end;
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec make_spec_test(mfa(), fun_repr(), timeout(), false_positive_mfas(), state()) ->
+ rich_result2(proper:test(),state()).
+make_spec_test({Mod,_Fun,_Arity}=MFA, {Domain,_Range}=FunRepr, SpecTimeout, FalsePositiveMFAs, State) ->
+ case convert(Mod, {type,0,'$fixed_list',Domain}, State) of
+ {ok,FinType,NewState} ->
+ Test = ?FORALL(Args, FinType, apply_spec_test(MFA, FunRepr, SpecTimeout, FalsePositiveMFAs, Args)),
+ {ok, Test, NewState};
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec apply_spec_test(mfa(), fun_repr(), timeout(), false_positive_mfas(), term()) -> proper:test().
+apply_spec_test({Mod,Fun,_Arity}=MFA, {_Domain,Range}, SpecTimeout, FalsePositiveMFAs, Args) ->
+ ?TIMEOUT(SpecTimeout,
+ begin
+ %% NOTE: only call apply/3 inside try/catch (do not trust ?MODULE:is_instance/3)
+ Result =
+ try apply(Mod,Fun,Args) of
+ X -> {ok, X}
+ catch
+ X:Y -> {X, Y}
+ end,
+ case Result of
+ {ok, Z} ->
+ case ?MODULE:is_instance(Z,Mod,Range) of
+ true ->
+ true;
+ false when is_function(FalsePositiveMFAs) ->
+ FalsePositiveMFAs(MFA, Args, {fail, Z});
+ false ->
+ false
+ end;
+ Exception when is_function(FalsePositiveMFAs) ->
+ case FalsePositiveMFAs(MFA, Args, Exception) of
+ true ->
+ true;
+ false ->
+ error(Exception, erlang:get_stacktrace())
+ end;
+ Exception ->
+ error(Exception, erlang:get_stacktrace())
+ end
+ end).
+
+-spec get_exp_specced(mod_name(), state()) -> rich_result2([mfa()],state()).
+get_exp_specced(Mod, State) ->
+ case add_module(Mod, State) of
+ {ok,#state{exp_specs = ExpSpecs} = NewState} ->
+ ModExpSpecs = dict:fetch(Mod, ExpSpecs),
+ ExpSpecced = [{Mod,F,A} || {F,A} <- dict:fetch_keys(ModExpSpecs)],
+ {ok, ExpSpecced, NewState};
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec get_type_repr(mod_name(), type_ref(), boolean(), state()) ->
+ rich_result2(type_repr(),state()).
+get_type_repr(Mod, {type,Name,Arity} = TypeRef, true, State) ->
+ case prepare_for_remote(Mod, Name, Arity, State) of
+ {ok,NewState} ->
+ get_type_repr(Mod, TypeRef, false, NewState);
+ {error,_Reason} = Error ->
+ Error
+ end;
+get_type_repr(Mod, TypeRef, false, #state{types = Types} = State) ->
+ ModTypes = dict:fetch(Mod, Types),
+ case dict:find(TypeRef, ModTypes) of
+ {ok,TypeRepr} ->
+ {ok, TypeRepr, State};
+ error ->
+ {error, {missing_type,Mod,TypeRef}}
+ end.
+
+-spec prepare_for_remote(mod_name(), type_name(), arity(), state()) ->
+ rich_result(state()).
+prepare_for_remote(RemMod, Name, Arity, State) ->
+ case add_module(RemMod, State) of
+ {ok,#state{exp_types = ExpTypes} = NewState} ->
+ RemModExpTypes = dict:fetch(RemMod, ExpTypes),
+ case sets:is_element({Name,Arity}, RemModExpTypes) of
+ true -> {ok, NewState};
+ false -> {error, {type_not_exported,{RemMod,Name,Arity}}}
+ end;
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec translate_type(imm_type(), state()) -> rich_result2(fin_type(),state()).
+translate_type({Mod,Str} = ImmType, #state{cached = Cached} = State) ->
+ case dict:find(ImmType, Cached) of
+ {ok,Type} ->
+ {ok, Type, State};
+ error ->
+ case parse_type(Str) of
+ {ok,TypeForm} ->
+ case add_module(Mod, State) of
+ {ok,NewState} ->
+ case convert(Mod, TypeForm, NewState) of
+ {ok,FinType,
+ #state{cached = Cached} = FinalState} ->
+ NewCached = dict:store(ImmType, FinType,
+ Cached),
+ {ok, FinType,
+ FinalState#state{cached = NewCached}};
+ {error,_Reason} = Error ->
+ Error
+ end;
+ {error,_Reason} = Error ->
+ Error
+ end;
+ {error,Reason} ->
+ {error, {parse_error,Str,Reason}}
+ end
+ end.
+
+-spec parse_type(string()) -> rich_result(abs_type()).
+parse_type(Str) ->
+ TypeStr = "-type mytype() :: " ++ Str ++ ".",
+ case erl_scan:string(TypeStr) of
+ {ok,Tokens,_EndLocation} ->
+ case erl_parse:parse_form(Tokens) of
+ {ok,{attribute,_Line,type,{mytype,TypeExpr,[]}}} ->
+ {ok, TypeExpr};
+ {error,_ErrorInfo} = Error ->
+ Error
+ end;
+ {error,ErrorInfo,_EndLocation} ->
+ {error, ErrorInfo}
+ end.
+
+-spec add_module(mod_name(), state()) -> rich_result(state()).
+add_module(Mod, #state{exp_types = ExpTypes} = State) ->
+ case dict:is_key(Mod, ExpTypes) of
+ true ->
+ {ok, State};
+ false ->
+ case get_code_and_exports(Mod) of
+ {ok,AbsCode,ModExpFuns} ->
+ RawModInfo = get_mod_info(Mod, AbsCode, ModExpFuns),
+ ModInfo = process_adts(Mod, RawModInfo),
+ {ok, store_mod_info(Mod,ModInfo,State)};
+ {error,Reason} ->
+ {error, {cant_load_code,Mod,Reason}}
+ end
+ end.
+
+%% @private
+-spec get_exp_info(mod_name()) -> rich_result2(mod_exp_types(),mod_exp_funs()).
+get_exp_info(Mod) ->
+ case get_code_and_exports(Mod) of
+ {ok,AbsCode,ModExpFuns} ->
+ RawModInfo = get_mod_info(Mod, AbsCode, ModExpFuns),
+ {ok, RawModInfo#mod_info.mod_exp_types, ModExpFuns};
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec get_code_and_exports(mod_name()) ->
+ rich_result2([abs_form()],mod_exp_funs()).
+get_code_and_exports(Mod) ->
+ case code:get_object_code(Mod) of
+ {Mod, ObjBin, _ObjFileName} ->
+ case get_chunks(ObjBin) of
+ {ok,_AbsCode,_ModExpFuns} = Result ->
+ Result;
+ {error,Reason} ->
+ get_code_and_exports_from_source(Mod, Reason)
+ end;
+ error ->
+ get_code_and_exports_from_source(Mod, cant_find_object_file)
+ end.
+
+-spec get_code_and_exports_from_source(mod_name(), term()) ->
+ rich_result2([abs_form()],mod_exp_funs()).
+get_code_and_exports_from_source(Mod, ObjError) ->
+ SrcFileName = atom_to_list(Mod) ++ ?SRC_FILE_EXT,
+ case code:where_is_file(SrcFileName) of
+ FullSrcFileName when is_list(FullSrcFileName) ->
+ Opts = [binary,debug_info,return_errors,{d,'PROPER_REMOVE_PROPS'}],
+ case compile:file(FullSrcFileName, Opts) of
+ {ok,Mod,Binary} ->
+ get_chunks(Binary);
+ {error,Errors,_Warnings} ->
+ {error, {ObjError,{cant_compile_source_file,Errors}}}
+ end;
+ non_existing ->
+ {error, {ObjError,cant_find_source_file}}
+ end.
+
+-spec get_chunks(string() | binary()) ->
+ rich_result2([abs_form()],mod_exp_funs()).
+get_chunks(ObjFile) ->
+ case beam_lib:chunks(ObjFile, [abstract_code,exports]) of
+ {ok,{_Mod,[{abstract_code,AbsCodeChunk},{exports,ExpFunsList}]}} ->
+ case AbsCodeChunk of
+ {raw_abstract_v1,AbsCode} ->
+ %% HACK: Add a declaration for iolist() to every module
+ {ok, add_iolist(AbsCode), sets:from_list(ExpFunsList)};
+ no_abstract_code ->
+ {error, no_abstract_code};
+ _ ->
+ {error, unsupported_abstract_code_format}
+ end;
+ {error,beam_lib,Reason} ->
+ {error, Reason}
+ end.
+
+-spec add_iolist([abs_form()]) -> [abs_form()].
+add_iolist(Forms) ->
+ IOListDef =
+ {type,0,maybe_improper_list,
+ [{type,0,union,[{type,0,byte,[]},{type,0,binary,[]},
+ {type,0,iolist,[]}]},
+ {type,0,binary,[]}]},
+ IOListDecl = {attribute,0,type,{iolist,IOListDef,[]}},
+ [IOListDecl | Forms].
+
+-spec get_mod_info(mod_name(), [abs_form()], mod_exp_funs()) -> mod_info().
+get_mod_info(Mod, AbsCode, ModExpFuns) ->
+ StartModInfo = #mod_info{mod_exp_funs = ModExpFuns},
+ ImmModInfo = lists:foldl(fun add_mod_info/2, StartModInfo, AbsCode),
+ #mod_info{mod_specs = AllModSpecs} = ImmModInfo,
+ IsExported = fun(FunRef,_FunRepr) -> sets:is_element(FunRef,ModExpFuns) end,
+ ModExpSpecs = dict:filter(IsExported, AllModSpecs),
+ ModInfo = ImmModInfo#mod_info{mod_specs = ModExpSpecs},
+ case orddict:find(Mod, ?HARD_ADT_MODS) of
+ {ok,ModADTs} ->
+ #mod_info{mod_exp_types = ModExpTypes, mod_types = ModTypes,
+ mod_opaques = ModOpaques} = ModInfo,
+ ModADTsSet =
+ sets:from_list([ImmTypeRef
+ || {ImmTypeRef,_HardADTRepr} <- ModADTs]),
+ NewModExpTypes = sets:union(ModExpTypes, ModADTsSet),
+ NewModTypes = lists:foldl(fun store_hard_adt/2, ModTypes, ModADTs),
+ NewModOpaques = sets:union(ModOpaques, ModADTsSet),
+ ModInfo#mod_info{mod_exp_types = NewModExpTypes,
+ mod_types = NewModTypes,
+ mod_opaques = NewModOpaques};
+ error ->
+ ModInfo
+ end.
+
+-spec store_hard_adt({imm_type_ref(),hard_adt_repr()}, mod_types()) ->
+ mod_types().
+store_hard_adt({_ImmTypeRef,already_declared}, ModTypes) ->
+ ModTypes;
+store_hard_adt({{Name,Arity},{TypeForm,VarNames}}, ModTypes) ->
+ TypeRef = {type,Name,Arity},
+ TypeRepr = {abs_type,TypeForm,VarNames,not_symb},
+ dict:store(TypeRef, TypeRepr, ModTypes).
+
+-spec add_mod_info(abs_form(), mod_info()) -> mod_info().
+add_mod_info({attribute,_Line,export_type,TypesList},
+ #mod_info{mod_exp_types = ModExpTypes} = ModInfo) ->
+ NewModExpTypes = sets:union(sets:from_list(TypesList), ModExpTypes),
+ ModInfo#mod_info{mod_exp_types = NewModExpTypes};
+add_mod_info({attribute,_Line,type,{{record,RecName},Fields,[]}},
+ #mod_info{mod_types = ModTypes} = ModInfo) ->
+ FieldInfo = [process_rec_field(F) || F <- Fields],
+ NewModTypes = dict:store({record,RecName,0}, {abs_record,FieldInfo},
+ ModTypes),
+ ModInfo#mod_info{mod_types = NewModTypes};
+add_mod_info({attribute,_Line,record,{RecName,Fields}},
+ #mod_info{mod_types = ModTypes} = ModInfo) ->
+ case dict:is_key(RecName, ModTypes) of
+ true ->
+ ModInfo;
+ false ->
+ A = erl_anno:new(0),
+ TypedRecord = {attribute,A,type,{{record,RecName},Fields,[]}},
+ add_mod_info(TypedRecord, ModInfo)
+ end;
+add_mod_info({attribute,_Line,Kind,{Name,TypeForm,VarForms}},
+ #mod_info{mod_types = ModTypes,
+ mod_opaques = ModOpaques} = ModInfo)
+ when Kind =:= type; Kind =:= opaque ->
+ Arity = length(VarForms),
+ VarNames = [V || {var,_,V} <- VarForms],
+ %% TODO: No check whether variables are different, or non-'_'.
+ NewModTypes = dict:store({type,Name,Arity},
+ {abs_type,TypeForm,VarNames,not_symb}, ModTypes),
+ NewModOpaques =
+ case Kind of
+ type -> ModOpaques;
+ opaque -> sets:add_element({Name,Arity}, ModOpaques)
+ end,
+ ModInfo#mod_info{mod_types = NewModTypes, mod_opaques = NewModOpaques};
+add_mod_info({attribute,_Line,spec,{RawFunRef,[RawFirstClause | _Rest]}},
+ #mod_info{mod_specs = ModSpecs} = ModInfo) ->
+ FunRef = case RawFunRef of
+ {_Mod,Name,Arity} -> {Name,Arity};
+ {_Name,_Arity} = F -> F
+ end,
+ %% TODO: We just take the first function clause.
+ FirstClause = process_fun_clause(RawFirstClause),
+ NewModSpecs = dict:store(FunRef, FirstClause, ModSpecs),
+ ModInfo#mod_info{mod_specs = NewModSpecs};
+add_mod_info(_Form, ModInfo) ->
+ ModInfo.
+
+-spec process_rec_field(abs_rec_field()) -> {field_name(),abs_type()}.
+process_rec_field({record_field,_,{atom,_,FieldName}}) ->
+ {FieldName, {type,0,any,[]}};
+process_rec_field({record_field,_,{atom,_,FieldName},_Initialization}) ->
+ {FieldName, {type,0,any,[]}};
+process_rec_field({typed_record_field,RecField,FieldType}) ->
+ {FieldName,_} = process_rec_field(RecField),
+ {FieldName, FieldType}.
+
+-spec process_fun_clause(abs_type()) -> fun_clause_repr().
+process_fun_clause({type,_,'fun',[{type,_,product,Domain},Range]}) ->
+ {Domain, Range};
+process_fun_clause({type,_,bounded_fun,[MainClause,Constraints]}) ->
+ {RawDomain,RawRange} = process_fun_clause(MainClause),
+ VarSubsts = [{V,T} || {type,_,constraint,
+ [{atom,_,is_subtype},[{var,_,V},T]]} <- Constraints,
+ V =/= '_'],
+ VarSubstsDict = dict:from_list(VarSubsts),
+ Domain = [update_vars(A, VarSubstsDict, false) || A <- RawDomain],
+ Range = update_vars(RawRange, VarSubstsDict, false),
+ {Domain, Range}.
+
+-spec store_mod_info(mod_name(), mod_info(), state()) -> state().
+store_mod_info(Mod, #mod_info{mod_exp_types = ModExpTypes, mod_types = ModTypes,
+ mod_specs = ImmModExpSpecs},
+ #state{exp_types = ExpTypes, types = Types,
+ exp_specs = ExpSpecs} = State) ->
+ NewExpTypes = dict:store(Mod, ModExpTypes, ExpTypes),
+ NewTypes = dict:store(Mod, ModTypes, Types),
+ ModExpSpecs = dict:map(fun unbound_to_any/2, ImmModExpSpecs),
+ NewExpSpecs = dict:store(Mod, ModExpSpecs, ExpSpecs),
+ State#state{exp_types = NewExpTypes, types = NewTypes,
+ exp_specs = NewExpSpecs}.
+
+-spec unbound_to_any(fun_ref(), fun_repr()) -> fun_repr().
+unbound_to_any(_FunRef, {Domain,Range}) ->
+ EmptySubstsDict = dict:new(),
+ NewDomain = [update_vars(A,EmptySubstsDict,true) || A <- Domain],
+ NewRange = update_vars(Range, EmptySubstsDict, true),
+ {NewDomain, NewRange}.
+
+
+%%------------------------------------------------------------------------------
+%% ADT translation functions
+%%------------------------------------------------------------------------------
+
+-spec process_adts(mod_name(), mod_info()) -> mod_info().
+process_adts(Mod,
+ #mod_info{mod_exp_types = ModExpTypes, mod_opaques = ModOpaques,
+ mod_specs = ModExpSpecs} = ModInfo) ->
+ %% TODO: No warning on unexported opaques.
+ case sets:to_list(sets:intersection(ModExpTypes,ModOpaques)) of
+ [] ->
+ ModInfo;
+ ModADTs ->
+ %% TODO: No warning on unexported API functions.
+ ModExpSpecsList = [{Name,Domain,Range}
+ || {{Name,_Arity},{Domain,Range}}
+ <- dict:to_list(ModExpSpecs)],
+ AddADT = fun(ADT,Acc) -> add_adt(Mod,ADT,Acc,ModExpSpecsList) end,
+ lists:foldl(AddADT, ModInfo, ModADTs)
+ end.
+
+-spec add_adt(mod_name(), imm_type_ref(), mod_info(), [proc_fun_ref()]) ->
+ mod_info().
+add_adt(Mod, {Name,Arity}, #mod_info{mod_types = ModTypes} = ModInfo,
+ ModExpFunSpecs) ->
+ ADTRef = {type,Name,Arity},
+ {abs_type,InternalRepr,VarNames,not_symb} = dict:fetch(ADTRef, ModTypes),
+ FullADTRef = {Mod,Name,Arity},
+ %% TODO: No warning on unsuitable range.
+ SymbCalls1 = [get_symb_call(FullADTRef,Spec) || Spec <- ModExpFunSpecs],
+ %% TODO: No warning on bad use of variables.
+ SymbCalls2 = [fix_vars(FullADTRef,Call,RangeVars,VarNames)
+ || {ok,Call,RangeVars} <- SymbCalls1],
+ case [Call || {ok,Call} <- SymbCalls2] of
+ [] ->
+ %% TODO: No warning on no acceptable spec.
+ ModInfo;
+ SymbCalls3 ->
+ NewADTRepr = {abs_type,{type,0,union,SymbCalls3},VarNames,
+ {orig_abs,InternalRepr}},
+ NewModTypes = dict:store(ADTRef, NewADTRepr, ModTypes),
+ ModInfo#mod_info{mod_types = NewModTypes}
+ end.
+
+-spec get_symb_call(full_imm_type_ref(), proc_fun_ref()) ->
+ tagged_result2(abs_type(),[var_name()]).
+get_symb_call({Mod,_TypeName,_Arity} = FullADTRef, {FunName,Domain,Range}) ->
+ BaseCall = {type,0,tuple,[{atom,0,'$call'},{atom,0,Mod},{atom,0,FunName},
+ {type,0,'$fixed_list',Domain}]},
+ unwrap_range(FullADTRef, BaseCall, Range, false).
+
+-spec unwrap_range(full_imm_type_ref(), abs_type() | next_step(), abs_type(),
+ boolean()) ->
+ tagged_result2(abs_type() | next_step(),[var_name()]).
+unwrap_range(FullADTRef, Call, {paren_type,_,[Type]}, TestRun) ->
+ unwrap_range(FullADTRef, Call, Type, TestRun);
+unwrap_range(FullADTRef, Call, {ann_type,_,[_Var,Type]}, TestRun) ->
+ unwrap_range(FullADTRef, Call, Type, TestRun);
+unwrap_range(FullADTRef, Call, {type,_,list,[ElemType]}, TestRun) ->
+ unwrap_list(FullADTRef, Call, ElemType, TestRun);
+unwrap_range(FullADTRef, Call, {type,_,maybe_improper_list,[Cont,_Term]},
+ TestRun) ->
+ unwrap_list(FullADTRef, Call, Cont, TestRun);
+unwrap_range(FullADTRef, Call, {type,_,nonempty_list,[ElemType]}, TestRun) ->
+ unwrap_list(FullADTRef, Call, ElemType, TestRun);
+unwrap_range(FullADTRef, Call, {type,_,nonempty_improper_list,[Cont,_Term]},
+ TestRun) ->
+ unwrap_list(FullADTRef, Call, Cont, TestRun);
+unwrap_range(FullADTRef, Call,
+ {type,_,nonempty_maybe_improper_list,[Cont,_Term]}, TestRun) ->
+ unwrap_list(FullADTRef, Call, Cont, TestRun);
+unwrap_range(_FullADTRef, _Call, {type,_,tuple,any}, _TestRun) ->
+ error;
+unwrap_range(FullADTRef, Call, {type,_,tuple,FieldForms}, TestRun) ->
+ Translates = fun(T) -> unwrap_range(FullADTRef,none,T,true) =/= error end,
+ case proper_arith:find_first(Translates, FieldForms) of
+ none ->
+ error;
+ {TargetPos,TargetElem} ->
+ Pattern = get_pattern(TargetPos, FieldForms),
+ case TestRun of
+ true ->
+ NewCall =
+ case Call of
+ none -> {match_with,Pattern};
+ _ -> Call
+ end,
+ {ok, NewCall, []};
+ false ->
+ AbsPattern = term_to_singleton_type(Pattern),
+ NewCall =
+ {type,0,tuple,
+ [{atom,0,'$call'},{atom,0,?MODULE},{atom,0,match},
+ {type,0,'$fixed_list',[AbsPattern,Call]}]},
+ unwrap_range(FullADTRef, NewCall, TargetElem, TestRun)
+ end
+ end;
+unwrap_range(FullADTRef, Call, {type,_,union,Choices}, TestRun) ->
+ TestedChoices = [unwrap_range(FullADTRef,none,C,true) || C <- Choices],
+ NotError = fun(error) -> false; (_) -> true end,
+ case proper_arith:find_first(NotError, TestedChoices) of
+ none ->
+ error;
+ {_ChoicePos,{ok,none,_RangeVars}} ->
+ error;
+ {ChoicePos,{ok,NextStep,_RangeVars}} ->
+ {A, [ChoiceElem|B]} = lists:split(ChoicePos-1, Choices),
+ OtherChoices = A ++ B,
+ DistinctChoice =
+ case NextStep of
+ take_head ->
+ fun cant_have_head/1;
+ {match_with,Pattern} ->
+ fun(C) -> cant_match(Pattern, C) end
+ end,
+ case {lists:all(DistinctChoice,OtherChoices), TestRun} of
+ {true,true} ->
+ {ok, NextStep, []};
+ {true,false} ->
+ unwrap_range(FullADTRef, Call, ChoiceElem, TestRun);
+ {false,_} ->
+ error
+ end
+ end;
+unwrap_range({_Mod,SameName,Arity}, Call, {type,_,SameName,ArgForms},
+ _TestRun) ->
+ RangeVars = [V || {var,_,V} <- ArgForms, V =/= '_'],
+ case length(ArgForms) =:= Arity andalso length(RangeVars) =:= Arity of
+ true -> {ok, Call, RangeVars};
+ false -> error
+ end;
+unwrap_range({SameMod,SameName,_Arity} = FullADTRef, Call,
+ {remote_type,_,[{atom,_,SameMod},{atom,_,SameName},ArgForms]},
+ TestRun) ->
+ unwrap_range(FullADTRef, Call, {type,0,SameName,ArgForms}, TestRun);
+unwrap_range(_FullADTRef, _Call, _Range, _TestRun) ->
+ error.
+
+-spec unwrap_list(full_imm_type_ref(), abs_type() | next_step(), abs_type(),
+ boolean()) ->
+ tagged_result2(abs_type() | next_step(),[var_name()]).
+unwrap_list(FullADTRef, Call, HeadType, TestRun) ->
+ NewCall =
+ case TestRun of
+ true ->
+ case Call of
+ none -> take_head;
+ _ -> Call
+ end;
+ false ->
+ {type,0,tuple,[{atom,0,'$call'},{atom,0,erlang},{atom,0,hd},
+ {type,0,'$fixed_list',[Call]}]}
+ end,
+ unwrap_range(FullADTRef, NewCall, HeadType, TestRun).
+
+-spec fix_vars(full_imm_type_ref(), abs_type(), [var_name()], [var_name()]) ->
+ tagged_result(abs_type()).
+fix_vars(FullADTRef, Call, RangeVars, VarNames) ->
+ NotAnyVar = fun(V) -> V =/= '_' end,
+ case no_duplicates(VarNames) andalso lists:all(NotAnyVar,VarNames) of
+ true ->
+ RawUsedVars =
+ collect_vars(FullADTRef, Call, [[V] || V <- RangeVars]),
+ UsedVars = [lists:usort(L) || L <- RawUsedVars],
+ case correct_var_use(UsedVars) of
+ true ->
+ PairAll = fun(L,Y) -> [{X,{var,0,Y}} || X <- L] end,
+ VarSubsts =
+ lists:flatten(lists:zipwith(PairAll,UsedVars,VarNames)),
+ VarSubstsDict = dict:from_list(VarSubsts),
+ {ok, update_vars(Call,VarSubstsDict,true)};
+ false ->
+ error
+ end;
+ false ->
+ error
+ end.
+
+-spec no_duplicates(list()) -> boolean().
+no_duplicates(L) ->
+ length(lists:usort(L)) =:= length(L).
+
+-spec correct_var_use([[var_name() | 0]]) -> boolean().
+correct_var_use(UsedVars) ->
+ NoNonVarArgs = fun([0|_]) -> false; (_) -> true end,
+ lists:all(NoNonVarArgs, UsedVars)
+ andalso no_duplicates(lists:flatten(UsedVars)).
+
+-spec collect_vars(full_imm_type_ref(), abs_type(), [[var_name() | 0]]) ->
+ [[var_name() | 0]].
+collect_vars(FullADTRef, {paren_type,_,[Type]}, UsedVars) ->
+ collect_vars(FullADTRef, Type, UsedVars);
+collect_vars(FullADTRef, {ann_type,_,[_Var,Type]}, UsedVars) ->
+ collect_vars(FullADTRef, Type, UsedVars);
+collect_vars(_FullADTRef, {type,_,tuple,any}, UsedVars) ->
+ UsedVars;
+collect_vars({_Mod,SameName,Arity} = FullADTRef, {type,_,SameName,ArgForms},
+ UsedVars) ->
+ case length(ArgForms) =:= Arity of
+ true ->
+ VarArgs = [V || {var,_,V} <- ArgForms, V =/= '_'],
+ case length(VarArgs) =:= Arity of
+ true ->
+ AddToList = fun(X,L) -> [X | L] end,
+ lists:zipwith(AddToList, VarArgs, UsedVars);
+ false ->
+ [[0|L] || L <- UsedVars]
+ end;
+ false ->
+ multi_collect_vars(FullADTRef, ArgForms, UsedVars)
+ end;
+collect_vars(FullADTRef, {type,_,_Name,ArgForms}, UsedVars) ->
+ multi_collect_vars(FullADTRef, ArgForms, UsedVars);
+collect_vars({SameMod,SameName,_Arity} = FullADTRef,
+ {remote_type,_,[{atom,_,SameMod},{atom,_,SameName},ArgForms]},
+ UsedVars) ->
+ collect_vars(FullADTRef, {type,0,SameName,ArgForms}, UsedVars);
+collect_vars(FullADTRef, {remote_type,_,[_RemModForm,_NameForm,ArgForms]},
+ UsedVars) ->
+ multi_collect_vars(FullADTRef, ArgForms, UsedVars);
+collect_vars(_FullADTRef, _Call, UsedVars) ->
+ UsedVars.
+
+-spec multi_collect_vars(full_imm_type_ref(), [abs_type()],
+ [[var_name() | 0]]) -> [[var_name() | 0]].
+multi_collect_vars({_Mod,_Name,Arity} = FullADTRef, Forms, UsedVars) ->
+ NoUsedVars = lists:duplicate(Arity, []),
+ MoreUsedVars = [collect_vars(FullADTRef,T,NoUsedVars) || T <- Forms],
+ CombineVars = fun(L1,L2) -> lists:zipwith(fun erlang:'++'/2, L1, L2) end,
+ lists:foldl(CombineVars, UsedVars, MoreUsedVars).
+
+-ifdef(NO_MODULES_IN_OPAQUES).
+-type var_substs_dict() :: dict().
+-else.
+-type var_substs_dict() :: dict:dict(var_name(),abs_type()).
+-endif.
+-spec update_vars(abs_type(), var_substs_dict(), boolean()) -> abs_type().
+update_vars({paren_type,Line,[Type]}, VarSubstsDict, UnboundToAny) ->
+ {paren_type, Line, [update_vars(Type,VarSubstsDict,UnboundToAny)]};
+update_vars({ann_type,Line,[Var,Type]}, VarSubstsDict, UnboundToAny) ->
+ {ann_type, Line, [Var,update_vars(Type,VarSubstsDict,UnboundToAny)]};
+update_vars({var,Line,VarName} = Call, VarSubstsDict, UnboundToAny) ->
+ case dict:find(VarName, VarSubstsDict) of
+ {ok,SubstType} ->
+ SubstType;
+ error when UnboundToAny =:= false ->
+ Call;
+ error when UnboundToAny =:= true ->
+ {type,Line,any,[]}
+ end;
+update_vars({remote_type,Line,[RemModForm,NameForm,ArgForms]}, VarSubstsDict,
+ UnboundToAny) ->
+ NewArgForms = [update_vars(A,VarSubstsDict,UnboundToAny) || A <- ArgForms],
+ {remote_type, Line, [RemModForm,NameForm,NewArgForms]};
+update_vars({type,_,tuple,any} = Call, _VarSubstsDict, _UnboundToAny) ->
+ Call;
+update_vars({type,Line,Name,ArgForms}, VarSubstsDict, UnboundToAny) ->
+ {type, Line, Name, [update_vars(A,VarSubstsDict,UnboundToAny)
+ || A <- ArgForms]};
+update_vars(Call, _VarSubstsDict, _UnboundToAny) ->
+ Call.
+
+
+%%------------------------------------------------------------------------------
+%% Match-related functions
+%%------------------------------------------------------------------------------
+
+-spec get_pattern(position(), [abs_type()]) -> pattern().
+get_pattern(TargetPos, FieldForms) ->
+ {0,RevPattern} = lists:foldl(fun add_field/2, {TargetPos,[]}, FieldForms),
+ list_to_tuple(lists:reverse(RevPattern)).
+
+-spec add_field(abs_type(), {non_neg_integer(),[pat_field()]}) ->
+ {non_neg_integer(),[pat_field(),...]}.
+add_field(_Type, {1,Acc}) ->
+ {0, [1|Acc]};
+add_field({atom,_,Tag}, {Left,Acc}) ->
+ {erlang:max(0,Left-1), [Tag|Acc]};
+add_field(_Type, {Left,Acc}) ->
+ {erlang:max(0,Left-1), [0|Acc]}.
+
+%% @private
+-spec match(pattern(), tuple()) -> term().
+match(Pattern, Term) when tuple_size(Pattern) =:= tuple_size(Term) ->
+ match(tuple_to_list(Pattern), tuple_to_list(Term), none, false);
+match(_Pattern, _Term) ->
+ throw(no_match).
+
+-spec match([pat_field()], [term()], 'none' | {'ok',T}, boolean()) -> T.
+match([], [], {ok,Target}, _TypeMode) ->
+ Target;
+match([0|PatRest], [_|ToMatchRest], Acc, TypeMode) ->
+ match(PatRest, ToMatchRest, Acc, TypeMode);
+match([1|PatRest], [Target|ToMatchRest], none, TypeMode) ->
+ match(PatRest, ToMatchRest, {ok,Target}, TypeMode);
+match([Tag|PatRest], [X|ToMatchRest], Acc, TypeMode) when is_atom(Tag) ->
+ MatchesTag =
+ case TypeMode of
+ true -> can_be_tag(Tag, X);
+ false -> Tag =:= X
+ end,
+ case MatchesTag of
+ true -> match(PatRest, ToMatchRest, Acc, TypeMode);
+ false -> throw(no_match)
+ end.
+
+%% CAUTION: these must be sorted
+-define(NON_ATOM_TYPES,
+ [arity,binary,bitstring,byte,char,float,'fun',function,integer,iodata,
+ iolist,list,maybe_improper_list,mfa,neg_integer,nil,no_return,
+ non_neg_integer,none,nonempty_improper_list,nonempty_list,
+ nonempty_maybe_improper_list,nonempty_string,number,pid,port,
+ pos_integer,range,record,reference,string,tuple]).
+-define(NON_TUPLE_TYPES,
+ [arity,atom,binary,bitstring,bool,boolean,byte,char,float,'fun',
+ function,identifier,integer,iodata,iolist,list,maybe_improper_list,
+ neg_integer,nil,no_return,node,non_neg_integer,none,
+ nonempty_improper_list,nonempty_list,nonempty_maybe_improper_list,
+ nonempty_string,number,pid,port,pos_integer,range,reference,string,
+ timeout]).
+-define(NO_HEAD_TYPES,
+ [arity,atom,binary,bitstring,bool,boolean,byte,char,float,'fun',
+ function,identifier,integer,mfa,module,neg_integer,nil,no_return,node,
+ non_neg_integer,none,number,pid,port,pos_integer,range,record,
+ reference,timeout,tuple]).
+
+-spec can_be_tag(atom(), abs_type()) -> boolean().
+can_be_tag(Tag, {ann_type,_,[_Var,Type]}) ->
+ can_be_tag(Tag, Type);
+can_be_tag(Tag, {paren_type,_,[Type]}) ->
+ can_be_tag(Tag, Type);
+can_be_tag(Tag, {atom,_,Atom}) ->
+ Tag =:= Atom;
+can_be_tag(_Tag, {integer,_,_Int}) ->
+ false;
+can_be_tag(_Tag, {op,_,_Op,_Arg}) ->
+ false;
+can_be_tag(_Tag, {op,_,_Op,_Arg1,_Arg2}) ->
+ false;
+can_be_tag(Tag, {type,_,BName,[]}) when BName =:= bool; BName =:= boolean ->
+ is_boolean(Tag);
+can_be_tag(Tag, {type,_,timeout,[]}) ->
+ Tag =:= infinity;
+can_be_tag(Tag, {type,_,union,Choices}) ->
+ lists:any(fun(C) -> can_be_tag(Tag,C) end, Choices);
+can_be_tag(_Tag, {type,_,Name,_Args}) ->
+ not ordsets:is_element(Name, ?NON_ATOM_TYPES);
+can_be_tag(_Tag, _Type) ->
+ true.
+
+-spec cant_match(pattern(), abs_type()) -> boolean().
+cant_match(Pattern, {ann_type,_,[_Var,Type]}) ->
+ cant_match(Pattern, Type);
+cant_match(Pattern, {paren_type,_,[Type]}) ->
+ cant_match(Pattern, Type);
+cant_match(_Pattern, {atom,_,_Atom}) ->
+ true;
+cant_match(_Pattern, {integer,_,_Int}) ->
+ true;
+cant_match(_Pattern, {op,_,_Op,_Arg}) ->
+ true;
+cant_match(_Pattern, {op,_,_Op,_Arg1,_Arg2}) ->
+ true;
+cant_match(Pattern, {type,_,mfa,[]}) ->
+ cant_match(Pattern, {type,0,tuple,[{type,0,atom,[]},{type,0,atom,[]},
+ {type,0,arity,[]}]});
+cant_match(Pattern, {type,_,union,Choices}) ->
+ lists:all(fun(C) -> cant_match(Pattern,C) end, Choices);
+cant_match(_Pattern, {type,_,tuple,any}) ->
+ false;
+cant_match(Pattern, {type,_,tuple,Fields}) ->
+ tuple_size(Pattern) =/= length(Fields) orelse
+ try match(tuple_to_list(Pattern), Fields, none, true) of
+ _ -> false
+ catch
+ throw:no_match -> true
+ end;
+cant_match(_Pattern, {type,_,Name,_Args}) ->
+ ordsets:is_element(Name, ?NON_TUPLE_TYPES);
+cant_match(_Pattern, _Type) ->
+ false.
+
+-spec cant_have_head(abs_type()) -> boolean().
+cant_have_head({ann_type,_,[_Var,Type]}) ->
+ cant_have_head(Type);
+cant_have_head({paren_type,_,[Type]}) ->
+ cant_have_head(Type);
+cant_have_head({atom,_,_Atom}) ->
+ true;
+cant_have_head({integer,_,_Int}) ->
+ true;
+cant_have_head({op,_,_Op,_Arg}) ->
+ true;
+cant_have_head({op,_,_Op,_Arg1,_Arg2}) ->
+ true;
+cant_have_head({type,_,union,Choices}) ->
+ lists:all(fun cant_have_head/1, Choices);
+cant_have_head({type,_,Name,_Args}) ->
+ ordsets:is_element(Name, ?NO_HEAD_TYPES);
+cant_have_head(_Type) ->
+ false.
+
+%% Only covers atoms, integers and tuples, i.e. those that can be specified
+%% through singleton types.
+-spec term_to_singleton_type(atom() | integer()
+ | loose_tuple(atom() | integer())) -> abs_type().
+term_to_singleton_type(Atom) when is_atom(Atom) ->
+ {atom,0,Atom};
+term_to_singleton_type(Int) when is_integer(Int), Int >= 0 ->
+ {integer,0,Int};
+term_to_singleton_type(Int) when is_integer(Int), Int < 0 ->
+ {op,0,'-',{integer,0,-Int}};
+term_to_singleton_type(Tuple) when is_tuple(Tuple) ->
+ Fields = tuple_to_list(Tuple),
+ {type,0,tuple,[term_to_singleton_type(F) || F <- Fields]}.
+
+
+%%------------------------------------------------------------------------------
+%% Instance testing functions
+%%------------------------------------------------------------------------------
+
+%% CAUTION: this must be sorted
+-define(EQUIV_TYPES,
+ [{arity, {type,0,range,[{integer,0,0},{integer,0,255}]}},
+ {bool, {type,0,boolean,[]}},
+ {byte, {type,0,range,[{integer,0,0},{integer,0,255}]}},
+ {char, {type,0,range,[{integer,0,0},{integer,0,16#10ffff}]}},
+ {function, {type,0,'fun',[]}},
+ {identifier, {type,0,union,[{type,0,pid,[]},{type,0,port,[]},
+ {type,0,reference,[]}]}},
+ {iodata, {type,0,union,[{type,0,binary,[]},{type,0,iolist,[]}]}},
+ {iolist, {type,0,maybe_improper_list,
+ [{type,0,union,[{type,0,byte,[]},{type,0,binary,[]},
+ {type,0,iolist,[]}]},
+ {type,0,binary,[]}]}},
+ {list, {type,0,list,[{type,0,any,[]}]}},
+ {maybe_improper_list, {type,0,maybe_improper_list,[{type,0,any,[]},
+ {type,0,any,[]}]}},
+ {mfa, {type,0,tuple,[{type,0,atom,[]},{type,0,atom,[]},
+ {type,0,arity,[]}]}},
+ {node, {type,0,atom,[]}},
+ {nonempty_list, {type,0,nonempty_list,[{type,0,any,[]}]}},
+ {nonempty_maybe_improper_list, {type,0,nonempty_maybe_improper_list,
+ [{type,0,any,[]},{type,0,any,[]}]}},
+ {nonempty_string, {type,0,nonempty_list,[{type,0,char,[]}]}},
+ {string, {type,0,list,[{type,0,char,[]}]}},
+ {term, {type,0,any,[]}},
+ {timeout, {type,0,union,[{atom,0,infinity},
+ {type,0,non_neg_integer,[]}]}}]).
+
+%% @private
+%% TODO: Most of these functions accept an extended form of abs_type(), namely
+%% the addition of a custom wrapper: {'from_mod',mod_name(),...}
+-spec is_instance(term(), mod_name(), abs_type()) -> boolean().
+is_instance(X, Mod, TypeForm) ->
+ is_instance(X, Mod, TypeForm, []).
+
+-spec is_instance(term(), mod_name(), abs_type(), imm_stack()) -> boolean().
+is_instance(X, _Mod, {from_mod,OrigMod,Type}, Stack) ->
+ is_instance(X, OrigMod, Type, Stack);
+is_instance(_X, _Mod, {var,_,'_'}, _Stack) ->
+ true;
+is_instance(_X, _Mod, {var,_,Name}, _Stack) ->
+ %% All unconstrained spec vars have been replaced by 'any()' and we always
+ %% replace the variables on the RHS of types before recursing into them.
+ %% Provided that '-type' declarations contain no unbound variables, we
+ %% don't expect to find any non-'_' variables while recursing.
+ throw({'$typeserver',{unbound_var_in_type_declaration,Name}});
+is_instance(X, Mod, {ann_type,_,[_Var,Type]}, Stack) ->
+ is_instance(X, Mod, Type, Stack);
+is_instance(X, Mod, {paren_type,_,[Type]}, Stack) ->
+ is_instance(X, Mod, Type, Stack);
+is_instance(X, Mod, {remote_type,_,[{atom,_,RemMod},{atom,_,Name},ArgForms]},
+ Stack) ->
+ is_custom_instance(X, Mod, RemMod, Name, ArgForms, true, Stack);
+is_instance(SameAtom, _Mod, {atom,_,SameAtom}, _Stack) ->
+ true;
+is_instance(SameInt, _Mod, {integer,_,SameInt}, _Stack) ->
+ true;
+is_instance(X, _Mod, {op,_,_Op,_Arg} = Expr, _Stack) ->
+ is_int_const(X, Expr);
+is_instance(X, _Mod, {op,_,_Op,_Arg1,_Arg2} = Expr, _Stack) ->
+ is_int_const(X, Expr);
+is_instance(_X, _Mod, {type,_,any,[]}, _Stack) ->
+ true;
+is_instance(X, _Mod, {type,_,atom,[]}, _Stack) ->
+ is_atom(X);
+is_instance(X, _Mod, {type,_,binary,[]}, _Stack) ->
+ is_binary(X);
+is_instance(X, _Mod, {type,_,binary,[BaseExpr,UnitExpr]}, _Stack) ->
+ %% <<_:X,_:_*Y>> means "bitstrings of X + k*Y bits, k >= 0"
+ case eval_int(BaseExpr) of
+ {ok,Base} when Base >= 0 ->
+ case eval_int(UnitExpr) of
+ {ok,Unit} when Unit >= 0 ->
+ case is_bitstring(X) of
+ true ->
+ BitSizeX = bit_size(X),
+ case Unit =:= 0 of
+ true ->
+ BitSizeX =:= Base;
+ false ->
+ BitSizeX >= Base
+ andalso
+ (BitSizeX - Base) rem Unit =:= 0
+ end;
+ false -> false
+ end;
+ _ ->
+ abs_expr_error(invalid_unit, UnitExpr)
+ end;
+ _ ->
+ abs_expr_error(invalid_base, BaseExpr)
+ end;
+is_instance(X, _Mod, {type,_,bitstring,[]}, _Stack) ->
+ is_bitstring(X);
+is_instance(X, _Mod, {type,_,boolean,[]}, _Stack) ->
+ is_boolean(X);
+is_instance(X, _Mod, {type,_,float,[]}, _Stack) ->
+ is_float(X);
+is_instance(X, _Mod, {type,_,'fun',[]}, _Stack) ->
+ is_function(X);
+%% TODO: how to check range type? random inputs? special case for 0-arity?
+is_instance(X, _Mod, {type,_,'fun',[{type,_,any,[]},_Range]}, _Stack) ->
+ is_function(X);
+is_instance(X, _Mod, {type,_,'fun',[{type,_,product,Domain},_Range]}, _Stack) ->
+ is_function(X, length(Domain));
+is_instance(X, _Mod, {type,_,integer,[]}, _Stack) ->
+ is_integer(X);
+is_instance(X, Mod, {type,_,list,[Type]}, _Stack) ->
+ list_test(X, Mod, Type, dummy, true, true, false);
+is_instance(X, Mod, {type,_,maybe_improper_list,[Cont,Term]}, _Stack) ->
+ list_test(X, Mod, Cont, Term, true, true, true);
+is_instance(X, _Mod, {type,_,module,[]}, _Stack) ->
+ is_atom(X) orelse
+ is_tuple(X) andalso X =/= {} andalso is_atom(element(1,X));
+is_instance([], _Mod, {type,_,nil,[]}, _Stack) ->
+ true;
+is_instance(X, _Mod, {type,_,neg_integer,[]}, _Stack) ->
+ is_integer(X) andalso X < 0;
+is_instance(X, _Mod, {type,_,non_neg_integer,[]}, _Stack) ->
+ is_integer(X) andalso X >= 0;
+is_instance(X, Mod, {type,_,nonempty_list,[Type]}, _Stack) ->
+ list_test(X, Mod, Type, dummy, false, true, false);
+is_instance(X, Mod, {type,_,nonempty_improper_list,[Cont,Term]}, _Stack) ->
+ list_test(X, Mod, Cont, Term, false, false, true);
+is_instance(X, Mod, {type,_,nonempty_maybe_improper_list,[Cont,Term]},
+ _Stack) ->
+ list_test(X, Mod, Cont, Term, false, true, true);
+is_instance(X, _Mod, {type,_,number,[]}, _Stack) ->
+ is_number(X);
+is_instance(X, _Mod, {type,_,pid,[]}, _Stack) ->
+ is_pid(X);
+is_instance(X, _Mod, {type,_,port,[]}, _Stack) ->
+ is_port(X);
+is_instance(X, _Mod, {type,_,pos_integer,[]}, _Stack) ->
+ is_integer(X) andalso X > 0;
+is_instance(_X, _Mod, {type,_,product,_Elements}, _Stack) ->
+ throw({'$typeserver',{internal,product_in_is_instance}});
+is_instance(X, _Mod, {type,_,range,[LowExpr,HighExpr]}, _Stack) ->
+ case {eval_int(LowExpr),eval_int(HighExpr)} of
+ {{ok,Low},{ok,High}} when Low =< High ->
+ X >= Low andalso X =< High;
+ _ ->
+ abs_expr_error(invalid_range, LowExpr, HighExpr)
+ end;
+is_instance(X, Mod, {type,_,record,[{atom,_,Name} = NameForm | RawSubsts]},
+ Stack) ->
+ Substs = [{N,T} || {type,_,field_type,[{atom,_,N},T]} <- RawSubsts],
+ SubstsDict = dict:from_list(Substs),
+ case get_type_repr(Mod, {record,Name,0}, false) of
+ {ok,{abs_record,OrigFields}} ->
+ Fields = [case dict:find(FieldName, SubstsDict) of
+ {ok,NewFieldType} -> NewFieldType;
+ error -> OrigFieldType
+ end
+ || {FieldName,OrigFieldType} <- OrigFields],
+ is_instance(X, Mod, {type,0,tuple,[NameForm|Fields]}, Stack);
+ {error,Reason} ->
+ throw({'$typeserver',Reason})
+ end;
+is_instance(X, _Mod, {type,_,reference,[]}, _Stack) ->
+ is_reference(X);
+is_instance(X, _Mod, {type,_,tuple,any}, _Stack) ->
+ is_tuple(X);
+is_instance(X, Mod, {type,_,tuple,Fields}, _Stack) ->
+ is_tuple(X) andalso tuple_test(tuple_to_list(X), Mod, Fields);
+is_instance(X, Mod, {type,_,union,Choices}, Stack) ->
+ IsInstance = fun(Choice) -> is_instance(X,Mod,Choice,Stack) end,
+ lists:any(IsInstance, Choices);
+is_instance(X, Mod, {type,_,Name,[]}, Stack) ->
+ case orddict:find(Name, ?EQUIV_TYPES) of
+ {ok,EquivType} ->
+ is_instance(X, Mod, EquivType, Stack);
+ error ->
+ is_maybe_hard_adt(X, Mod, Name, [], Stack)
+ end;
+is_instance(X, Mod, {type,_,Name,ArgForms}, Stack) ->
+ is_maybe_hard_adt(X, Mod, Name, ArgForms, Stack);
+is_instance(_X, _Mod, _Type, _Stack) ->
+ false.
+
+-spec is_int_const(term(), abs_expr()) -> boolean().
+is_int_const(X, Expr) ->
+ case eval_int(Expr) of
+ {ok,Int} ->
+ X =:= Int;
+ error ->
+ abs_expr_error(invalid_int_const, Expr)
+ end.
+
+%% TODO: We implicitly add the '| []' at the termination of maybe_improper_list.
+%% TODO: We ignore a '[]' termination in improper_list.
+-spec list_test(term(), mod_name(), abs_type(), 'dummy' | abs_type(), boolean(),
+ boolean(), boolean()) -> boolean().
+list_test(X, Mod, Content, Termination, CanEmpty, CanProper, CanImproper) ->
+ is_list(X) andalso
+ list_rec(X, Mod, Content, Termination, CanEmpty, CanProper, CanImproper).
+
+-spec list_rec(term(), mod_name(), abs_type(), 'dummy' | abs_type(), boolean(),
+ boolean(), boolean()) -> boolean().
+list_rec([], _Mod, _Content, _Termination, CanEmpty, CanProper, _CanImproper) ->
+ CanEmpty andalso CanProper;
+list_rec([X | Rest], Mod, Content, Termination, _CanEmpty, CanProper,
+ CanImproper) ->
+ is_instance(X, Mod, Content, []) andalso
+ list_rec(Rest, Mod, Content, Termination, true, CanProper, CanImproper);
+list_rec(X, Mod, _Content, Termination, _CanEmpty, _CanProper, CanImproper) ->
+ CanImproper andalso is_instance(X, Mod, Termination, []).
+
+-spec tuple_test([term()], mod_name(), [abs_type()]) -> boolean().
+tuple_test([], _Mod, []) ->
+ true;
+tuple_test([X | XTail], Mod, [T | TTail]) ->
+ is_instance(X, Mod, T, []) andalso tuple_test(XTail, Mod, TTail);
+tuple_test(_, _Mod, _) ->
+ false.
+
+-spec is_maybe_hard_adt(term(), mod_name(), type_name(), [abs_type()],
+ imm_stack()) -> boolean().
+is_maybe_hard_adt(X, Mod, Name, ArgForms, Stack) ->
+ case orddict:find({Name,length(ArgForms)}, ?HARD_ADTS) of
+ {ok,ADTMod} ->
+ is_custom_instance(X, Mod, ADTMod, Name, ArgForms, true, Stack);
+ error ->
+ is_custom_instance(X, Mod, Mod, Name, ArgForms, false, Stack)
+ end.
+
+-spec is_custom_instance(term(), mod_name(), mod_name(), type_name(),
+ [abs_type()], boolean(), imm_stack()) -> boolean().
+is_custom_instance(X, Mod, RemMod, Name, RawArgForms, IsRemote, Stack) ->
+ ArgForms = case Mod =/= RemMod of
+ true -> [{from_mod,Mod,A} || A <- RawArgForms];
+ false -> RawArgForms
+ end,
+ Arity = length(ArgForms),
+ FullTypeRef = {RemMod,Name,Arity},
+ case lists:member(FullTypeRef, Stack) of
+ true ->
+ throw({'$typeserver',{self_reference,FullTypeRef}});
+ false ->
+ TypeRef = {type,Name,Arity},
+ AbsType = get_abs_type(RemMod, TypeRef, ArgForms, IsRemote),
+ is_instance(X, RemMod, AbsType, [FullTypeRef|Stack])
+ end.
+
+-spec get_abs_type(mod_name(), type_ref(), [abs_type()], boolean()) ->
+ abs_type().
+get_abs_type(RemMod, TypeRef, ArgForms, IsRemote) ->
+ case get_type_repr(RemMod, TypeRef, IsRemote) of
+ {ok,TypeRepr} ->
+ {FinalAbsType,SymbInfo,VarNames} =
+ case TypeRepr of
+ {cached,_FinType,FAT,SI} -> {FAT,SI,[]};
+ {abs_type,FAT,VN,SI} -> {FAT,SI,VN}
+ end,
+ AbsType =
+ case SymbInfo of
+ not_symb -> FinalAbsType;
+ {orig_abs,OrigAbsType} -> OrigAbsType
+ end,
+ VarSubstsDict = dict:from_list(lists:zip(VarNames,ArgForms)),
+ update_vars(AbsType, VarSubstsDict, false);
+ {error,Reason} ->
+ throw({'$typeserver',Reason})
+ end.
+
+-spec abs_expr_error(atom(), abs_expr()) -> no_return().
+abs_expr_error(ImmReason, Expr) ->
+ {error,Reason} = expr_error(ImmReason, Expr),
+ throw({'$typeserver',Reason}).
+
+-spec abs_expr_error(atom(), abs_expr(), abs_expr()) -> no_return().
+abs_expr_error(ImmReason, Expr1, Expr2) ->
+ {error,Reason} = expr_error(ImmReason, Expr1, Expr2),
+ throw({'$typeserver',Reason}).
+
+
+%%------------------------------------------------------------------------------
+%% Type translation functions
+%%------------------------------------------------------------------------------
+
+-spec convert(mod_name(), abs_type(), state()) ->
+ rich_result2(fin_type(),state()).
+convert(Mod, TypeForm, State) ->
+ case convert(Mod, TypeForm, State, [], dict:new()) of
+ {ok,{simple,Type},NewState} ->
+ {ok, Type, NewState};
+ {ok,{rec,_RecFun,_RecArgs},_NewState} ->
+ {error, {internal,rec_returned_to_toplevel}};
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec convert(mod_name(), abs_type(), state(), stack(), var_dict()) ->
+ rich_result2(ret_type(),state()).
+convert(Mod, {paren_type,_,[Type]}, State, Stack, VarDict) ->
+ convert(Mod, Type, State, Stack, VarDict);
+convert(Mod, {ann_type,_,[_Var,Type]}, State, Stack, VarDict) ->
+ convert(Mod, Type, State, Stack, VarDict);
+convert(_Mod, {var,_,'_'}, State, _Stack, _VarDict) ->
+ {ok, {simple,proper_types:any()}, State};
+convert(_Mod, {var,_,VarName}, State, _Stack, VarDict) ->
+ case dict:find(VarName, VarDict) of
+ %% TODO: do we need to check if we are at toplevel of a recursive?
+ {ok,RetType} -> {ok, RetType, State};
+ error -> {error, {unbound_var,VarName}}
+ end;
+convert(Mod, {remote_type,_,[{atom,_,RemMod},{atom,_,Name},ArgForms]}, State,
+ Stack, VarDict) ->
+ case prepare_for_remote(RemMod, Name, length(ArgForms), State) of
+ {ok,NewState} ->
+ convert_custom(Mod,RemMod,Name,ArgForms,NewState,Stack,VarDict);
+ {error,_Reason} = Error ->
+ Error
+ end;
+convert(_Mod, {atom,_,Atom}, State, _Stack, _VarDict) ->
+ {ok, {simple,proper_types:exactly(Atom)}, State};
+convert(_Mod, {integer,_,_Int} = IntExpr, State, _Stack, _VarDict) ->
+ convert_integer(IntExpr, State);
+convert(_Mod, {op,_,_Op,_Arg} = OpExpr, State, _Stack, _VarDict) ->
+ convert_integer(OpExpr, State);
+convert(_Mod, {op,_,_Op,_Arg1,_Arg2} = OpExpr, State, _Stack, _VarDict) ->
+ convert_integer(OpExpr, State);
+convert(_Mod, {type,_,binary,[BaseExpr,UnitExpr]}, State, _Stack, _VarDict) ->
+ %% <<_:X,_:_*Y>> means "bitstrings of X + k*Y bits, k >= 0"
+ case eval_int(BaseExpr) of
+ {ok,0} ->
+ case eval_int(UnitExpr) of
+ {ok,0} -> {ok, {simple,proper_types:exactly(<<>>)}, State};
+ {ok,1} -> {ok, {simple,proper_types:bitstring()}, State};
+ {ok,8} -> {ok, {simple,proper_types:binary()}, State};
+ {ok,N} when N > 0 ->
+ Gen = ?LET(L, proper_types:list(proper_types:bitstring(N)),
+ concat_bitstrings(L)),
+ {ok, {simple,Gen}, State};
+ _ -> expr_error(invalid_unit, UnitExpr)
+ end;
+ {ok,Base} when Base > 0 ->
+ Head = proper_types:bitstring(Base),
+ case eval_int(UnitExpr) of
+ {ok,0} -> {ok, {simple,Head}, State};
+ {ok,1} ->
+ Tail = proper_types:bitstring(),
+ {ok, {simple,concat_binary_gens(Head, Tail)}, State};
+ {ok,8} ->
+ Tail = proper_types:binary(),
+ {ok, {simple,concat_binary_gens(Head, Tail)}, State};
+ {ok,N} when N > 0 ->
+ Tail =
+ ?LET(L, proper_types:list(proper_types:bitstring(N)),
+ concat_bitstrings(L)),
+ {ok, {simple,concat_binary_gens(Head, Tail)}, State};
+ _ -> expr_error(invalid_unit, UnitExpr)
+ end;
+ _ ->
+ expr_error(invalid_base, BaseExpr)
+ end;
+convert(_Mod, {type,_,range,[LowExpr,HighExpr]}, State, _Stack, _VarDict) ->
+ case {eval_int(LowExpr),eval_int(HighExpr)} of
+ {{ok,Low},{ok,High}} when Low =< High ->
+ {ok, {simple,proper_types:integer(Low,High)}, State};
+ _ ->
+ expr_error(invalid_range, LowExpr, HighExpr)
+ end;
+convert(_Mod, {type,_,nil,[]}, State, _Stack, _VarDict) ->
+ {ok, {simple,proper_types:exactly([])}, State};
+convert(Mod, {type,_,list,[ElemForm]}, State, Stack, VarDict) ->
+ convert_list(Mod, false, ElemForm, State, Stack, VarDict);
+convert(Mod, {type,_,nonempty_list,[ElemForm]}, State, Stack, VarDict) ->
+ convert_list(Mod, true, ElemForm, State, Stack, VarDict);
+convert(_Mod, {type,_,nonempty_list,[]}, State, _Stack, _VarDict) ->
+ {ok, {simple,proper_types:non_empty(proper_types:list())}, State};
+convert(_Mod, {type,_,nonempty_string,[]}, State, _Stack, _VarDict) ->
+ {ok, {simple,proper_types:non_empty(proper_types:string())}, State};
+convert(_Mod, {type,_,tuple,any}, State, _Stack, _VarDict) ->
+ {ok, {simple,proper_types:tuple()}, State};
+convert(Mod, {type,_,tuple,ElemForms}, State, Stack, VarDict) ->
+ convert_tuple(Mod, ElemForms, false, State, Stack, VarDict);
+convert(Mod, {type,_,'$fixed_list',ElemForms}, State, Stack, VarDict) ->
+ convert_tuple(Mod, ElemForms, true, State, Stack, VarDict);
+convert(Mod, {type,_,record,[{atom,_,Name}|FieldForms]}, State, Stack,
+ VarDict) ->
+ convert_record(Mod, Name, FieldForms, State, Stack, VarDict);
+convert(Mod, {type,_,union,ChoiceForms}, State, Stack, VarDict) ->
+ convert_union(Mod, ChoiceForms, State, Stack, VarDict);
+convert(Mod, {type,_,'fun',[{type,_,product,Domain},Range]}, State, Stack,
+ VarDict) ->
+ convert_fun(Mod, length(Domain), Range, State, Stack, VarDict);
+%% TODO: These types should be replaced with accurate types.
+%% TODO: Add support for nonempty_improper_list/2.
+convert(Mod, {type,_,maybe_improper_list,[]}, State, Stack, VarDict) ->
+ convert(Mod, {type,0,list,[]}, State, Stack, VarDict);
+convert(Mod, {type,_,maybe_improper_list,[Cont,_Ter]}, State, Stack, VarDict) ->
+ convert(Mod, {type,0,list,[Cont]}, State, Stack, VarDict);
+convert(Mod, {type,_,nonempty_maybe_improper_list,[]}, State, Stack, VarDict) ->
+ convert(Mod, {type,0,nonempty_list,[]}, State, Stack, VarDict);
+convert(Mod, {type,_,nonempty_maybe_improper_list,[Cont,_Term]}, State, Stack,
+ VarDict) ->
+ convert(Mod, {type,0,nonempty_list,[Cont]}, State, Stack, VarDict);
+convert(Mod, {type,_,iodata,[]}, State, Stack, VarDict) ->
+ RealType = {type,0,union,[{type,0,binary,[]},{type,0,iolist,[]}]},
+ convert(Mod, RealType, State, Stack, VarDict);
+convert(Mod, {type,_,Name,[]}, State, Stack, VarDict) ->
+ case ordsets:is_element(Name, ?STD_TYPES_0) of
+ true ->
+ {ok, {simple,proper_types:Name()}, State};
+ false ->
+ convert_maybe_hard_adt(Mod, Name, [], State, Stack, VarDict)
+ end;
+convert(Mod, {type,_,Name,ArgForms}, State, Stack, VarDict) ->
+ convert_maybe_hard_adt(Mod, Name, ArgForms, State, Stack, VarDict);
+convert(_Mod, TypeForm, _State, _Stack, _VarDict) ->
+ {error, {unsupported_type,TypeForm}}.
+
+-spec concat_bitstrings([bitstring()]) -> bitstring().
+concat_bitstrings(BitStrings) ->
+ concat_bitstrings_tr(BitStrings, <<>>).
+
+-spec concat_bitstrings_tr([bitstring()], bitstring()) -> bitstring().
+concat_bitstrings_tr([], Acc) ->
+ Acc;
+concat_bitstrings_tr([BitString | Rest], Acc) ->
+ concat_bitstrings_tr(Rest, <<Acc/bits,BitString/bits>>).
+
+-spec concat_binary_gens(fin_type(), fin_type()) -> fin_type().
+concat_binary_gens(HeadType, TailType) ->
+ ?LET({H,T}, {HeadType,TailType}, <<H/bits,T/bits>>).
+
+-spec convert_fun(mod_name(), arity(), abs_type(), state(), stack(),
+ var_dict()) -> rich_result2(ret_type(),state()).
+convert_fun(Mod, Arity, Range, State, Stack, VarDict) ->
+ case convert(Mod, Range, State, ['fun' | Stack], VarDict) of
+ {ok,{simple,RangeType},NewState} ->
+ {ok, {simple,proper_types:function(Arity,RangeType)}, NewState};
+ {ok,{rec,RecFun,RecArgs},NewState} ->
+ case at_toplevel(RecArgs, Stack) of
+ true -> base_case_error(Stack);
+ false -> convert_rec_fun(Arity, RecFun, RecArgs, NewState)
+ end;
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec convert_rec_fun(arity(), rec_fun(), rec_args(), state()) ->
+ {'ok',ret_type(),state()}.
+convert_rec_fun(Arity, RecFun, RecArgs, State) ->
+ %% We bind the generated value by size.
+ NewRecFun =
+ fun(GenFuns,Size) ->
+ proper_types:function(Arity, RecFun(GenFuns,Size))
+ end,
+ NewRecArgs = clean_rec_args(RecArgs),
+ {ok, {rec,NewRecFun,NewRecArgs}, State}.
+
+-spec convert_list(mod_name(), boolean(), abs_type(), state(), stack(),
+ var_dict()) -> rich_result2(ret_type(),state()).
+convert_list(Mod, NonEmpty, ElemForm, State, Stack, VarDict) ->
+ case convert(Mod, ElemForm, State, [list | Stack], VarDict) of
+ {ok,{simple,ElemType},NewState} ->
+ InnerType = proper_types:list(ElemType),
+ FinType = case NonEmpty of
+ true -> proper_types:non_empty(InnerType);
+ false -> InnerType
+ end,
+ {ok, {simple,FinType}, NewState};
+ {ok,{rec,RecFun,RecArgs},NewState} ->
+ case {at_toplevel(RecArgs,Stack), NonEmpty} of
+ {true,true} ->
+ base_case_error(Stack);
+ {true,false} ->
+ NewRecFun =
+ fun(GenFuns,Size) ->
+ ElemGen = fun(S) -> ?LAZY(RecFun(GenFuns,S)) end,
+ proper_types:distlist(Size, ElemGen, false)
+ end,
+ NewRecArgs = clean_rec_args(RecArgs),
+ {ok, {rec,NewRecFun,NewRecArgs}, NewState};
+ {false,_} ->
+ {NewRecFun,NewRecArgs} =
+ convert_rec_list(RecFun, RecArgs, NonEmpty),
+ {ok, {rec,NewRecFun,NewRecArgs}, NewState}
+ end;
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec convert_rec_list(rec_fun(), rec_args(), boolean()) ->
+ {rec_fun(),rec_args()}.
+convert_rec_list(RecFun, [{true,FullTypeRef}] = RecArgs, NonEmpty) ->
+ {NewRecFun,_NormalRecArgs} =
+ convert_normal_rec_list(RecFun, RecArgs, NonEmpty),
+ AltRecFun =
+ fun([InstListGen],Size) ->
+ InstTypesList =
+ proper_types:get_prop(internal_types, InstListGen(Size)),
+ proper_types:fixed_list([RecFun([fun(_Size) -> I end],0)
+ || I <- InstTypesList])
+ end,
+ NewRecArgs = [{{list,NonEmpty,AltRecFun},FullTypeRef}],
+ {NewRecFun, NewRecArgs};
+convert_rec_list(RecFun, RecArgs, NonEmpty) ->
+ convert_normal_rec_list(RecFun, RecArgs, NonEmpty).
+
+-spec convert_normal_rec_list(rec_fun(), rec_args(), boolean()) ->
+ {rec_fun(),rec_args()}.
+convert_normal_rec_list(RecFun, RecArgs, NonEmpty) ->
+ NewRecFun = fun(GenFuns,Size) ->
+ ElemGen = fun(S) -> RecFun(GenFuns, S) end,
+ proper_types:distlist(Size, ElemGen, NonEmpty)
+ end,
+ NewRecArgs = clean_rec_args(RecArgs),
+ {NewRecFun, NewRecArgs}.
+
+-spec convert_tuple(mod_name(), [abs_type()], boolean(), state(), stack(),
+ var_dict()) -> rich_result2(ret_type(),state()).
+convert_tuple(Mod, ElemForms, ToList, State, Stack, VarDict) ->
+ case process_list(Mod, ElemForms, State, [tuple | Stack], VarDict) of
+ {ok,RetTypes,NewState} ->
+ case combine_ret_types(RetTypes, {tuple,ToList}) of
+ {simple,_FinType} = RetType ->
+ {ok, RetType, NewState};
+ {rec,_RecFun,RecArgs} = RetType ->
+ case at_toplevel(RecArgs, Stack) of
+ true -> base_case_error(Stack);
+ false -> {ok, RetType, NewState}
+ end
+ end;
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec convert_union(mod_name(), [abs_type()], state(), stack(), var_dict()) ->
+ rich_result2(ret_type(),state()).
+convert_union(Mod, ChoiceForms, State, Stack, VarDict) ->
+ case process_list(Mod, ChoiceForms, State, [union | Stack], VarDict) of
+ {ok,RawChoices,NewState} ->
+ ProcessChoice = fun(T,A) -> process_choice(T,A,Stack) end,
+ {RevSelfRecs,RevNonSelfRecs,RevNonRecs} =
+ lists:foldl(ProcessChoice, {[],[],[]}, RawChoices),
+ case {lists:reverse(RevSelfRecs),lists:reverse(RevNonSelfRecs),
+ lists:reverse(RevNonRecs)} of
+ {_SelfRecs,[],[]} ->
+ base_case_error(Stack);
+ {[],NonSelfRecs,NonRecs} ->
+ {ok, combine_ret_types(NonRecs ++ NonSelfRecs, union),
+ NewState};
+ {SelfRecs,NonSelfRecs,NonRecs} ->
+ {BCaseRecFun,BCaseRecArgs} =
+ case combine_ret_types(NonRecs ++ NonSelfRecs, union) of
+ {simple,BCaseType} ->
+ {fun([],_Size) -> BCaseType end,[]};
+ {rec,BCRecFun,BCRecArgs} ->
+ {BCRecFun,BCRecArgs}
+ end,
+ NumBCaseGens = length(BCaseRecArgs),
+ [ParentRef | _Upper] = Stack,
+ FallbackRecFun = fun([SelfGen],_Size) -> SelfGen(0) end,
+ FallbackRecArgs = [{false,ParentRef}],
+ FallbackRetType = {rec,FallbackRecFun,FallbackRecArgs},
+ {rec,RCaseRecFun,RCaseRecArgs} =
+ combine_ret_types([FallbackRetType] ++ SelfRecs
+ ++ NonSelfRecs, wunion),
+ NewRecFun =
+ fun(AllGens,Size) ->
+ {BCaseGens,RCaseGens} =
+ lists:split(NumBCaseGens, AllGens),
+ case Size of
+ 0 -> BCaseRecFun(BCaseGens,0);
+ _ -> RCaseRecFun(RCaseGens,Size)
+ end
+ end,
+ NewRecArgs = BCaseRecArgs ++ RCaseRecArgs,
+ {ok, {rec,NewRecFun,NewRecArgs}, NewState}
+ end;
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec process_choice(ret_type(), {[ret_type()],[ret_type()],[ret_type()]},
+ stack()) -> {[ret_type()],[ret_type()],[ret_type()]}.
+process_choice({simple,_} = RetType, {SelfRecs,NonSelfRecs,NonRecs}, _Stack) ->
+ {SelfRecs, NonSelfRecs, [RetType | NonRecs]};
+process_choice({rec,RecFun,RecArgs}, {SelfRecs,NonSelfRecs,NonRecs}, Stack) ->
+ case at_toplevel(RecArgs, Stack) of
+ true ->
+ case partition_by_toplevel(RecArgs, Stack, true) of
+ {[],[],_,_} ->
+ NewRecArgs = clean_rec_args(RecArgs),
+ {[{rec,RecFun,NewRecArgs} | SelfRecs], NonSelfRecs,
+ NonRecs};
+ {SelfRecArgs,SelfPos,OtherRecArgs,_OtherPos} ->
+ NumInstances = length(SelfRecArgs),
+ IsListInst = fun({true,_FTRef}) -> false
+ ; ({{list,_NE,_AltRecFun},_FTRef}) -> true
+ end,
+ NewRecFun =
+ case proper_arith:filter(IsListInst,SelfRecArgs) of
+ {[],[]} ->
+ no_list_inst_rec_fun(RecFun,NumInstances,
+ SelfPos);
+ {[{{list,NonEmpty,AltRecFun},_}],[ListInstPos]} ->
+ list_inst_rec_fun(AltRecFun,NumInstances,
+ SelfPos,NonEmpty,ListInstPos)
+ end,
+ [{_B,SelfRef} | _] = SelfRecArgs,
+ NewRecArgs =
+ [{false,SelfRef} | clean_rec_args(OtherRecArgs)],
+ {[{rec,NewRecFun,NewRecArgs} | SelfRecs], NonSelfRecs,
+ NonRecs}
+ end;
+ false ->
+ NewRecArgs = clean_rec_args(RecArgs),
+ {SelfRecs, [{rec,RecFun,NewRecArgs} | NonSelfRecs], NonRecs}
+ end.
+
+-spec no_list_inst_rec_fun(rec_fun(), pos_integer(), [position()]) -> rec_fun().
+no_list_inst_rec_fun(RecFun, NumInstances, SelfPos) ->
+ fun([SelfGen|OtherGens], Size) ->
+ ?LETSHRINK(
+ Instances,
+ %% Size distribution will be a little off if both normal and
+ %% instance-accepting generators are present.
+ lists:duplicate(NumInstances, SelfGen(Size div NumInstances)),
+ begin
+ InstGens = [fun(_Size) -> proper_types:exactly(I) end
+ || I <- Instances],
+ AllGens = proper_arith:insert(InstGens, SelfPos, OtherGens),
+ RecFun(AllGens, Size)
+ end)
+ end.
+
+-spec list_inst_rec_fun(rec_fun(), pos_integer(), [position()], boolean(),
+ position()) -> rec_fun().
+list_inst_rec_fun(AltRecFun, NumInstances, SelfPos, NonEmpty, ListInstPos) ->
+ fun([SelfGen|OtherGens], Size) ->
+ ?LETSHRINK(
+ AllInsts,
+ lists:duplicate(NumInstances - 1, SelfGen(Size div NumInstances))
+ ++ proper_types:distlist(Size div NumInstances, SelfGen, NonEmpty),
+ begin
+ {Instances,InstList} = lists:split(NumInstances - 1, AllInsts),
+ InstGens = [fun(_Size) -> proper_types:exactly(I) end
+ || I <- Instances],
+ InstTypesList = [proper_types:exactly(I) || I <- InstList],
+ InstListGen =
+ fun(_Size) -> proper_types:fixed_list(InstTypesList) end,
+ AllInstGens = proper_arith:list_insert(ListInstPos, InstListGen,
+ InstGens),
+ AllGens = proper_arith:insert(AllInstGens, SelfPos, OtherGens),
+ AltRecFun(AllGens, Size)
+ end)
+ end.
+
+-spec convert_maybe_hard_adt(mod_name(), type_name(), [abs_type()], state(),
+ stack(), var_dict()) ->
+ rich_result2(ret_type(),state()).
+convert_maybe_hard_adt(Mod, Name, ArgForms, State, Stack, VarDict) ->
+ Arity = length(ArgForms),
+ case orddict:find({Name,Arity}, ?HARD_ADTS) of
+ {ok,Mod} ->
+ convert_custom(Mod, Mod, Name, ArgForms, State, Stack, VarDict);
+ {ok,ADTMod} ->
+ ADT = {remote_type,0,[{atom,0,ADTMod},{atom,0,Name},ArgForms]},
+ convert(Mod, ADT, State, Stack, VarDict);
+ error ->
+ convert_custom(Mod, Mod, Name, ArgForms, State, Stack, VarDict)
+ end.
+
+-spec convert_custom(mod_name(), mod_name(), type_name(), [abs_type()], state(),
+ stack(), var_dict()) -> rich_result2(ret_type(),state()).
+convert_custom(Mod, RemMod, Name, ArgForms, State, Stack, VarDict) ->
+ case process_list(Mod, ArgForms, State, Stack, VarDict) of
+ {ok,Args,NewState} ->
+ Arity = length(Args),
+ TypeRef = {type,Name,Arity},
+ FullTypeRef = {RemMod,type,Name,Args},
+ convert_type(TypeRef, FullTypeRef, NewState, Stack);
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec convert_record(mod_name(), type_name(), [abs_type()], state(), stack(),
+ var_dict()) -> rich_result2(ret_type(),state()).
+convert_record(Mod, Name, RawSubsts, State, Stack, VarDict) ->
+ Substs = [{N,T} || {type,_,field_type,[{atom,_,N},T]} <- RawSubsts],
+ {SubstFields,SubstTypeForms} = lists:unzip(Substs),
+ case process_list(Mod, SubstTypeForms, State, Stack, VarDict) of
+ {ok,SubstTypes,NewState} ->
+ SubstsDict = dict:from_list(lists:zip(SubstFields, SubstTypes)),
+ TypeRef = {record,Name,0},
+ FullTypeRef = {Mod,record,Name,SubstsDict},
+ convert_type(TypeRef, FullTypeRef, NewState, Stack);
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec convert_type(type_ref(), full_type_ref(), state(), stack()) ->
+ rich_result2(ret_type(),state()).
+convert_type(TypeRef, {Mod,_Kind,_Name,_Spec} = FullTypeRef, State, Stack) ->
+ case stack_position(FullTypeRef, Stack) of
+ none ->
+ case get_type_repr(Mod, TypeRef, false, State) of
+ {ok,TypeRepr,NewState} ->
+ convert_new_type(TypeRef, FullTypeRef, TypeRepr, NewState,
+ Stack);
+ {error,_Reason} = Error ->
+ Error
+ end;
+ 1 ->
+ base_case_error(Stack);
+ _Pos ->
+ {ok, {rec,fun([Gen],Size) -> Gen(Size) end,[{true,FullTypeRef}]},
+ State}
+ end.
+
+-spec convert_new_type(type_ref(), full_type_ref(), type_repr(), state(),
+ stack()) -> rich_result2(ret_type(),state()).
+convert_new_type(_TypeRef, {_Mod,type,_Name,[]},
+ {cached,FinType,_TypeForm,_SymbInfo}, State, _Stack) ->
+ {ok, {simple,FinType}, State};
+convert_new_type(TypeRef, {Mod,type,_Name,Args} = FullTypeRef,
+ {abs_type,TypeForm,Vars,SymbInfo}, State, Stack) ->
+ VarDict = dict:from_list(lists:zip(Vars, Args)),
+ case convert(Mod, TypeForm, State, [FullTypeRef | Stack], VarDict) of
+ {ok, {simple,ImmFinType}, NewState} ->
+ FinType = case SymbInfo of
+ not_symb ->
+ ImmFinType;
+ {orig_abs,_OrigAbsType} ->
+ proper_symb:internal_well_defined(ImmFinType)
+ end,
+ FinalState = case Vars of
+ [] -> cache_type(Mod, TypeRef, FinType, TypeForm,
+ SymbInfo, NewState);
+ _ -> NewState
+ end,
+ {ok, {simple,FinType}, FinalState};
+ {ok, {rec,RecFun,RecArgs}, NewState} ->
+ convert_maybe_rec(FullTypeRef, SymbInfo, RecFun, RecArgs, NewState,
+ Stack);
+ {error,_Reason} = Error ->
+ Error
+ end;
+convert_new_type(_TypeRef, {Mod,record,Name,SubstsDict} = FullTypeRef,
+ {abs_record,OrigFields}, State, Stack) ->
+ Fields = [case dict:find(FieldName, SubstsDict) of
+ {ok,NewFieldType} -> NewFieldType;
+ error -> OrigFieldType
+ end
+ || {FieldName,OrigFieldType} <- OrigFields],
+ case convert_tuple(Mod, [{atom,0,Name} | Fields], false, State,
+ [FullTypeRef | Stack], dict:new()) of
+ {ok, {simple,_FinType}, _NewState} = Result ->
+ Result;
+ {ok, {rec,RecFun,RecArgs}, NewState} ->
+ convert_maybe_rec(FullTypeRef, not_symb, RecFun, RecArgs, NewState,
+ Stack);
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec cache_type(mod_name(), type_ref(), fin_type(), abs_type(), symb_info(),
+ state()) -> state().
+cache_type(Mod, TypeRef, FinType, TypeForm, SymbInfo,
+ #state{types = Types} = State) ->
+ TypeRepr = {cached,FinType,TypeForm,SymbInfo},
+ ModTypes = dict:fetch(Mod, Types),
+ NewModTypes = dict:store(TypeRef, TypeRepr, ModTypes),
+ NewTypes = dict:store(Mod, NewModTypes, Types),
+ State#state{types = NewTypes}.
+
+-spec convert_maybe_rec(full_type_ref(), symb_info(), rec_fun(), rec_args(),
+ state(), stack()) -> rich_result2(ret_type(),state()).
+convert_maybe_rec(FullTypeRef, SymbInfo, RecFun, RecArgs, State, Stack) ->
+ case at_toplevel(RecArgs, Stack) of
+ true -> base_case_error(Stack);
+ false -> safe_convert_maybe_rec(FullTypeRef, SymbInfo, RecFun, RecArgs,
+ State)
+ end.
+
+-spec safe_convert_maybe_rec(full_type_ref(),symb_info(),rec_fun(),rec_args(),
+ state()) -> rich_result2(ret_type(),state()).
+safe_convert_maybe_rec(FullTypeRef, SymbInfo, RecFun, RecArgs, State) ->
+ case partition_rec_args(FullTypeRef, RecArgs, false) of
+ {[],[],_,_} ->
+ {ok, {rec,RecFun,RecArgs}, State};
+ {MyRecArgs,MyPos,OtherRecArgs,_OtherPos} ->
+ case lists:all(fun({B,_T}) -> B =:= false end, MyRecArgs) of
+ true -> convert_rec_type(SymbInfo, RecFun, MyPos, OtherRecArgs,
+ State);
+ false -> {error, {internal,true_rec_arg_reached_type}}
+ end
+ end.
+
+-spec convert_rec_type(symb_info(), rec_fun(), [position()], rec_args(),
+ state()) -> {ok, ret_type(), state()}.
+convert_rec_type(SymbInfo, RecFun, MyPos, [], State) ->
+ NumRecArgs = length(MyPos),
+ M = fun(GenFun) ->
+ fun(Size) ->
+ GenFuns = lists:duplicate(NumRecArgs, GenFun),
+ RecFun(GenFuns, erlang:max(0,Size - 1))
+ end
+ end,
+ SizedGen = y(M),
+ ImmFinType = ?SIZED(Size,SizedGen(Size + 1)),
+ FinType = case SymbInfo of
+ not_symb ->
+ ImmFinType;
+ {orig_abs,_OrigAbsType} ->
+ proper_symb:internal_well_defined(ImmFinType)
+ end,
+ {ok, {simple,FinType}, State};
+convert_rec_type(_SymbInfo, RecFun, MyPos, OtherRecArgs, State) ->
+ NumRecArgs = length(MyPos),
+ NewRecFun =
+ fun(OtherGens,TopSize) ->
+ M = fun(GenFun) ->
+ fun(Size) ->
+ GenFuns = lists:duplicate(NumRecArgs, GenFun),
+ AllGens =
+ proper_arith:insert(GenFuns, MyPos, OtherGens),
+ RecFun(AllGens, erlang:max(0,Size - 1))
+ end
+ end,
+ (y(M))(TopSize)
+ end,
+ NewRecArgs = clean_rec_args(OtherRecArgs),
+ {ok, {rec,NewRecFun,NewRecArgs}, State}.
+
+%% Y Combinator: Read more at http://bc.tech.coop/blog/070611.html.
+-spec y(fun((fun((T) -> S)) -> fun((T) -> S))) -> fun((T) -> S).
+y(M) ->
+ G = fun(F) ->
+ M(fun(A) -> (F(F))(A) end)
+ end,
+ G(G).
+
+-spec process_list(mod_name(), [abs_type() | ret_type()], state(), stack(),
+ var_dict()) -> rich_result2([ret_type()],state()).
+process_list(Mod, RawTypes, State, Stack, VarDict) ->
+ Process = fun({simple,_FinType} = Type, {ok,Types,State1}) ->
+ {ok, [Type|Types], State1};
+ ({rec,_RecFun,_RecArgs} = Type, {ok,Types,State1}) ->
+ {ok, [Type|Types], State1};
+ (TypeForm, {ok,Types,State1}) ->
+ case convert(Mod, TypeForm, State1, Stack, VarDict) of
+ {ok,Type,State2} -> {ok,[Type|Types],State2};
+ {error,_} = Err -> Err
+ end;
+ (_RawType, {error,_} = Err) ->
+ Err
+ end,
+ case lists:foldl(Process, {ok,[],State}, RawTypes) of
+ {ok,RevTypes,NewState} ->
+ {ok, lists:reverse(RevTypes), NewState};
+ {error,_Reason} = Error ->
+ Error
+ end.
+
+-spec convert_integer(abs_expr(), state()) -> rich_result2(ret_type(),state()).
+convert_integer(Expr, State) ->
+ case eval_int(Expr) of
+ {ok,Int} -> {ok, {simple,proper_types:exactly(Int)}, State};
+ error -> expr_error(invalid_int_const, Expr)
+ end.
+
+-spec eval_int(abs_expr()) -> tagged_result(integer()).
+eval_int(Expr) ->
+ NoBindings = erl_eval:new_bindings(),
+ try erl_eval:expr(Expr, NoBindings) of
+ {value,Value,_NewBindings} when is_integer(Value) ->
+ {ok, Value};
+ _ ->
+ error
+ catch
+ error:_ ->
+ error
+ end.
+
+-spec expr_error(atom(), abs_expr()) -> {'error',term()}.
+expr_error(Reason, Expr) ->
+ {error, {Reason,lists:flatten(erl_pp:expr(Expr))}}.
+
+-spec expr_error(atom(), abs_expr(), abs_expr()) -> {'error',term()}.
+expr_error(Reason, Expr1, Expr2) ->
+ Str1 = lists:flatten(erl_pp:expr(Expr1)),
+ Str2 = lists:flatten(erl_pp:expr(Expr2)),
+ {error, {Reason,Str1,Str2}}.
+
+-spec base_case_error(stack()) -> {'error',term()}.
+%% TODO: This might confuse, since it doesn't record the arguments to parametric
+%% types or the type subsitutions of a record.
+base_case_error([{Mod,type,Name,Args} | _Upper]) ->
+ Arity = length(Args),
+ {error, {no_base_case,{Mod,type,Name,Arity}}};
+base_case_error([{Mod,record,Name,_SubstsDict} | _Upper]) ->
+ {error, {no_base_case,{Mod,record,Name}}}.
+
+
+%%------------------------------------------------------------------------------
+%% Helper datatypes handling functions
+%%------------------------------------------------------------------------------
+
+-spec stack_position(full_type_ref(), stack()) -> 'none' | pos_integer().
+stack_position(FullTypeRef, Stack) ->
+ SameType = fun(A) -> same_full_type_ref(A,FullTypeRef) end,
+ case proper_arith:find_first(SameType, Stack) of
+ {Pos,_} -> Pos;
+ none -> none
+ end.
+
+-spec partition_by_toplevel(rec_args(), stack(), boolean()) ->
+ {rec_args(),[position()],rec_args(),[position()]}.
+partition_by_toplevel(RecArgs, [], _OnlyInstanceAccepting) ->
+ {[],[],RecArgs,lists:seq(1,length(RecArgs))};
+partition_by_toplevel(RecArgs, [_Parent | _Upper], _OnlyInstanceAccepting)
+ when is_atom(_Parent) ->
+ {[],[],RecArgs,lists:seq(1,length(RecArgs))};
+partition_by_toplevel(RecArgs, [Parent | _Upper], OnlyInstanceAccepting) ->
+ partition_rec_args(Parent, RecArgs, OnlyInstanceAccepting).
+
+-spec at_toplevel(rec_args(), stack()) -> boolean().
+at_toplevel(RecArgs, Stack) ->
+ case partition_by_toplevel(RecArgs, Stack, false) of
+ {[],[],_,_} -> false;
+ _ -> true
+ end.
+
+-spec partition_rec_args(full_type_ref(), rec_args(), boolean()) ->
+ {rec_args(),[position()],rec_args(),[position()]}.
+partition_rec_args(FullTypeRef, RecArgs, OnlyInstanceAccepting) ->
+ SameType =
+ case OnlyInstanceAccepting of
+ true -> fun({false,_T}) -> false
+ ; ({_B,T}) -> same_full_type_ref(T,FullTypeRef) end;
+ false -> fun({_B,T}) -> same_full_type_ref(T,FullTypeRef) end
+ end,
+ proper_arith:partition(SameType, RecArgs).
+
+%% Tuples can be of 0 arity, unions of 1 and wunions at least of 2.
+-spec combine_ret_types([ret_type()], {'tuple',boolean()} | 'union'
+ | 'wunion') -> ret_type().
+combine_ret_types(RetTypes, EnclosingType) ->
+ case lists:all(fun is_simple_ret_type/1, RetTypes) of
+ true ->
+ %% This should never happen for wunion.
+ Combine = case EnclosingType of
+ {tuple,false} -> fun proper_types:tuple/1;
+ {tuple,true} -> fun proper_types:fixed_list/1;
+ union -> fun proper_types:union/1
+ end,
+ FinTypes = [T || {simple,T} <- RetTypes],
+ {simple, Combine(FinTypes)};
+ false ->
+ NumTypes = length(RetTypes),
+ {RevRecFuns,RevRecArgsList,NumRecs} =
+ lists:foldl(fun add_ret_type/2, {[],[],0}, RetTypes),
+ RecFuns = lists:reverse(RevRecFuns),
+ RecArgsList = lists:reverse(RevRecArgsList),
+ RecArgLens = [length(RecArgs) || RecArgs <- RecArgsList],
+ RecFunInfo = {NumTypes,NumRecs,RecArgLens,RecFuns},
+ FlatRecArgs = lists:flatten(RecArgsList),
+ {NewRecFun,NewRecArgs} =
+ case EnclosingType of
+ {tuple,ToList} ->
+ {tuple_rec_fun(RecFunInfo,ToList),
+ soft_clean_rec_args(FlatRecArgs,RecFunInfo,ToList)};
+ union ->
+ {union_rec_fun(RecFunInfo),clean_rec_args(FlatRecArgs)};
+ wunion ->
+ {wunion_rec_fun(RecFunInfo),
+ clean_rec_args(FlatRecArgs)}
+ end,
+ {rec, NewRecFun, NewRecArgs}
+ end.
+
+-spec tuple_rec_fun(rec_fun_info(), boolean()) -> rec_fun().
+tuple_rec_fun({_NumTypes,NumRecs,RecArgLens,RecFuns}, ToList) ->
+ Combine = case ToList of
+ true -> fun proper_types:fixed_list/1;
+ false -> fun proper_types:tuple/1
+ end,
+ fun(AllGFs,TopSize) ->
+ Size = TopSize div NumRecs,
+ GFsList = proper_arith:unflatten(AllGFs, RecArgLens),
+ ArgsList = [[GenFuns,Size] || GenFuns <- GFsList],
+ ZipFun = fun erlang:apply/2,
+ Combine(lists:zipwith(ZipFun, RecFuns, ArgsList))
+ end.
+
+-spec union_rec_fun(rec_fun_info()) -> rec_fun().
+union_rec_fun({_NumTypes,_NumRecs,RecArgLens,RecFuns}) ->
+ fun(AllGFs,Size) ->
+ GFsList = proper_arith:unflatten(AllGFs, RecArgLens),
+ ArgsList = [[GenFuns,Size] || GenFuns <- GFsList],
+ ZipFun = fun(F,A) -> ?LAZY(apply(F,A)) end,
+ proper_types:union(lists:zipwith(ZipFun, RecFuns, ArgsList))
+ end.
+
+-spec wunion_rec_fun(rec_fun_info()) -> rec_fun().
+wunion_rec_fun({NumTypes,_NumRecs,RecArgLens,RecFuns}) ->
+ fun(AllGFs,Size) ->
+ GFsList = proper_arith:unflatten(AllGFs, RecArgLens),
+ ArgsList = [[GenFuns,Size] || GenFuns <- GFsList],
+ ZipFun = fun(W,F,A) -> {W,?LAZY(apply(F,A))} end,
+ RecWeight = erlang:max(1, Size div (NumTypes - 1)),
+ Weights = [1 | lists:duplicate(NumTypes - 1, RecWeight)],
+ WeightedChoices = lists:zipwith3(ZipFun, Weights, RecFuns, ArgsList),
+ proper_types:wunion(WeightedChoices)
+ end.
+
+-spec add_ret_type(ret_type(), {[rec_fun()],[rec_args()],non_neg_integer()}) ->
+ {[rec_fun()],[rec_args()],non_neg_integer()}.
+add_ret_type({simple,FinType}, {RecFuns,RecArgsList,NumRecs}) ->
+ {[fun([],_) -> FinType end | RecFuns], [[] | RecArgsList], NumRecs};
+add_ret_type({rec,RecFun,RecArgs}, {RecFuns,RecArgsList,NumRecs}) ->
+ {[RecFun | RecFuns], [RecArgs | RecArgsList], NumRecs + 1}.
+
+-spec is_simple_ret_type(ret_type()) -> boolean().
+is_simple_ret_type({simple,_FinType}) ->
+ true;
+is_simple_ret_type({rec,_RecFun,_RecArgs}) ->
+ false.
+
+-spec clean_rec_args(rec_args()) -> rec_args().
+clean_rec_args(RecArgs) ->
+ [{false,F} || {_B,F} <- RecArgs].
+
+-spec soft_clean_rec_args(rec_args(), rec_fun_info(), boolean()) -> rec_args().
+soft_clean_rec_args(RecArgs, RecFunInfo, ToList) ->
+ soft_clean_rec_args_tr(RecArgs, [], RecFunInfo, ToList, false, 1).
+
+-spec soft_clean_rec_args_tr(rec_args(), rec_args(), rec_fun_info(), boolean(),
+ boolean(), position()) -> rec_args().
+soft_clean_rec_args_tr([], Acc, _RecFunInfo, _ToList, _FoundListInst, _Pos) ->
+ lists:reverse(Acc);
+soft_clean_rec_args_tr([{{list,_NonEmpty,_AltRecFun},FTRef} | Rest], Acc,
+ RecFunInfo, ToList, true, Pos) ->
+ NewArg = {false,FTRef},
+ soft_clean_rec_args_tr(Rest, [NewArg|Acc], RecFunInfo, ToList, true, Pos+1);
+soft_clean_rec_args_tr([{{list,NonEmpty,AltRecFun},FTRef} | Rest], Acc,
+ RecFunInfo, ToList, false, Pos) ->
+ {NumTypes,NumRecs,RecArgLens,RecFuns} = RecFunInfo,
+ AltRecFunPos = get_group(Pos, RecArgLens),
+ AltRecFuns = proper_arith:list_update(AltRecFunPos, AltRecFun, RecFuns),
+ AltRecFunInfo = {NumTypes,NumRecs,RecArgLens,AltRecFuns},
+ NewArg = {{list,NonEmpty,tuple_rec_fun(AltRecFunInfo,ToList)},FTRef},
+ soft_clean_rec_args_tr(Rest, [NewArg|Acc], RecFunInfo, ToList, true, Pos+1);
+soft_clean_rec_args_tr([Arg | Rest], Acc, RecFunInfo, ToList, FoundListInst,
+ Pos) ->
+ soft_clean_rec_args_tr(Rest, [Arg | Acc], RecFunInfo, ToList, FoundListInst,
+ Pos+1).
+
+-spec get_group(pos_integer(), [non_neg_integer()]) -> pos_integer().
+get_group(Pos, AllMembers) ->
+ get_group_tr(Pos, AllMembers, 1).
+
+-spec get_group_tr(pos_integer(), [non_neg_integer()], pos_integer()) ->
+ pos_integer().
+get_group_tr(Pos, [Members | Rest], GroupNum) ->
+ case Pos =< Members of
+ true -> GroupNum;
+ false -> get_group_tr(Pos - Members, Rest, GroupNum + 1)
+ end.
+
+-spec same_full_type_ref(full_type_ref(), term()) -> boolean().
+same_full_type_ref({SameMod,type,SameName,Args1},
+ {SameMod,type,SameName,Args2}) ->
+ length(Args1) =:= length(Args2)
+ andalso lists:all(fun({A,B}) -> same_ret_type(A,B) end,
+ lists:zip(Args1, Args2));
+same_full_type_ref({SameMod,record,SameName,SubstsDict1},
+ {SameMod,record,SameName,SubstsDict2}) ->
+ same_substs_dict(SubstsDict1, SubstsDict2);
+same_full_type_ref(_, _) ->
+ false.
+
+-spec same_ret_type(ret_type(), ret_type()) -> boolean().
+same_ret_type({simple,FinType1}, {simple,FinType2}) ->
+ same_fin_type(FinType1, FinType2);
+same_ret_type({rec,RecFun1,RecArgs1}, {rec,RecFun2,RecArgs2}) ->
+ NumRecArgs = length(RecArgs1),
+ length(RecArgs2) =:= NumRecArgs
+ andalso lists:all(fun({A1,A2}) -> same_rec_arg(A1,A2,NumRecArgs) end,
+ lists:zip(RecArgs1,RecArgs2))
+ andalso same_rec_fun(RecFun1, RecFun2, NumRecArgs);
+same_ret_type(_, _) ->
+ false.
+
+%% TODO: Is this too strict?
+-spec same_rec_arg(rec_arg(), rec_arg(), arity()) -> boolean().
+same_rec_arg({{list,SameBool,AltRecFun1},FTRef1},
+ {{list,SameBool,AltRecFun2},FTRef2}, NumRecArgs) ->
+ same_rec_fun(AltRecFun1, AltRecFun2, NumRecArgs)
+ andalso same_full_type_ref(FTRef1, FTRef2);
+same_rec_arg({true,FTRef1}, {true,FTRef2}, _NumRecArgs) ->
+ same_full_type_ref(FTRef1, FTRef2);
+same_rec_arg({false,FTRef1}, {false,FTRef2}, _NumRecArgs) ->
+ same_full_type_ref(FTRef1, FTRef2);
+same_rec_arg(_, _, _NumRecArgs) ->
+ false.
+
+-spec same_substs_dict(substs_dict(), substs_dict()) -> boolean().
+same_substs_dict(SubstsDict1, SubstsDict2) ->
+ SameKVPair = fun({{_K,V1},{_K,V2}}) -> same_ret_type(V1,V2);
+ (_) -> false
+ end,
+ SubstsKVList1 = lists:sort(dict:to_list(SubstsDict1)),
+ SubstsKVList2 = lists:sort(dict:to_list(SubstsDict2)),
+ length(SubstsKVList1) =:= length(SubstsKVList2)
+ andalso lists:all(SameKVPair, lists:zip(SubstsKVList1,SubstsKVList2)).
+
+-spec same_fin_type(fin_type(), fin_type()) -> boolean().
+same_fin_type(Type1, Type2) ->
+ proper_types:equal_types(Type1, Type2).
+
+-spec same_rec_fun(rec_fun(), rec_fun(), arity()) -> boolean().
+same_rec_fun(RecFun1, RecFun2, NumRecArgs) ->
+ %% It's ok that we return a type, even if there's a 'true' for use of
+ %% an instance.
+ GenFun = fun(_Size) -> proper_types:exactly('$dummy') end,
+ GenFuns = lists:duplicate(NumRecArgs,GenFun),
+ same_fin_type(RecFun1(GenFuns,0), RecFun2(GenFuns,0)).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/simple/exact_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/simple/exact_adt.erl
new file mode 100644
index 0000000000..7103847ae7
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/simple/exact_adt.erl
@@ -0,0 +1,17 @@
+-module(exact_adt).
+
+-export([exact_adt_set_type/1, exact_adt_set_type2/1]).
+
+-export_type([exact_adt/0]).
+
+-record(exact_adt, {}).
+
+-opaque exact_adt() :: #exact_adt{}.
+
+-spec exact_adt_set_type(_) -> exact_adt().
+
+exact_adt_set_type(G) -> G.
+
+-spec exact_adt_set_type2(exact_adt()) -> exact_adt().
+
+exact_adt_set_type2(G) -> G.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/simple/exact_api.erl b/lib/dialyzer/test/opaque_SUITE_data/src/simple/exact_api.erl
new file mode 100644
index 0000000000..c19330eb30
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/simple/exact_api.erl
@@ -0,0 +1,60 @@
+-module(exact_api).
+
+-export([new/0, exact_api_test/1, exact_api_new/1,
+ exact_adt_test/1, exact_adt_new/1]).
+
+-export_type([exact_api/0]).
+
+-record(digraph, {vtab = notable :: ets:tab(),
+ etab = notable :: ets:tab(),
+ ntab = notable :: ets:tab(),
+ cyclic = true :: boolean()}).
+
+-spec new() -> digraph:graph().
+
+new() ->
+ A = #digraph{},
+ set_type(A), % does not have an opaque term as 1st argument
+ A.
+
+-spec set_type(digraph:graph()) -> true.
+
+set_type(G) ->
+ digraph:delete(G).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% The derived spec of exact_api_new() is
+%%% -spec exact_api_new(exact_api:exact_api()) -> exact_api:exact_api().
+%%% This won't happen unless dialyzer_typesig uses
+%%% t_is_exactly_equal() rather than t_is_equal().
+%%% [As of R17B the latter considers two types equal if nothing but
+%%% their ?opaque tags differ.]
+
+-record(exact_api, {}).
+
+-opaque exact_api() :: #exact_api{}.
+
+exact_api_test(X) ->
+ #exact_api{} = exact_api_set_type(X). % OK
+
+exact_api_new(A) ->
+ A = #exact_api{},
+ _ = exact_api_set_type(A), % OK (the opaque type is local)
+ A.
+
+-spec exact_api_set_type(exact_api()) -> exact_api().
+
+exact_api_set_type(#exact_api{}=E) -> E.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(exact_adt, {}).
+
+exact_adt_test(X) ->
+ #exact_adt{} = exact_adt:exact_adt_set_type(X). % breaks the opaqueness
+
+exact_adt_new(A) ->
+ A = #exact_adt{},
+ _ = exact_adt:exact_adt_set_type2(A), % does not have an opaque term as 1st argument
+ A.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/simple/is_rec.erl b/lib/dialyzer/test/opaque_SUITE_data/src/simple/is_rec.erl
new file mode 100644
index 0000000000..2b157483bc
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/simple/is_rec.erl
@@ -0,0 +1,65 @@
+-module(is_rec).
+
+-export([ri1/0, ri11/0, ri13/0, ri14/0, ri2/0, ri3/0, ri4/0, ri5/0,
+ ri6/0, ri7/0, ri8/0]).
+
+-record(r, {f1 :: integer()}).
+
+ri1() ->
+ A = simple1_adt:d1(),
+ is_record(A, r). % opaque term 1
+
+ri11() ->
+ A = simple1_adt:d1(),
+ I = '1-3'(),
+ is_record(A, r, I). % opaque term 1
+
+ri13() ->
+ A = simple1_adt:d1(),
+ if is_record(A, r) -> true end. % breaks the opaqueness
+
+ri14() ->
+ A = simple1_adt:d1(),
+ if is_record({A, 1}, r) -> true end. % breaks the opaqueness
+
+-type '1-3-t'() :: 1..3.
+
+-spec '1-3'() -> '1-3-t'().
+
+'1-3'() ->
+ random:uniform(3).
+
+
+-spec 'Atom'() -> atom().
+
+'Atom'() ->
+ a.
+
+ri2() ->
+ A = simple1_adt:d1(),
+ R = 'Atom'(),
+ is_record(A, R). % opaque term 1
+
+ri3() ->
+ A = simple1_adt:d1(),
+ is_record(A, A, 1). % opaque term 2
+
+ri4() ->
+ A = simple1_adt:d1(),
+ is_record(A, hipp:hopp(), 1). % opaque term 1
+
+ri5() ->
+ A = simple1_adt:d1(),
+ is_record(A, A, hipp:hopp()). % opaque term 2
+
+ri6() ->
+ A = simple1_adt:d1(),
+ if is_record(A, r) -> true end. % breaks opaqueness
+
+ri7() ->
+ A = simple1_adt:d1(),
+ if is_record({r, A}, r) -> true end. % A violates #r{}
+
+ri8() ->
+ A = simple1_adt:d1(),
+ is_record({A, 1}, r). % opaque term 1
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/simple/rec_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/simple/rec_adt.erl
new file mode 100644
index 0000000000..ff80d6e99b
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/simple/rec_adt.erl
@@ -0,0 +1,28 @@
+-module(rec_adt).
+
+-export([f/0, r1/0]).
+
+-export_type([r1/0]).
+
+-export_type([f/0, op_t/0, a/0]).
+
+-opaque a() :: a | b.
+
+-record(r1,
+ {f1 :: a()}).
+
+-opaque r1() :: #r1{}.
+
+-opaque f() :: fun((_) -> _).
+
+-opaque op_t() :: integer().
+
+-spec f() -> f().
+
+f() ->
+ fun(_) -> 3 end.
+
+-spec r1() -> r1().
+
+r1() ->
+ #r1{f1 = a}.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/simple/rec_api.erl b/lib/dialyzer/test/opaque_SUITE_data/src/simple/rec_api.erl
new file mode 100644
index 0000000000..fb6d59d263
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/simple/rec_api.erl
@@ -0,0 +1,123 @@
+-module(rec_api).
+
+-export([t1/0, t2/0, t3/0, adt_t1/0, adt_t1/1, adt_r1/0,
+ t/1, t_adt/0, r/0, r_adt/0, u1/0, u2/0, u3/0, v1/0, v2/0, v3/0]).
+
+-export_type([{a,0},{r1,0}, r2/0, r3/0]).
+
+-export_type([f/0, op_t/0, r/0, tup/0]).
+
+-opaque a() :: a | b.
+
+-record(r1,
+ {f1 :: a()}).
+
+-opaque r1() :: #r1{}.
+
+t1() ->
+ A = #r1{f1 = a},
+ {r1, a} = A.
+
+t2() ->
+ A = {r1, 10},
+ {r1, 10} = A,
+ A = #r1{f1 = 10}, % violates the type of field f1
+ #r1{f1 = 10} = A.
+
+t3() ->
+ A = {r1, 10},
+ #r1{f1 = 10} = A. % violates the type of #r1{}
+
+adt_t1() ->
+ R = rec_adt:r1(),
+ {r1, a} = R. % breaks the opaqueness
+
+-spec adt_t1(rec_adt:r1()) -> rec_adt:r1(). % invalid type spec
+
+adt_t1(R) ->
+ {r1, a} = R.
+
+-spec adt_r1() -> rec_adt:r1(). % invalid type spec
+
+adt_r1() ->
+ #r1{f1 = a}.
+
+-opaque f() :: fun((_) -> _).
+
+-opaque op_t() :: integer().
+
+-spec t(f()) -> _.
+
+t(A) ->
+ T = term(),
+ %% 3(T), % cannot test this: dialyzer_dep deliberately crashes
+ A(T).
+
+-spec term() -> op_t().
+
+term() ->
+ 3.
+
+t_adt() ->
+ A = rec_adt:f(),
+ T = term(),
+ A(T).
+
+-record(r, {f = fun(_) -> 3 end :: f(), o = 1 :: op_t()}).
+
+-opaque r() :: #r{}.
+
+-opaque tup() :: {'r', f(), op_t()}.
+
+-spec r() -> _.
+
+r() ->
+ {{r, f(), 2},
+ #r{f = f(), o = 2}}. % OK, f() is a local opaque type
+
+-spec f() -> f().
+
+f() ->
+ fun(_) -> 3 end.
+
+r_adt() ->
+ {{r, rec_adt:f(), 2},
+ #r{f = rec_adt:f(), o = 2}}. % breaks the opaqueness
+
+-record(r2, % like #r1{}, but with initial value
+ {f1 = a :: a()}).
+
+-opaque r2() :: #r2{}.
+
+u1() ->
+ A = #r2{f1 = a},
+ {r2, a} = A.
+
+u2() ->
+ A = {r2, 10},
+ {r2, 10} = A,
+ A = #r2{f1 = 10}, % violates the type of field f1
+ #r2{f1 = 10} = A.
+
+u3() ->
+ A = {r2, 10},
+ #r2{f1 = 10} = A. % violates the type of #r2{}
+
+-record(r3, % like #r1{}, but an opaque type
+ {f1 = queue:new():: queue:queue()}).
+
+-opaque r3() :: #r3{}.
+
+v1() ->
+ A = #r3{f1 = queue:new()},
+ {r3, a} = A. % breaks the opaqueness
+
+v2() ->
+ A = {r3, 10},
+ {r3, 10} = A,
+ A = #r3{f1 = 10}, % violates the type of field f1
+ #r3{f1 = 10} = A.
+
+v3() ->
+ A = {r3, 10},
+ #r3{f1 = 10} = A. % breaks the opaqueness
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple1_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple1_adt.erl
new file mode 100644
index 0000000000..21a277c1e9
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple1_adt.erl
@@ -0,0 +1,138 @@
+-module(simple1_adt).
+
+-export([d1/0, d2/0, i/0, n1/0, n2/0, o1/0, o2/0,
+ c1/0, c2/0, bit1/0, a/0, i1/0, tuple/0,
+ b1/0, b2/0, ty_i1/0]).
+
+-export_type([o1/0, o2/0, d1/0, d2/0]).
+
+-export_type([i1/0, i2/0, di1/0, di2/0]).
+
+-export_type([ty_i1/0, c1/0, c2/0]).
+
+-export_type([b1/0, b2/0]).
+
+-export_type([bit1/0]).
+
+-export_type([tuple1/0, a/0, i/0]).
+
+%% Equal:
+
+-opaque o1() :: a | b | c.
+
+-opaque o2() :: a | b | c.
+
+%% Disjoint:
+
+-opaque d1() :: a | b | c.
+
+-opaque d2() :: d | e | f.
+
+%% One common element:
+
+-opaque c1() :: a | b | c.
+
+-opaque c2() :: c | e | f.
+
+%% Equal integer range:
+
+-opaque i1() :: 1 | 2.
+
+-opaque i2() :: 1 | 2.
+
+%% Disjoint integer range:
+
+-opaque di1() :: 1 | 2.
+
+-opaque di2() :: 3 | 4.
+
+
+-type ty_i1() :: 1 | 2.
+
+%% Boolean types
+
+-opaque b1() :: boolean().
+
+-opaque b2() :: boolean().
+
+%% Binary types
+
+-opaque bit1() :: binary().
+
+%% Tuple types
+
+-opaque tuple1() :: tuple().
+
+%% Atom type
+
+-opaque a() :: atom().
+
+-opaque i() :: integer().
+
+-spec d1() -> d1().
+
+d1() -> a.
+
+-spec d2() -> d2().
+
+d2() -> d.
+
+-spec i() -> i().
+
+i() ->
+ 1.
+
+-spec n1() -> o1().
+
+n1() -> a.
+
+-spec n2() -> o2().
+
+n2() -> a.
+
+-spec o1() -> o1().
+
+o1() -> a.
+
+-spec o2() -> o2().
+
+o2() -> a.
+
+-spec c1() -> c1().
+
+c1() -> a.
+
+-spec c2() -> c2().
+
+c2() -> e.
+
+-spec bit1() -> bit1().
+
+bit1() ->
+ <<"hej">>.
+
+-spec a() -> a().
+
+a() ->
+ e.
+
+-spec i1() -> i1().
+
+i1() -> 1.
+
+-spec tuple() -> tuple1().
+
+tuple() -> {1,2}.
+
+-spec b1() -> b1().
+
+b1() -> true.
+
+-spec b2() -> b2().
+
+b2() -> false.
+
+-spec ty_i1() -> ty_i1().
+
+ty_i1() ->
+ 1.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple1_api.erl b/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple1_api.erl
new file mode 100644
index 0000000000..7db1100597
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple1_api.erl
@@ -0,0 +1,571 @@
+-module(simple1_api).
+
+-export([t1/1, adt_t1/1, t2/1, adt_t2/1, tup/0, t3/0, t4/0, t5/0, t6/0, t7/0,
+ t8/0, adt_t3/0, adt_t4/0, adt_t7/0, adt_t8/0, adt_t5/0,
+ c1/2, c2/2, c2/0, c3/0, c4/0, tt1/0, tt2/0,
+ cmp1/0, cmp2/0, cmp3/0, cmp4/0,
+ ty_cmp1/0, ty_cmp2/0, ty_cmp3/0, ty_cmp4/0,
+ f1/0, f2/0, adt_f1/0, adt_f2/0, f3/0, f4/0, adt_f3/0, adt_f4/0,
+ adt_f4_a/0, adt_f4_b/0,
+ bool_t1/0, bool_t2/0, bool_t3/0, bool_t4/0, bool_t5/1, bool_t6/1,
+ bool_t7/0, bool_adt_t1/0, bool_adt_t2/0, bool_adt_t5/1,
+ bool_adt_t6/1, bool_t8/0, bool_adt_t8/2, bool_t9/0, bool_adt_t9/2,
+ bit_t1/0, bit_adt_t1/0, bit_t3/1, bit_adt_t2/0, bit_adt_t3/1,
+ bit_t5/1, bit_t4/1, bit_adt_t4/1, bit_t5/0, bit_adt_t5/0,
+ call_f/1, call_f_adt/1, call_m_adt/1, call_m/1, call_f_i/1,
+ call_m_i/1, call_m_adt_i/1, call_f_adt_i/1,
+ eq1/0, eq2/0, c5/0, c6/2, c7/2, c8/0]).
+
+%%% Equal opaque types
+
+-export_type([o1/0, o2/0]).
+
+-export_type([d1/0, d2/0]).
+
+-opaque o1() :: a | b | c.
+
+-opaque o2() :: a | b | c.
+
+-export_type([i1/0, i2/0, di1/0, di2/0]).
+
+-export_type([b1/0, b2/0]).
+
+-export_type([bit1/0]).
+
+-export_type([a/0, i/0]).
+
+%% The derived spec is
+%% -spec t1('a' | 'b') -> simple1_api:o1('a') | simple1_api:o2('a').
+%% but that is not tested...
+
+t1(a) ->
+ o1();
+t1(b) ->
+ o2().
+
+-spec o1() -> o1().
+
+o1() -> a.
+
+-spec o2() -> o2().
+
+o2() -> a.
+
+%% The derived spec is
+%% -spec adt_t1('a' | 'b') -> simple1_adt:o1('a') | simple1_adt:o2('a').
+%% but that is not tested...
+
+adt_t1(a) ->
+ simple1_adt:o1();
+adt_t1(b) ->
+ simple1_adt:o2().
+
+%%% Disjunct opaque types
+
+-opaque d1() :: a | b | c.
+
+-opaque d2() :: d | e | f.
+
+%% -spec t2('a' | 'b') -> simple1_api:d1('a') | simple1_api:d2('d').
+
+t2(a) ->
+ d1();
+t2(b) ->
+ d2().
+
+-spec d1() -> d1().
+
+d1() -> a.
+
+-spec d2() -> d2().
+
+d2() -> d.
+
+%% -spec adt_t2('a' | 'b') -> simple1_adt:d1('a') | simple1_adt:d2('d').
+
+adt_t2(a) ->
+ simple1_adt:d1();
+adt_t2(b) ->
+ simple1_adt:d2().
+
+-spec tup() -> simple1_adt:tuple1(). % invalid type spec
+
+tup() ->
+ {a, b}.
+
+%%% Matching equal opaque types with different names
+
+t3() ->
+ A = n1(),
+ B = n2(),
+ A = A, % OK, of course
+ A = B. % OK since o1() and o2() are local opaque types
+
+t4() ->
+ A = n1(),
+ B = n2(),
+ true = A =:= A, % OK, of course
+ A =:= B. % OK since o1() and o2() are local opaque types
+
+t5() ->
+ A = d1(),
+ B = d2(),
+ A =:= B. % can never evaluate to true
+
+t6() ->
+ A = d1(),
+ B = d2(),
+ A = B. % can never succeed
+
+t7() ->
+ A = d1(),
+ B = d2(),
+ A =/= B. % OK (always true?)
+
+t8() ->
+ A = d1(),
+ B = d2(),
+ A /= B. % OK (always true?)
+
+-spec n1() -> o1().
+
+n1() -> a.
+
+-spec n2() -> o2().
+
+n2() -> a.
+
+adt_t3() ->
+ A = simple1_adt:n1(),
+ B = simple1_adt:n2(),
+ true = A =:= A, % OK.
+ A =:= B. % opaque test, not OK
+
+adt_t4() ->
+ A = simple1_adt:n1(),
+ B = simple1_adt:n2(),
+ A = A, % OK
+ A = B. % opaque terms
+
+adt_t7() ->
+ A = simple1_adt:n1(),
+ B = simple1_adt:n2(),
+ false = A =/= A, % OK
+ A =/= B. % opaque test, not OK
+
+adt_t8() ->
+ A = simple1_adt:n1(),
+ B = simple1_adt:n2(),
+ false = A /= A, % OK
+ A /= B. % opaque test, not OK
+
+adt_t5() ->
+ A = simple1_adt:c1(),
+ B = simple1_adt:c2(),
+ A =:= B. % opaque test, not OK
+
+%% Comparison in guard
+
+-spec c1(simple1_adt:d1(), simple1_adt:d2()) -> boolean().
+
+c1(A, B) when A =< B -> true. % succ type of A and B is any() (type spec is OK)
+
+-spec c2(simple1_adt:d1(), simple1_adt:d2()) -> boolean().
+
+c2(A, B) ->
+ if A =< B -> true end. % succ type of A and B is any() (type spec is OK)
+
+c2() ->
+ A = simple1_adt:d1(),
+ B = simple1_adt:d2(),
+ if A =< B -> ok end. % opaque terms
+
+c3() ->
+ B = simple1_adt:d2(),
+ if a =< B -> ok end. % opaque term
+
+c4() ->
+ A = simple1_adt:d1(),
+ if A =< d -> ok end. % opaque term
+
+tt1() ->
+ A = o1(),
+ is_integer(A). % OK
+
+tt2() ->
+ A = simple1_adt:d1(),
+ is_integer(A). % breaks the opaqueness
+
+%% Comparison with integers
+
+-opaque i1() :: 1 | 2.
+
+-opaque i2() :: 1 | 2.
+
+-opaque di1() :: 1 | 2.
+
+-opaque di2() :: 3 | 4.
+
+-spec i1() -> i1().
+
+i1() -> 1.
+
+-type ty_i1() :: 1 | 2.
+
+-spec ty_i1() -> ty_i1().
+
+ty_i1() -> 1.
+
+cmp1() ->
+ A = i1(),
+ if A > 3 -> ok end. % can never succeed
+
+cmp2() ->
+ A = simple1_adt:i1(),
+ if A > 3 -> ok end. % opaque term
+
+cmp3() ->
+ A = i1(),
+ if A < 3 -> ok end.
+
+cmp4() ->
+ A = simple1_adt:i1(),
+ if A < 3 -> ok end. % opaque term
+
+%% -type
+
+ty_cmp1() ->
+ A = ty_i1(),
+ if A > 3 -> ok end. % can never succeed
+
+ty_cmp2() ->
+ A = simple1_adt:ty_i1(),
+ if A > 3 -> ok end. % can never succeed
+
+ty_cmp3() ->
+ A = ty_i1(),
+ if A < 3 -> ok end.
+
+ty_cmp4() ->
+ A = simple1_adt:ty_i1(),
+ if A < 3 -> ok end.
+
+%% is_function
+
+f1() ->
+ T = n1(),
+ if is_function(T) -> ok end. % can never succeed
+
+f2() ->
+ T = n1(),
+ is_function(T). % ok
+
+adt_f1() ->
+ T = simple1_adt:n1(),
+ if is_function(T) -> ok end. % breaks the opaqueness
+
+adt_f2() ->
+ T = simple1_adt:n1(),
+ is_function(T). % breaks the opaqueness
+
+f3() ->
+ A = i1(),
+ T = n1(),
+ if is_function(T, A) -> ok end. % can never succeed
+
+f4() ->
+ A = i1(),
+ T = n1(),
+ is_function(T, A). % ok
+
+adt_f3() ->
+ A = simple1_adt:i1(),
+ T = simple1_adt:n1(),
+ if is_function(T, A) -> ok end. % breaks the opaqueness
+
+adt_f4() ->
+ A = simple1_adt:i1(),
+ T = simple1_adt:n1(),
+ is_function(T, A). % breaks the opaqueness
+
+adt_f4_a() ->
+ A = simple1_adt:i1(),
+ T = n1(),
+ is_function(T, A). % opaque term
+
+
+adt_f4_b() ->
+ A = i1(),
+ T = simple1_adt:n1(),
+ is_function(T, A). % breaks the opaqueness
+
+%% A few Boolean examples
+
+bool_t1() ->
+ B = b2(),
+ if B -> ok end. % B =:= true can never succeed
+
+bool_t2() ->
+ A = b1(),
+ B = b2(),
+ if A and not B -> ok end.
+
+bool_t3() ->
+ A = b1(),
+ if not A -> ok end. % can never succeed
+
+bool_t4() ->
+ A = n1(),
+ if not ((A >= 1) and not (A < 1)) -> ok end. % can never succeed
+
+-spec bool_t5(i1()) -> integer().
+
+bool_t5(A) ->
+ if [not (A > 1)] =:=
+ [false]-> 1 end.
+
+-spec bool_t6(b1()) -> integer().
+
+bool_t6(A) ->
+ if [not A] =:=
+ [false]-> 1 end.
+
+-spec bool_t7() -> integer().
+
+bool_t7() ->
+ A = i1(),
+ if [not A] =:= % cannot succeed
+ [false]-> 1 end.
+
+bool_adt_t1() ->
+ B = simple1_adt:b2(),
+ if B -> ok end. % opaque term
+
+bool_adt_t2() ->
+ A = simple1_adt:b1(),
+ B = simple1_adt:b2(),
+ if A and not B -> ok end. % opaque term
+
+-spec bool_adt_t5(simple1_adt:i1()) -> integer().
+
+bool_adt_t5(A) ->
+ if [not (A > 1)] =:= % succ type of A is any() (type spec is OK)
+ [false]-> 1 end.
+
+-spec bool_adt_t6(simple1_adt:b1()) -> integer(). % invalid type spec
+
+bool_adt_t6(A) ->
+ if [not A] =:= % succ type of A is 'true'
+ [false]-> 1 end.
+
+-spec bool_t8() -> integer().
+
+bool_t8() ->
+ A = i1(),
+ if [A and A] =:= % cannot succeed
+ [false]-> 1 end.
+
+-spec bool_adt_t8(simple1_adt:b1(), simple1_adt:b2()) -> integer(). % invalid
+
+bool_adt_t8(A, B) ->
+ if [A and B] =:=
+ [false]-> 1 end.
+
+-spec bool_t9() -> integer().
+
+bool_t9() ->
+ A = i1(),
+ if [A or A] =:= % cannot succeed
+ [false]-> 1 end.
+
+-spec bool_adt_t9(simple1_adt:b1(), simple1_adt:b2()) -> integer(). % invalid
+
+bool_adt_t9(A, B) ->
+ if [A or B] =:=
+ [false]-> 1 end.
+
+-opaque b1() :: boolean().
+
+-opaque b2() :: boolean().
+
+-spec b1() -> b1().
+
+b1() -> true.
+
+-spec b2() -> b2().
+
+b2() -> false.
+
+%% Few (very few...) examples with bit syntax
+
+bit_t1() ->
+ A = i1(),
+ <<100:(A)>>.
+
+bit_adt_t1() ->
+ A = simple1_adt:i1(),
+ <<100:(A)>>. % breaks the opaqueness
+
+bit_t3(A) ->
+ B = i1(),
+ case none:none() of
+ <<A:B>> -> 1
+ end.
+
+bit_adt_t2() ->
+ A = simple1_adt:i1(),
+ case <<"hej">> of
+ <<_:A>> -> ok % breaks the opaqueness (but the message is strange)
+ end.
+
+
+bit_adt_t3(A) ->
+ B = simple1_adt:i1(),
+ case none:none() of
+ <<A: % breaks the opaqueness (the message is less than perfect)
+ B>> -> 1
+ end.
+
+bit_t5(A) ->
+ B = o1(),
+ case none:none() of % the type is any(); should fix that XXX
+ <<A:B>> -> 1 % can never match (local opaque type is OK)
+ end.
+
+-spec bit_t4(<<_:1>>) -> integer().
+
+bit_t4(A) ->
+ Sz = i1(),
+ case A of
+ <<_:Sz>> -> 1
+ end.
+
+-spec bit_adt_t4(<<_:1>>) -> integer().
+
+bit_adt_t4(A) ->
+ Sz = simple1_adt:i1(),
+ case A of
+ <<_:Sz>> -> 1 % breaks the opaqueness
+ end.
+
+bit_t5() ->
+ A = bit1(),
+ case A of
+ <<_/binary>> -> 1
+ end.
+
+bit_adt_t5() ->
+ A = simple1_adt:bit1(),
+ case A of
+ <<_/binary>> -> 1 % breaks the opaqueness
+ end.
+
+-opaque bit1() :: binary().
+
+-spec bit1() -> bit1().
+
+bit1() ->
+ <<"hej">>.
+
+%% Calls with variable module or function
+
+call_f(A) ->
+ A = a(),
+ foo:A(A).
+
+call_f_adt(A) ->
+ A = simple1_adt:a(),
+ foo:A(A). % breaks the opaqueness
+
+call_m(A) ->
+ A = a(),
+ A:foo(A).
+
+call_m_adt(A) ->
+ A = simple1_adt:a(),
+ A:foo(A). % breaks the opaqueness
+
+-opaque a() :: atom().
+
+-opaque i() :: integer().
+
+-spec a() -> a().
+
+a() ->
+ e.
+
+call_f_i(A) ->
+ A = i(),
+ foo:A(A). % A is not atom() but i()
+
+call_f_adt_i(A) ->
+ A = simple1_adt:i(),
+ foo:A(A). % A is not atom() but simple1_adt:i()
+
+call_m_i(A) ->
+ A = i(),
+ A:foo(A). % A is not atom() but i()
+
+call_m_adt_i(A) ->
+ A = simple1_adt:i(),
+ A:foo(A). % A is not atom() but simple1_adt:i()
+
+-spec eq1() -> integer().
+
+eq1() ->
+ A = simple1_adt:d2(),
+ B = simple1_adt:d1(),
+ if
+ A == B -> % opaque terms
+ 0;
+ A == A ->
+ 1;
+ A =:= A -> % compiler finds this one cannot match
+ 2;
+ true -> % compiler finds this one cannot match
+ 3
+ end.
+
+eq2() ->
+ A = simple1_adt:d1(),
+ if
+ {A} >= {A} ->
+ 1;
+ A >= 3 -> % opaque term
+ 2;
+ A == 3 -> % opaque term
+ 3;
+ A =:= 3 -> % opaque term
+ 4;
+ A == A ->
+ 5;
+ A =:= A -> % compiler finds this one cannot match
+ 6
+ end.
+
+c5() ->
+ A = simple1_adt:d1(),
+ A < 3. % opaque term
+
+c6(A, B) ->
+ A = simple1_adt:d1(),
+ B = simple1_adt:d1(),
+ A =< B. % same type - no warning
+
+c7(A, B) ->
+ A = simple1_adt:d1(),
+ B = simple1_adt:d2(),
+ A =< B. % opaque terms
+
+c8() ->
+ D = digraph:new(),
+ E = ets:new(foo, []),
+ if {D, a} > {D, E} -> true; % OK
+ {1.0, 2} > {{D}, {E}} -> true; % OK
+ {D, 3} > {D, E} -> true % opaque term 2
+ end.
+
+-spec i() -> i().
+
+i() ->
+ 1.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple2_api.erl b/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple2_api.erl
new file mode 100644
index 0000000000..c86f6fd0b5
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/simple/simple2_api.erl
@@ -0,0 +1,125 @@
+-module(simple2_api).
+
+-export([c1/2, c2/0, c3/0, c4/1, c5/1, c6/0, c6_b/0, c7/0, c7_b/0,
+ c7_c/0, c8/0, c9/0, c10/0, c11/0, c12/0, c13/0, c14/0, c15/0,
+ c16/0, c17/0, c18/0, c19/0, c20/0, c21/0, c22/0, c23/0,
+ c24/0, c25/0, c26/0]).
+
+-spec c1(simple1_adt:d1(), simple1_adt:d2()) -> boolean().
+
+c1(A, B) ->
+ {A} =< {B}. % succ type of A and B is any()
+
+c2() ->
+ A = simple1_adt:d1(),
+ erlang:make_tuple(1, A). % ok
+
+c3() ->
+ A = simple1_adt:d1(),
+ setelement(1, {A}, A). % ok
+
+c4(_) ->
+ A = simple1_adt:d1(),
+ halt(A). % ok (BIF fails...)
+
+c5(_) ->
+ A = simple1_adt:d1(),
+ [A] -- [A]. % ok
+
+c6() ->
+ A = simple1_adt:d1(),
+ A ! foo. % opaque term
+
+c6_b() ->
+ A = simple1_adt:d1(),
+ erlang:send(A, foo). % opaque term
+
+c7() ->
+ A = simple1_adt:d1(),
+ foo ! A. % ok
+
+c7_b() ->
+ A = simple1_adt:d1(),
+ erlang:send(foo, A). % ok
+
+c7_c() ->
+ A = simple1_adt:d1(),
+ erlang:send(foo, A, []). % ok
+
+c8() ->
+ A = simple1_adt:d1(),
+ A < 3. % opaque term
+
+c9() ->
+ A = simple1_adt:d1(),
+ lists:keysearch(A, 1, []). % ok
+
+c10() ->
+ A = simple1_adt:d1(),
+ lists:keysearch(1, A, []). % opaque term 2
+
+c11() ->
+ A = simple1_adt:tuple(),
+ lists:keysearch(key, 1, [A]). % ok
+
+c12() ->
+ A = simple1_adt:tuple(),
+ lists:keysearch(key, 1, A). % opaque term 3
+
+c13() ->
+ A = simple1_adt:tuple(),
+ lists:keysearch(key, 1, [{A,2}]). % ok
+
+c14() ->
+ A = simple1_adt:tuple(),
+ lists:keysearch(key, 1, [{2,A}]). % ok
+
+c15() ->
+ A = simple1_adt:d1(),
+ lists:keysearch(key, 1, [A]). % ok
+
+c16() ->
+ A = simple1_adt:tuple(),
+ erlang:send(foo, A). % ok
+
+c17() ->
+ A = simple1_adt:tuple(),
+ lists:reverse([A]). % ok
+
+c18() ->
+ A = simple1_adt:tuple(),
+ lists:keyreplace(a, 1, [A], {1,2}). % ok
+
+c19() ->
+ A = simple1_adt:tuple(),
+ %% Problem. The spec says argument 4 is a tuple(). Fix that!
+ lists:keyreplace(a, 1, [{1,2}], A). % opaque term 4
+
+c20() ->
+ A = simple1_adt:tuple(),
+ lists:flatten(A). % opaque term 1
+
+c21() ->
+ A = simple1_adt:tuple(),
+ lists:flatten([[{A}]]). % ok
+
+c22() ->
+ A = simple1_adt:tuple(),
+ lists:flatten([[A]]). % ok
+
+c23() ->
+ A = simple1_adt:tuple(),
+ lists:flatten([A]). % ok
+
+c24() ->
+ A = simple1_adt:tuple(),
+ lists:flatten({A}). % will never return
+
+c25() ->
+ A = simple1_adt:d1(),
+ B = simple1_adt:tuple(),
+ if {A,3} > {A,B} -> true end. % opaque 2nd argument
+
+c26() ->
+ B = simple1_adt:tuple(),
+ tuple_to_list(B). % opaque term 1
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/union/union_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/union/union_adt.erl
index 5ca3202bba..d88f238190 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/union/union_adt.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/union/union_adt.erl
@@ -1,10 +1,15 @@
-module(union_adt).
-export([new/1, new_a/1, new_rec/1]).
+%% Now (R17) that opaque types are no longer recognized by their shape
+%% this test case is rather meaningless.
+
-record(rec, {x = 42 :: integer()}).
-opaque u() :: 'aaa' | 'bbb' | #rec{}.
+-spec new(_) -> u().
+
new(a) -> aaa;
new(b) -> bbb;
new(X) when is_integer(X) ->
@@ -13,7 +18,11 @@ new(X) when is_integer(X) ->
%% the following two functions (and their uses in union_use.erl) test
%% that the return type is the opaque one and not just a subtype of it
+-spec new_a(_) -> u().
+
new_a(a) -> aaa.
+-spec new_rec(_) -> u().
+
new_rec(X) when is_integer(X) ->
#rec{x = X}.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/wings/wings_util.erl b/lib/dialyzer/test/opaque_SUITE_data/src/wings/wings_util.erl
index 8f0da1f5dc..ca6bc0ab4a 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/wings/wings_util.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/wings/wings_util.erl
@@ -14,12 +14,12 @@
rel2fam(Rel) ->
sofs:to_external(sofs:relation_to_family(sofs:relation(Rel))).
-%% a definition that does not violate the opaqueness of gb_tree()
+%% a definition that does not violate the opaqueness of gb_trees:tree()
gb_trees_smallest_key(Tree) ->
{Key, _V} = gb_trees:smallest(Tree),
Key.
-%% a definition that violates the opaqueness of gb_tree()
+%% a definition that violates the opaqueness of gb_trees:tree()
gb_trees_largest_key({_, Tree}) ->
largest_key1(Tree).
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_adt.erl b/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_adt.erl
new file mode 100644
index 0000000000..c742990c6a
--- /dev/null
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_adt.erl
@@ -0,0 +1,5 @@
+-module(zoltan_adt).
+
+-export_type([id/0]).
+
+-opaque id() :: string().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis2.erl b/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis2.erl
index 38c6051c58..e094d1982b 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis2.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis2.erl
@@ -2,7 +2,7 @@
-export([get/2]).
--opaque data() :: gb_tree().
+-opaque data() :: gb_trees:tree().
-spec get(term(), data()) -> term().
diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis3.erl b/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis3.erl
index b62b9de576..07c9f0a270 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis3.erl
+++ b/lib/dialyzer/test/opaque_SUITE_data/src/zoltan_kis3.erl
@@ -2,13 +2,13 @@
-export([f/0, gen/0]).
--opaque id() :: string().
+%-opaque id() :: string().
-spec f() -> char().
%% List pattern matching issue
f() -> [H|_T] = gen(), H.
--spec gen() -> id().
+-spec gen() -> zoltan_adt:id().
gen() -> "Dummy".