rdl 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -6
  4. data/CHANGES.md +29 -0
  5. data/README.md +94 -26
  6. data/lib/rdl/boot.rb +82 -41
  7. data/lib/rdl/boot_rails.rb +5 -0
  8. data/lib/rdl/config.rb +9 -1
  9. data/lib/rdl/query.rb +2 -2
  10. data/lib/rdl/typecheck.rb +972 -225
  11. data/lib/rdl/types/annotated_arg.rb +8 -0
  12. data/lib/rdl/types/ast_node.rb +73 -0
  13. data/lib/rdl/types/bot.rb +8 -0
  14. data/lib/rdl/types/bound_arg.rb +63 -0
  15. data/lib/rdl/types/computed.rb +48 -0
  16. data/lib/rdl/types/dependent_arg.rb +9 -0
  17. data/lib/rdl/types/dynamic.rb +61 -0
  18. data/lib/rdl/types/finite_hash.rb +54 -9
  19. data/lib/rdl/types/generic.rb +33 -0
  20. data/lib/rdl/types/intersection.rb +8 -0
  21. data/lib/rdl/types/lexer.rex +6 -1
  22. data/lib/rdl/types/lexer.rex.rb +13 -1
  23. data/lib/rdl/types/method.rb +14 -0
  24. data/lib/rdl/types/nominal.rb +8 -0
  25. data/lib/rdl/types/non_null.rb +8 -0
  26. data/lib/rdl/types/optional.rb +8 -0
  27. data/lib/rdl/types/parser.racc +31 -5
  28. data/lib/rdl/types/parser.tab.rb +540 -302
  29. data/lib/rdl/types/rdl_types.rb +45 -0
  30. data/lib/rdl/types/singleton.rb +14 -1
  31. data/lib/rdl/types/string.rb +104 -0
  32. data/lib/rdl/types/structural.rb +8 -0
  33. data/lib/rdl/types/top.rb +8 -0
  34. data/lib/rdl/types/tuple.rb +32 -8
  35. data/lib/rdl/types/type.rb +54 -11
  36. data/lib/rdl/types/union.rb +41 -2
  37. data/lib/rdl/types/var.rb +10 -0
  38. data/lib/rdl/types/vararg.rb +8 -0
  39. data/lib/rdl/util.rb +13 -10
  40. data/lib/rdl/wrap.rb +271 -27
  41. data/lib/rdl_disable.rb +16 -2
  42. data/lib/types/active_record.rb +1 -0
  43. data/lib/types/core/array.rb +442 -23
  44. data/lib/types/core/basic_object.rb +3 -3
  45. data/lib/types/core/bigdecimal.rb +5 -0
  46. data/lib/types/core/class.rb +2 -0
  47. data/lib/types/core/dir.rb +3 -3
  48. data/lib/types/core/enumerable.rb +4 -4
  49. data/lib/types/core/enumerator.rb +1 -1
  50. data/lib/types/core/file.rb +4 -4
  51. data/lib/types/core/float.rb +203 -0
  52. data/lib/types/core/hash.rb +390 -15
  53. data/lib/types/core/integer.rb +223 -10
  54. data/lib/types/core/io.rb +2 -2
  55. data/lib/types/core/kernel.rb +8 -5
  56. data/lib/types/core/marshal.rb +3 -0
  57. data/lib/types/core/module.rb +3 -3
  58. data/lib/types/core/numeric.rb +0 -2
  59. data/lib/types/core/object.rb +5 -5
  60. data/lib/types/core/pathname.rb +2 -2
  61. data/lib/types/core/process.rb +1 -3
  62. data/lib/types/core/range.rb +1 -1
  63. data/lib/types/core/regexp.rb +2 -2
  64. data/lib/types/core/set.rb +1 -1
  65. data/lib/types/core/string.rb +408 -16
  66. data/lib/types/core/symbol.rb +3 -3
  67. data/lib/types/core/time.rb +1 -1
  68. data/lib/types/core/uri.rb +13 -13
  69. data/lib/types/rails/_helpers.rb +7 -1
  70. data/lib/types/rails/action_controller/mime_responds.rb +2 -0
  71. data/lib/types/rails/active_record/associations.rb +42 -30
  72. data/lib/types/rails/active_record/comp_types.rb +637 -0
  73. data/lib/types/rails/active_record/finder_methods.rb +1 -1
  74. data/lib/types/rails/active_record/model_schema.rb +28 -16
  75. data/lib/types/rails/active_record/relation.rb +5 -3
  76. data/lib/types/rails/active_record/sql-strings.rb +166 -0
  77. data/lib/types/rails/string.rb +1 -1
  78. data/lib/types/sequel.rb +1 -0
  79. data/lib/types/sequel/comp_types.rb +581 -0
  80. data/rdl.gemspec +5 -4
  81. data/test/test_alias.rb +4 -0
  82. data/test/test_array_types.rb +244 -0
  83. data/test/test_bound_types.rb +80 -0
  84. data/test/test_contract.rb +4 -0
  85. data/test/test_dsl.rb +5 -0
  86. data/test/test_dyn_comptype_checks.rb +206 -0
  87. data/test/test_generic.rb +21 -20
  88. data/test/test_hash_types.rb +322 -0
  89. data/test/test_intersection.rb +1 -0
  90. data/test/test_le.rb +29 -4
  91. data/test/test_member.rb +3 -1
  92. data/test/test_parser.rb +5 -0
  93. data/test/test_query.rb +1 -0
  94. data/test/test_rdl.rb +63 -28
  95. data/test/test_rdl_type.rb +4 -0
  96. data/test/test_string_types.rb +102 -0
  97. data/test/test_type_contract.rb +59 -37
  98. data/test/test_typecheck.rb +480 -75
  99. data/test/test_types.rb +17 -0
  100. data/test/test_wrap.rb +5 -0
  101. metadata +35 -5
  102. data/lib/types/rails/active_record/schema_types.rb +0 -51
@@ -2,7 +2,7 @@ RDL.nowrap :Symbol
2
2
 
3
3
  RDL.type :Symbol, 'self.all_symbols', '() -> Array<Symbol>'
4
4
  RDL.type :Symbol, :<=>, '(Symbol other) -> Integer or nil'
5
- RDL.type :Symbol, :==, '(%any obj) -> %bool'
5
+ RDL.type :Symbol, :==, '(%any obj) -> %bool', effect: [:+, :+]
6
6
  RDL.type :Symbol, :=~, '(%any obj) -> Integer or nil'
7
7
  RDL.type :Symbol, :[], '(Integer idx) -> String'
8
8
  RDL.type :Symbol, :[], '(Integer b, Integer n) -> String'
@@ -22,6 +22,6 @@ RDL.rdl_alias :Symbol, :size, :length
22
22
  RDL.rdl_alias :Symbol, :slice, :[]
23
23
  RDL.type :Symbol, :swapcase, '() -> Symbol'
24
24
  RDL.type :Symbol, :to_proc, '() -> Proc' # TODO proc
25
- RDL.rdl_alias :Symbol, :to_s, :id2name
26
- RDL.rdl_alias :Symbol, :to_sym, :intern
25
+ RDL.type :Symbol, :to_s, "() -> String", effect: [:+, :+]
26
+ RDL.type :Symbol, :to_sym, "() -> self", effect: [:+, :+]
27
27
  RDL.type :Symbol, :upcase, '() -> Symbol'
@@ -39,7 +39,7 @@ RDL.type :Time, :monday?, '() -> %bool'
39
39
  RDL.rdl_alias :Time, :month, :mon
40
40
  RDL.type :Time, :nsec, '() -> Integer'
41
41
  RDL.type :Time, :round, '(Integer) -> Time'
42
- RDL.type :Time, :saturday, '() -> %bool'
42
+ RDL.type :Time, :saturday?, '() -> %bool'
43
43
  RDL.type :Time, :sec, '() -> Integer'
44
44
  RDL.type :Time, :strftime, '(String) -> String'
45
45
  RDL.type :Time, :subsec, '() -> Numeric'
@@ -1,18 +1,18 @@
1
1
  RDL.nowrap :URI
2
2
 
3
- RDL.type :URI, :decode_www_form, '(String, ?Encoding, ?String separator, %bool use_charset, %bool isindex) -> Array<[String,String]>'
4
- RDL.type :URI, :decode_www_form_component, '(String, ?Encoding) -> Array<[String,String]>'
3
+ RDL.type :URI, :'self.decode_www_form', '(String, ?Encoding, ?String separator, %bool use_charset, %bool isindex) -> Array<[String,String]>'
4
+ RDL.type :URI, :'self.decode_www_form_component', '(String, ?Encoding) -> Array<[String,String]>'
5
5
  # RDL.type :URI, :encode_www_form, '(Array<Array<String>>, ?) -> String' #Doublesplat
6
6
  # RDL.type :URI, :encode_www_form_component, '(String, ?) -> String'
7
- RDL.type :URI, :extract, '(String, ?Array) { (*%any) -> %any} -> Array<String>'
8
- RDL.type :URI, :join, '(*String) -> URI::HTTP'
9
- RDL.type :URI, :parse, '(String) -> URI::HTTP'
10
- RDL.type :URI, :regexp, '(?Array schemes) -> Array<String>' #Assume schemes are strings
11
- RDL.type :URI, :scheme_list, '() -> Hash<String,Class>'
12
- RDL.type :URI, :split, '(String) -> Array<String or nil>'
7
+ RDL.type :URI, :'self.extract', '(String, ?Array) { (*%any) -> %any} -> Array<String>'
8
+ RDL.type :URI, :'self.join', '(*String) -> URI::HTTP'
9
+ RDL.type :URI, :'self.parse', '(String) -> URI::HTTP'
10
+ RDL.type :URI, :'self.regexp', '(?Array schemes) -> Array<String>' #Assume schemes are strings
11
+ RDL.type :URI, :'self.scheme_list', '() -> Hash<String,Class>'
12
+ RDL.type :URI, :'self.split', '(String) -> Array<String or nil>'
13
13
 
14
- RDL.type :URI, :escape, '(String, *Regexp) -> String'
15
- RDL.type :URI, :escape, '(String, *String) -> String'
16
- RDL.type :URI, :unescape, '(*String) -> String'
17
- RDL.rdl_alias :URI, :encode, :escape
18
- RDL.rdl_alias :URI, :decode, :unescape
14
+ RDL.type :URI, :'self.escape', '(String, *Regexp) -> String'
15
+ RDL.type :URI, :'self.escape', '(String, *String) -> String'
16
+ RDL.type :URI, :'self.unescape', '(*String) -> String'
17
+ RDL.rdl_alias :URI, :'self.encode', :'self.escape'
18
+ RDL.rdl_alias :URI, :'self.decode', :'self.unescape'
@@ -12,7 +12,7 @@ class RDL::Rails
12
12
  when :string, :text, :binary
13
13
  return 'String'
14
14
  when :integer
15
- return 'Fixnum'
15
+ return 'Integer'
16
16
  when :float
17
17
  return 'Float'
18
18
  when :decimal
@@ -25,6 +25,12 @@ class RDL::Rails
25
25
  return 'Time'
26
26
  when :datetime
27
27
  return 'DateTime'
28
+ when :json
29
+ return 'Json'
30
+ when :inet
31
+ return 'Inet'
32
+ when :tsvector
33
+ return 'tsvector'
28
34
  else
29
35
  raise RuntimeError, "Unrecoganized column type #{rails_type}"
30
36
  end
@@ -1,3 +1,5 @@
1
+ RDL.nowrap :'ActionController::MimeResponds'
2
+
1
3
  RDL.type :'ActionController::MimeResponds', :respond_to,
2
4
  '(*(String or Symbol)) { (ActionController::MimeResponds::Collector) -> %any } -> Array<String> or String'
3
5
 
@@ -1,4 +1,5 @@
1
1
  RDL.nowrap :'ActiveRecord::Associations::CollectionProxy'
2
+ RDL.nowrap :'ActiveRecord::Associations::ClassMethods'
2
3
 
3
4
  RDL.type_params :'ActiveRecord::Associations::CollectionProxy', [:t], :all?
4
5
 
@@ -60,6 +61,28 @@ RDL.type :'ActiveRecord::Associations::CollectionProxy', :second_to_last, '(Inte
60
61
  RDL.type :'ActiveRecord::Associations::CollectionProxy', :last, '() -> t or nil'
61
62
  RDL.type :'ActiveRecord::Associations::CollectionProxy', :last, '(Integer) -> ActiveRecord::Associations::CollectionProxy<t>'
62
63
 
64
+ RDL.type :'ActiveRecord::Associations::CollectionProxy', :where, '(String, *%any) -> ActiveRecord::Associations::CollectionProxy<t>'
65
+ RDL.type :'ActiveRecord::Associations::CollectionProxy', :where, '(**%any) -> ActiveRecord::Associations::CollectionProxy<t>'
66
+ RDL.type :'ActiveRecord::Associations::CollectionProxy', :group, '(Symbol) -> ActiveRecord::Associations::CollectionProxy<t>'
67
+ RDL.type :'ActiveRecord::Associations::CollectionProxy', :order, '(Symbol) -> ActiveRecord::Associations::CollectionProxy<t>'
68
+
69
+
70
+ # Remaining methods are from CollectionProxy
71
+ # TODO give these precise types for this particular model
72
+ # collection<<(object, ...)
73
+ # collection.delete(object, ...)
74
+ # collection.destroy(object, ...)
75
+ # collection.clear
76
+ # collection.empty?
77
+ # collection.size
78
+ # collection.find(...)
79
+ # collection.where(...)
80
+ # collection.exists?(...)
81
+ # collection.build(attributes = {})
82
+ # collection.create(attributes = {})
83
+ # collection.create!(attributes = {})
84
+
85
+
63
86
  module ActiveRecord::Associations::ClassMethods
64
87
 
65
88
  # TODO: Check presence of methods required by, e.g., foreign_key, primary_key, etc.
@@ -118,8 +141,6 @@ module ActiveRecord::Associations::ClassMethods
118
141
  assoc_type = '%any' # type is data-driven, can't determine statically
119
142
  elsif class_name
120
143
  assoc_type = class_name.to_s.classify
121
- elsif anonymous_class # not sure this has anonymou_class
122
- assoc_type = anonymous_class.to_s.classify
123
144
  else
124
145
  assoc_type = name.to_s.classify # camelize?
125
146
  end
@@ -138,6 +159,7 @@ module ActiveRecord::Associations::ClassMethods
138
159
  rdl_type :'ActiveRecord::Associations::ClassMethods', :has_many,
139
160
  '(%symstr name, ?{ (?ActiveRecord::Base) -> %any } scope, class_name: ?%symstr,' +
140
161
  'foreign_key: ?%symstr, foreign_type: ?%symstr, primary_key: ?%symstr,' +
162
+ 'join_table: ?%symstr,'+
141
163
  'dependent: ?(:destroy or :delete_all or :nullify or :restrict_with_exception or :restrict_with_error),' +
142
164
  'counter_cache: ?(%bool or %symstr), as: ?%symstr, through: ?%symstr, source: ?%symstr,' +
143
165
  'source_type: ?%symstr, validate: ?%bool, inverse_of: ?%symstr, extend: ?(Module or Array<Module>))' +
@@ -146,7 +168,7 @@ module ActiveRecord::Associations::ClassMethods
146
168
  rdl_pre :'ActiveRecord::Associations::ClassMethods', :has_many do
147
169
  |name, scope=nil, class_name: nil, foreign_key: nil, foreign_type: nil, primary_key: nil,
148
170
  dependent: nil, counter_cache: nil, as: nil, through: nil, source: nil, source_type: nil,
149
- validate: nil, inverse_of: nil, extend: nil|
171
+ validate: nil, inverse_of: nil, extend: nil, join_table: nil|
150
172
 
151
173
  if class_name
152
174
  collect_type = class_name.to_s.classify
@@ -155,12 +177,14 @@ module ActiveRecord::Associations::ClassMethods
155
177
  end
156
178
  rdl_type name, "() -> ActiveRecord::Associations::CollectionProxy<#{collect_type}>"
157
179
  rdl_type "#{name}=", "(Array<t>) -> ActiveRecord::Associations::CollectionProxy<#{collect_type}>" # TODO not sure of type
158
- RDL.at(:model) {
159
- # primary_key is not available when has_many is first called!
160
- id_type = RDL::Rails.column_to_rdl(collect_type.constantize.columns_hash[primary_key].type)
161
- rdl_type "#{name.to_s.singularize}_ids", "() -> Array<#{id_type}>"
162
- rdl_type "#{name.to_s.singularize}_ids=", "() -> Array<#{id_type}>"
163
- }
180
+ if primary_key # not every model has a primary key
181
+ RDL.at(:model) {
182
+ # primary_key is not available when has_many is first called!
183
+ id_type = RDL::Rails.column_to_rdl(collect_type.constantize.columns_hash[primary_key].type)
184
+ rdl_type "#{name.to_s.singularize}_ids", "() -> Array<#{id_type}>"
185
+ rdl_type "#{name.to_s.singularize}_ids=", "() -> Array<#{id_type}>"
186
+ }
187
+ end
164
188
  true
165
189
  end
166
190
 
@@ -181,27 +205,15 @@ module ActiveRecord::Associations::ClassMethods
181
205
  end
182
206
  rdl_type name, "() -> ActiveRecord::Associations::CollectionProxy<#{collect_type}>"
183
207
  rdl_type "#{name}=", "(Array<t>) -> ActiveRecord::Associations::CollectionProxy<#{collect_type}>" # TODO not sure of type
184
- RDL.at(:model) {
185
- # primary_key is not available when has_and_belongs_to_many is first called!
186
- id_type = RDL::Rails.column_to_rdl(collect_type.constantize.columns_hash[primary_key].type)
187
- rdl_type "#{name.to_s.singularize}_ids", "() -> Array<#{id_type}>"
188
- rdl_type "#{name.to_s.singularize}_ids=", "() -> Array<#{id_type}>"
189
- }
190
-
191
- # Remaining methods are from CollectionProxy
192
- # TODO give these precise types for this particular model
193
- # collection<<(object, ...)
194
- # collection.delete(object, ...)
195
- # collection.destroy(object, ...)
196
- # collection.clear
197
- # collection.empty?
198
- # collection.size
199
- # collection.find(...)
200
- # collection.where(...)
201
- # collection.exists?(...)
202
- # collection.build(attributes = {})
203
- # collection.create(attributes = {})
204
- # collection.create!(attributes = {})
208
+ if primary_key # not every model has a primary key
209
+ RDL.at(:model) {
210
+ # primary_key is not available when has_and_belongs_to_many is first called!
211
+ id_type = RDL::Rails.column_to_rdl(collect_type.constantize.columns_hash[primary_key].type)
212
+ rdl_type "#{name.to_s.singularize}_ids", "() -> Array<#{id_type}>"
213
+ rdl_type "#{name.to_s.singularize}_ids=", "() -> Array<#{id_type}>"
214
+ }
215
+ end
216
+
205
217
  true
206
218
  end
207
219
 
@@ -0,0 +1,637 @@
1
+ require_relative "#{File.dirname(__FILE__)}/sql-strings.rb"
2
+
3
+ class ActiveRecord::Base
4
+ extend RDL::Annotate
5
+
6
+ type Object, :try, "(Symbol) -> Object", wrap: false
7
+ type Object, :present?, "() -> %bool", wrap: false
8
+ type :initialize, '(``DBType.rec_to_schema_type(trec, true)``) -> self', wrap: false
9
+ type 'self.create', '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false
10
+ type 'self.create!', '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false
11
+ type :initialize, '() -> self', wrap: false
12
+ type 'self.create', '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
13
+ type 'self.create!', '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
14
+ type :attribute_names, "() -> Array<String>", wrap: false
15
+ type :to_json, "(?{ only: Array<String> }) -> String", wrap: false
16
+ type :update_column, '(``uc_first_arg(trec)``, ``uc_second_arg(trec, targs)``) -> %bool', wrap: false
17
+ type :[], '(Symbol) -> ``access_output(trec, targs)``', wrap: false
18
+ type :save!, '(?{ validate: %bool }) -> %bool', wrap: false
19
+ type 'self.transaction', '() {() -> u} -> u', wrap: false
20
+
21
+ type RDL::Globals, 'self.ar_db_schema', "() -> Hash<%any, RDL::Type::GenericType>", wrap: false, effect: [:+, :+]
22
+ type String, :singularize, "() -> String", wrap: false, effect: [:+, :+]
23
+ type String, :camelize, "() -> String", wrap: false, effect: [:+, :+]
24
+ type String, :pluralize, "() -> String", wrap: false, effect: [:+, :+]
25
+ type String, :underscore, "() -> String", wrap: false, effect: [:+, :+]
26
+
27
+ def self.access_output(trec, targs)
28
+ case trec
29
+ when RDL::Type::NominalType
30
+ tname = trec.name.to_sym
31
+ tschema = RDL.type_cast(RDL::Globals.ar_db_schema[tname].params[0], "RDL::Type::FiniteHashType", force: true).elts
32
+ raise "Schema not found." unless tschema
33
+ arg = targs[0]
34
+ case arg
35
+ when RDL::Type::SingletonType
36
+ col = arg.val
37
+ ret = tschema[col]
38
+ ret = RDL::Globals.types[:nil] unless ret
39
+ return ret
40
+ else
41
+ raise "TODO"
42
+ end
43
+ else
44
+ raise 'unexpected type'
45
+ end
46
+ end
47
+ RDL.type ActiveRecord::Base, 'self.access_output', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
48
+
49
+ def self.uc_first_arg(trec)
50
+ case trec
51
+ when RDL::Type::NominalType
52
+ tname = trec.name.to_sym
53
+ tschema = RDL.type_cast(RDL::Globals.ar_db_schema[tname].params[0], "RDL::Type::FiniteHashType", force: true).elts
54
+ raise "Schema not found." unless tschema
55
+ typs = RDL.type_cast(tschema.keys, "Array<Symbol>", force: true).reject { |k| k == :__associations}.map { |k| RDL::Type::SingletonType.new(k) }
56
+ return RDL::Type::UnionType.new(*RDL.type_cast(typs, "Array<RDL::Type::Type>"))
57
+ else
58
+ raise "unexpected type"
59
+ end
60
+ end
61
+ RDL.type ActiveRecord::Base, 'self.uc_first_arg', "(RDL::Type::Type) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+]
62
+
63
+ def self.uc_second_arg(trec, targs)
64
+ case trec
65
+ when RDL::Type::NominalType
66
+ tname = trec.name.to_sym
67
+ tschema = RDL.type_cast(RDL::Globals.ar_db_schema[tname].params[0], "RDL::Type::FiniteHashType", force: true).elts
68
+ raise "Schema not found." unless tschema
69
+ raise "Unexpected first arg type." unless targs[0].is_a?(RDL::Type::SingletonType) && RDL.type_cast(targs[0], "RDL::Type::SingletonType<Object>").val.is_a?(Symbol)
70
+ return tschema[RDL.type_cast(targs[0], "RDL::Type::SingletonType<Symbol>").val]
71
+ else
72
+ raise "unexpected type"
73
+ end
74
+ end
75
+ RDL.type ActiveRecord::Base, 'self.uc_second_arg', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
76
+
77
+
78
+ end
79
+
80
+ module ActiveRecord::AutosaveAssociation
81
+ extend RDL::Annotate
82
+ type :reload, "() -> %any", wrap: false
83
+ end
84
+
85
+ module ActiveRecord::Transactions
86
+ extend RDL::Annotate
87
+ type :destroy, '() -> self', wrap: false
88
+ type :save, '(?{ validate: %bool }) -> %bool', wrap: false
89
+ end
90
+
91
+ module ActiveRecord::Suppressor
92
+ extend RDL::Annotate
93
+
94
+ type :save!, '() -> %bool', wrap: false
95
+ end
96
+
97
+ module ActiveRecord::Core::ClassMethods
98
+ extend RDL::Annotate
99
+ ## Types from this module are used when receiver is ActiveRecord::Base
100
+
101
+ type :find, '(Integer or String) -> ``DBType.find_output_type(trec, targs)``', wrap: false
102
+ type :find, '(Array<Integer>) -> ``DBType.find_output_type(trec, targs)``', wrap: false
103
+ type :find, '(Integer, Integer, *Integer) -> ``DBType.find_output_type(trec, targs)``', wrap: false
104
+ type :find_by, '(``DBType.find_input_type(trec, targs)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false
105
+ ## TODO: find_by's with conditions given as string
106
+ end
107
+
108
+ module ActiveRecord::FinderMethods
109
+ extend RDL::Annotate
110
+ ## Types from this module are used when receiver is ActiveRecord_Relation
111
+
112
+ type :find, '(Integer or String) -> ``DBType.find_output_type(trec, targs)``', wrap: false
113
+ type :find, '(Array<Integer>) -> ``DBType.find_output_type(trec, targs)``', wrap: false
114
+ type :find, '(Integer, Integer, *Integer) -> ``DBType.find_output_type(trec, targs)``', wrap: false
115
+ type :find_by, '(``DBType.find_input_type(trec, targs)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false
116
+ type :first, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
117
+ type :first!, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
118
+ type :first, '(Integer) -> ``DBType.rec_to_array(trec)``', wrap: false
119
+ type :last, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
120
+ type :last!, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
121
+ type :last, '(Integer) -> ``DBType.rec_to_array(trec)``', wrap: false
122
+ type :take, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
123
+ type :take!, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
124
+ type :take, '(Integer) -> ``DBType.rec_to_array(trec)``', wrap: false
125
+ type :exists?, '(``DBType.exists_input_type(trec, targs)``) -> %bool', wrap: false
126
+ end
127
+
128
+ module ActiveRecord::Querying
129
+ extend RDL::Annotate
130
+ ## Types from this module are used when receiver is ActiveRecord::Base
131
+
132
+ type :first, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
133
+ type :first!, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
134
+ type :first, '(Integer) -> ``DBType.rec_to_array(trec)``', wrap: false
135
+ type :last, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
136
+ type :last!, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
137
+ type :last, '(Integer) -> ``DBType.rec_to_array(trec)``', wrap: false
138
+ type :take, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
139
+ type :take!, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
140
+ type :take, '(Integer) -> ``DBType.rec_to_array(trec)``', wrap: false
141
+ type :exists?, '(``DBType.exists_input_type(trec, targs)``) -> %bool', wrap: false
142
+
143
+ type :where, '(``DBType.where_input_type(trec, targs)``) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
144
+ type :where, '(String, *%any) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
145
+ type :where, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord::QueryMethods::WhereChain), DBType.rec_to_nominal(trec))``', wrap: false
146
+
147
+
148
+ type :joins, '(``DBType.joins_one_input_type(trec, targs)``) -> ``DBType.joins_output(trec, targs)``', wrap: false
149
+ type :joins, '(``DBType.joins_multi_input_type(trec, targs)``, %any, *%any) -> ``DBType.joins_output(trec, targs)``', wrap: false
150
+ type :group, '(*Symbol or String) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
151
+ type :select, '(Symbol or String or Array<String>, *Symbol or String or Array<String>) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
152
+ type :select, '() { (self) -> %bool } -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
153
+ type :order, '(%any) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
154
+ type :includes, '(``DBType.joins_one_input_type(trec, targs)``) -> ``DBType.joins_output(trec, targs)``', wrap: false
155
+ type :includes, '(``DBType.joins_multi_input_type(trec, targs)``, %any, *%any) -> ``DBType.joins_output(trec, targs)``', wrap: false
156
+ type :limit, '(Integer) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
157
+ type :count, '() -> Integer', wrap: false
158
+ type :count, '(``DBType.count_input(trec, targs)``) -> Integer', wrap: false
159
+ type :sum, '(``DBType.count_input(trec, targs)``) -> Integer', wrap: false
160
+ type :destroy_all, '() -> ``DBType.rec_to_array(trec)``', wrap: false
161
+ type :delete_all, '() -> Integer', wrap: false
162
+ type :references, '(Symbol, *Symbol) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
163
+ end
164
+
165
+ module ActiveRecord::Relation::QueryMethods
166
+ extend RDL::Annotate
167
+ ## Types from this module are used when receiver is ActiveRecord_relation
168
+
169
+ type :where, '(``DBType.where_input_type(trec, targs)``) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), RDL.type_cast(trec, "RDL::Type::GenericType", force: true).params[0])``', wrap: false
170
+ type :where, '(String, *%any) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), RDL.type_cast(trec, "RDL::Type::GenericType", force: true).params[0])``', wrap: false
171
+ #type :where, '(String, *String) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), trec.params[0])``', wrap: false
172
+ type :where, '() -> ``DBType.where_noarg_output_type(trec)``', wrap: false
173
+
174
+ type :joins, '(``DBType.joins_one_input_type(trec, targs)``) -> ``DBType.joins_output(trec, targs)``', wrap: false
175
+ type :joins, '(``DBType.joins_multi_input_type(trec, targs)``, %any, *%any) -> ``DBType.joins_output(trec, targs)``', wrap: false
176
+ type :group, '(*Symbol or String) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), RDL.type_cast(trec, "RDL::Type::GenericType", force: true).params[0])``', wrap: false
177
+ type :select, '(Symbol or String or Array<String>, *Symbol or String or Array<String>) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), RDL.type_cast(trec, "RDL::Type::GenericType", force: true).params[0])``', wrap: false
178
+ type :select, '() { (``RDL.type_cast(trec, "RDL::Type::GenericType", force: true).params[0]``) -> %bool } -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), RDL.type_cast(trec, "RDL::Type::GenericType", force: true).params[0])``', wrap: false
179
+ type :order, '(%any) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), RDL.type_cast(trec, "RDL::Type::GenericType", force: true).params[0])``', wrap: false
180
+ type :includes, '(``DBType.joins_one_input_type(trec, targs)``) -> ``DBType.joins_output(trec, targs)``', wrap: false
181
+ type :includes, '(``DBType.joins_multi_input_type(trec, targs)``, %any, *%any) -> ``DBType.joins_output(trec, targs)``', wrap: false
182
+ type :limit, '(Integer) -> ``trec``', wrap: false
183
+ type :references, '(Symbol, *Symbol) -> self', wrap: false
184
+ end
185
+
186
+
187
+ class ActiveRecord::QueryMethods::WhereChain
188
+ extend RDL::Annotate
189
+ type_params [:t], :dummy
190
+
191
+ type :not, '(``DBType.not_input_type(trec, targs)``) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), RDL.type_cast(trec, "RDL::Type::GenericType", force: true).params[0])``', wrap: false
192
+
193
+ end
194
+
195
+ module ActiveRecord::Delegation
196
+ extend RDL::Annotate
197
+
198
+ type :+, '(%any) -> ``DBType.plus_output_type(trec, targs)``', wrap: false
199
+
200
+ end
201
+
202
+ class JoinTable
203
+ extend RDL::Annotate
204
+ type_params [:orig, :joined], :dummy
205
+ ## type param :orig will be nominal type of base table in join
206
+ ## type param :joined will be a union type of all joined tables, or just a nominal type if there's only one
207
+
208
+ ## this class is meant to only be the type parameter of ActiveRecord_Relation or WhereChain, expressing multiple joined tables instead of just a single table
209
+ end
210
+
211
+
212
+
213
+ module ActiveRecord::Scoping::Named::ClassMethods
214
+ extend RDL::Annotate
215
+ type :all, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false
216
+
217
+ end
218
+
219
+ module ActiveRecord::Persistence
220
+ extend RDL::Annotate
221
+ type :update!, '(``DBType.rec_to_schema_type(trec, true)``) -> %bool', wrap: false
222
+ type :update, '(``DBType.rec_to_schema_type(trec, true)``) -> %bool', wrap: false
223
+ type :update_attribute, '(Symbol, ``DBType.update_attribute_input(trec, targs)``) -> %bool', wrap: false
224
+ end
225
+
226
+ module ActiveRecord::Calculations
227
+ extend RDL::Annotate
228
+ type :count, '() -> Integer', wrap: false
229
+ type :count, '(``DBType.count_input(trec, targs)``) -> Integer', wrap: false
230
+ type :sum, '(``DBType.count_input(trec, targs)``) -> Integer', wrap: false
231
+ end
232
+
233
+ class ActiveRecord_Relation
234
+ ## In practice, this is actually a private class nested within
235
+ ## each ActiveRecord::Base, e.g. Person::ActiveRecord_Relation.
236
+ ## Using this class just for type checking.
237
+ extend RDL::Annotate
238
+ include ActiveRecord::Relation::QueryMethods
239
+ include ActiveRecord::FinderMethods
240
+ include ActiveRecord::Calculations
241
+ include ActiveRecord::Delegation
242
+
243
+ type_params [:t], :dummy
244
+
245
+ type :each, '() -> Enumerator<t>', wrap: false
246
+ type :each, '() { (t) -> %any } -> Array<t>', wrap: false
247
+ type :empty?, '() -> %bool', wrap: false
248
+ type :present?, '() -> %bool', wrap: false
249
+ type :create, '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false
250
+ type :create, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
251
+ type :create!, '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false
252
+ type :create!, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
253
+ type :new, '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false
254
+ type :new, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
255
+ type :build, '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false
256
+ type :build, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false
257
+ type :destroy_all, '() -> ``DBType.rec_to_array(trec)``', wrap: false
258
+ type :delete_all, '() -> Integer', wrap: false
259
+ type :map, '() { (t) -> u } -> Array<u>'
260
+ type :all, '() -> self', wrap: false ### kind of a silly method, always just returns self
261
+ type :collect, "() { (t) -> u } -> Array<u>", wrap: false
262
+ type :find_each, "() { (t) -> x } -> nil", wrap: false
263
+ type :to_a, "() -> ``DBType.rec_to_array(trec)``", wrap: false
264
+ type :[], "(Integer) -> t", wrap: false
265
+ type :size, "() -> Integer", wrap: false
266
+ type :update_all, '(``DBType.rec_to_schema_type(trec, true)``) -> Integer', wrap: false
267
+ end
268
+
269
+
270
+ class DBType
271
+ ## given a type (usually representing a receiver type in a method call), this method returns the nominal type version of that type.
272
+ ## if the given type represents a joined table, then we return the nominal type version of the *base* of the joined table.
273
+ ## [+ t +] is the type for which we want the nominal type.
274
+ def self.rec_to_nominal(t)
275
+ case t
276
+ when RDL::Type::SingletonType
277
+ val = RDL.type_cast(t.val, "Class", force: true)
278
+ raise RDL::Typecheck::StaticTypeError, "Expected class singleton type, got #{val} instead." unless val.is_a?(Class)
279
+ return RDL::Type::NominalType.new(val)
280
+ when RDL::Type::GenericType
281
+ raise RDL::Typecheck::StaticTypeError, "got unexpected type #{t}" unless t.base.klass == ActiveRecord_Relation
282
+ param = t.params[0]
283
+ case param
284
+ when RDL::Type::GenericType
285
+ ## should be JoinTable
286
+ ## When getting an indivual record from a join table, record will be of type of the base table in the join
287
+ raise RDL::Typecheck::StaticTypeError, "got unexpected type #{param}" unless param.base.klass == JoinTable
288
+ return param.params[0]
289
+ when RDL::Type::NominalType
290
+ return param
291
+ else
292
+ raise RDL::Typecheck::StaticTypeError, "got unexpected type #{t.params[0]}"
293
+ end
294
+ end
295
+ end
296
+ RDL.type DBType, 'self.rec_to_nominal', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
297
+
298
+ def self.rec_to_array(trec)
299
+ RDL::Type::GenericType.new(RDL::Globals.types[:array], rec_to_nominal(trec))
300
+ end
301
+ RDL.type DBType, 'self.rec_to_array', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+]
302
+
303
+ ## given a receiver type in various kinds of query calls, returns the accepted finite hash type input,
304
+ ## or a union of types if the receiver represents joined tables.
305
+ ## [+ trec +] is the type of the receiver in the method call.
306
+ ## [+ check_col +] is a boolean indicating whether or not the column types (i.e., values in the finite hash type) will be checked.
307
+ def self.rec_to_schema_type(trec, check_col, takes_array=false)
308
+ case trec
309
+ when RDL::Type::GenericType
310
+ raise "Unexpected type #{trec}." unless (trec.base.klass == ActiveRecord_Relation) || (trec.base.klass == ActiveRecord::QueryMethods::WhereChain)
311
+ param = trec.params[0]
312
+ case param
313
+ when RDL::Type::GenericType
314
+ ## should be JoinTable
315
+ raise "unexpected type #{trec}" unless param.base.klass == JoinTable
316
+ base_name = RDL.type_cast(param.params[0], "RDL::Type::NominalType", force: true).klass.to_s.singularize.to_sym ### singularized symbol name of first param in JoinTable, which is base table of the joins
317
+ type_hash = table_name_to_schema_type(base_name, check_col, takes_array).elts
318
+ pp1 = param.params[1]
319
+ case pp1
320
+ when RDL::Type::NominalType
321
+ ## just one table joined to base table
322
+ joined_name = pp1.klass.to_s.singularize.to_sym
323
+ joined_type = RDL::Type::OptionalType.new(table_name_to_schema_type(joined_name, check_col, takes_array))
324
+ type_hash = type_hash.merge({ joined_name.to_s.pluralize.underscore.to_sym => joined_type })
325
+ when RDL::Type::UnionType
326
+ ## multiple tables joined to base table
327
+ joined_hash = RDL.type_cast(Hash[pp1.types.map { |t|
328
+ joined_name = RDL.type_cast(t, "RDL::Type::NominalType", force: true).klass.to_s.singularize.to_sym
329
+ joined_type = table_name_to_schema_type(joined_name, check_col, takes_array)
330
+ [joined_name.to_s.pluralize.underscore.to_sym, joined_type]
331
+ }
332
+ ], "Hash<Symbol, RDL::Type::FiniteHashType>", force: true)
333
+ else
334
+ raise "unexpected type #{trec}"
335
+ end
336
+ return RDL::Type::FiniteHashType.new(type_hash, nil)
337
+ when RDL::Type::NominalType
338
+ tname = param.klass.to_s.to_sym
339
+ return table_name_to_schema_type(tname, check_col, takes_array)
340
+ else
341
+ raise RDL::Typecheck::StaticTypeError, "Unexpected type parameter in #{trec}."
342
+ end
343
+ when RDL::Type::SingletonType
344
+ val = RDL.type_cast(trec.val, 'Class', force: true)
345
+ raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}." unless val.is_a?(Class)
346
+ tname = val.to_s.to_sym
347
+ return table_name_to_schema_type(tname, check_col, takes_array)
348
+ when RDL::Type::NominalType
349
+ tname = trec.name.to_sym
350
+ return table_name_to_schema_type(tname, check_col, takes_array)
351
+ else
352
+ raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}."
353
+ end
354
+ end
355
+ RDL.type DBType, 'self.rec_to_schema_type', "(RDL::Type::Type, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code, effect: [:+, :+]
356
+
357
+ ## turns a given table name into the appropriate finite hash type based on table schema, with optional or top-type values
358
+ ## [+ tname +] is the table name as a symbol
359
+ ## [+ check_col +] is a boolean indicating whether or not column types will eventually be checked
360
+ def self.table_name_to_schema_type(tname, check_col, takes_array=false)
361
+ #h = RDL.type_cast({}, "Hash<%any, RDL::Type::Type>", force: true)
362
+ ttype = RDL::Globals.ar_db_schema[tname]
363
+ raise RDL::Typecheck::StaticTypeError, "No table type for #{tname} found." unless ttype
364
+ tschema = RDL.type_cast(ttype.params[0], "RDL::Type::FiniteHashType", force: true).elts.except(:__associations)
365
+ h = Hash[tschema.map { |k, v|
366
+ if check_col
367
+ v = RDL::Type::UnionType.new(v, RDL::Type::GenericType.new(RDL::Globals.types[:array], v)) if takes_array
368
+ [k, RDL::Type::OptionalType.new(v)]
369
+ else
370
+ [k, RDL::Type::OptionalType.new(RDL::Globals.types[:top])]
371
+ end
372
+ }]
373
+ RDL::Type::FiniteHashType.new(RDL.type_cast(h, "Hash<%any, RDL::Type::Type>", force: true), nil)
374
+ end
375
+ RDL.type DBType, 'self.table_name_to_schema_type', "(Symbol, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code, effect: [:+, :+]
376
+
377
+ def self.where_input_type(trec, targs)
378
+ handle_sql_strings(trec, targs) if targs[0].is_a? RDL::Type::PreciseStringType
379
+ tschema = rec_to_schema_type(trec, true, true)
380
+ return RDL::Type::UnionType.new(tschema, RDL::Globals.types[:string], RDL::Globals.types[:array]) ## no indepth checking for string or array cases
381
+ end
382
+ RDL.type Object, 'self.handle_sql_strings', "(RDL::Type::Type, Array<RDL::Type::Type>) -> %any", wrap: false, effect: [:+, :+]
383
+ RDL.type DBType, 'self.where_input_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+]
384
+
385
+ def self.find_input_type(trec, targs)
386
+ handle_sql_strings(trec, targs) if targs[0].is_a? RDL::Type::PreciseStringType
387
+ rec_to_schema_type(trec, true)
388
+ end
389
+
390
+ def self.update_attribute_input(trec, targs)
391
+ col = targs[0].val
392
+ col_type = targs[1]
393
+ schema = DBType.rec_to_schema_type(trec, true)
394
+ schema.elts[col]
395
+ end
396
+
397
+ def self.where_noarg_output_type(trec)
398
+ case trec
399
+ when RDL::Type::SingletonType
400
+ ## where called directly on class
401
+ RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord::QueryMethods::WhereChain), rec_to_nominal(trec))
402
+ when RDL::Type::GenericType
403
+ ## where called on ActiveRecord_Relation
404
+ raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}." unless trec.base.klass == ActiveRecord_Relation
405
+ return RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord::QueryMethods::WhereChain), trec.params[0])
406
+ else
407
+ raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}."
408
+ end
409
+ end
410
+ RDL.type DBType, 'self.where_noarg_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+]
411
+
412
+ def self.not_input_type(trec, targs)
413
+ tschema = rec_to_schema_type(trec, true)
414
+ return RDL::Type::UnionType.new(tschema, RDL::Globals.types[:string], RDL::Globals.types[:array]) ## no indepth checking for string or array cases
415
+ end
416
+ RDL.type DBType, 'self.not_input_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+]
417
+
418
+ def self.exists_input_type(trec, targs)
419
+ raise "Unexpected number of arguments to ActiveRecord::Base#exists?." unless targs.size <= 1
420
+ case targs[0]
421
+ when RDL::Type::FiniteHashType
422
+ typ = rec_to_schema_type(trec, false)
423
+ else
424
+ ## any type can be accepted, only thing we're intersted in is when a hash is given
425
+ ## TODO: what if we get a nominal Hash type?
426
+ typ = targs[0]
427
+ end
428
+ return RDL::Type::OptionalType.new(RDL::Type::UnionType.new(RDL::Globals.types[:integer], RDL::Globals.types[:string], typ))
429
+ end
430
+ RDL.type DBType, 'self.exists_input_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
431
+
432
+
433
+ def self.find_output_type(trec, targs)
434
+ case targs.size
435
+ when 0
436
+ raise RDL::Typecheck::StaticTypeError, "No arguments given to ActiveRecord::Base#find."
437
+ when 1
438
+ arg0 = targs[0]
439
+ case arg0
440
+ when RDL::Globals.types[:integer], RDL::Globals.types[:string]
441
+ DBType.rec_to_nominal(trec)
442
+ when RDL::Type::SingletonType
443
+ # expecting symbol or integer here
444
+ case arg0.val
445
+ when Integer
446
+ DBType.rec_to_nominal(trec)
447
+ when Symbol
448
+ ## TODO
449
+ ## Actually, this is deprecated in later versions
450
+ raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to ActiveRecord::Base#find."
451
+ else
452
+ raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to ActiveRecord::Base#find."
453
+ end
454
+ when RDL::Type::GenericType
455
+ RDL::Type::GenericType.new(RDL::Globals.types[:array], DBType.rec_to_nominal(trec))
456
+ when RDL::Type::TupleType
457
+ RDL::Type::GenericType.new(RDL::Globals.types[:array], DBType.rec_to_nominal(trec))
458
+ else
459
+ raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to ActiveRecord::Base#find."
460
+ end
461
+ else
462
+ DBType.rec_to_nominal(trec)
463
+ end
464
+ end
465
+ RDL.type DBType, 'self.find_output_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
466
+
467
+ def self.joins_one_input_type(trec, targs)
468
+ return RDL::Globals.types[:top] unless targs.size == 1 ## trivial case, won't be matched
469
+ case trec
470
+ when RDL::Type::SingletonType
471
+ base_klass = RDL.type_cast(trec, "RDL::Type::SingletonType<Symbol>").val
472
+ when RDL::Type::GenericType
473
+ raise "Unexpected type #{trec}." unless (RDL.type_cast(trec, "RDL::Type::GenericType").base.klass == ActiveRecord_Relation)
474
+ param = RDL.type_cast(trec, "RDL::Type::GenericType").params[0]
475
+ case param
476
+ when RDL::Type::GenericType
477
+ raise "Unexpected type #{trec}." unless (param.base.klass == JoinTable)
478
+ base_klass = RDL.type_cast(param.params[0], "RDL::Type::NominalType", force: true).klass
479
+ when RDL::Type::NominalType
480
+ base_klass = param.klass
481
+ else
482
+ raise "unexpected parameter type in #{trec}"
483
+ end
484
+ else
485
+ raise "unexpected receiver type #{trec}"
486
+ end
487
+ arg0 = targs[0]
488
+ case arg0
489
+ when RDL::Type::SingletonType
490
+ sym = RDL.type_cast(arg0, "RDL::Type::SingletonType<Symbol>").val
491
+ raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{trec} in call to joins." unless sym.is_a?(Symbol)
492
+ raise RDL::Typecheck::StaticTypeError, "#{trec} has no association to #{arg0}, cannot perform joins." unless associated_with?(RDL.type_cast(base_klass, "Symbol", force: true), sym)
493
+ return arg0
494
+ when RDL::Type::FiniteHashType
495
+ RDL.type_cast(RDL.type_cast(arg0, "RDL::Type::FiniteHashType").elts, "Hash<Symbol, RDL::Type::Type>", force: true).each { |key, val|
496
+ raise RDL::Typecheck::StaticTypeError, "Unexpected hash arg type #{arg0} in call to joins." unless key.is_a?(Symbol) && val.is_a?(RDL::Type::SingletonType) && RDL.type_cast(val, "RDL::Type::SingletonType<Object>").val.is_a?(Symbol)
497
+ val_sym = RDL.type_cast(val, "RDL::Type::SingletonType<Symbol>").val
498
+ raise RDL::Typecheck::StaticTypeError, "#{trec} has no association to #{key}, cannot perform joins." unless associated_with?(RDL.type_cast(base_klass, "Symbol", force: true), key)
499
+ key_klass = key.to_s.singularize.camelize
500
+ raise RDL::Typecheck::StaticTypeError, "#{key} has no association to #{val_sym}, cannot perform joins." unless associated_with?(key_klass, val_sym)
501
+ }
502
+ return arg0
503
+ else
504
+ raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to joins."
505
+ end
506
+ end
507
+ RDL.type DBType, 'self.joins_one_input_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
508
+
509
+ def self.joins_multi_input_type(trec, targs)
510
+ return RDL::Globals.types[:top] unless targs.size > 1 ## trivial case, won't be matched
511
+ targs.each { |arg|
512
+ joins_one_input_type(trec, [arg])
513
+ }
514
+ return targs[0] ## since this method is called as first argument in type
515
+ end
516
+ RDL.type DBType, 'self.joins_multi_input_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
517
+
518
+ def self.associated_with?(rec, sym)
519
+ tschema = RDL::Globals.ar_db_schema[rec.to_s.to_sym]
520
+ raise RDL::Typecheck::StaticTypeError, "No table type for #{rec} found." unless tschema
521
+ schema = RDL.type_cast(tschema.params[0], "RDL::Type::FiniteHashType", force: true).elts
522
+ assoc = schema[:__associations]
523
+ raise RDL::Typecheck::StaticTypeError, "Table #{rec} has no associations, cannot perform joins." unless assoc
524
+ RDL.type_cast(RDL.type_cast(assoc, "RDL::Type::FiniteHashType").elts, "Hash<Symbol, RDL::Type::Type>", force: true).each { |key, value|
525
+ case value
526
+ when RDL::Type::SingletonType
527
+ return true if RDL.type_cast(value.val, "Object", force: true) == sym ## no need to change any plurality here
528
+ when RDL::Type::UnionType
529
+ ## for when rec has multiple of the same kind of association
530
+ value.types.each { |t|
531
+ raise "Unexpected type #{t}." unless t.is_a?(RDL::Type::SingletonType) && (RDL.type_cast(t, "RDL::Type::SingletonType<Object>").val.class == Symbol)
532
+ return true if RDL.type_cast(t, "RDL::Type::SingletonType<Symbol>").val == sym
533
+ }
534
+ else
535
+ raise RDL::Typecheck::StaticTypeError, "Unexpected association type #{value}"
536
+ end
537
+ }
538
+ return false
539
+ end
540
+ RDL.type DBType, 'self.associated_with?', "(Class or Symbol or String, Symbol) -> %bool", wrap: false, typecheck: :type_code, effect: [:+, :+]
541
+
542
+ def self.get_joined_args(targs)
543
+ arg_types = RDL.type_cast([], "Array<RDL::Type::Type>", force: true)
544
+ targs.each { |arg|
545
+ case arg
546
+ when RDL::Type::SingletonType
547
+ raise RDL::Typecheck::StaticTypeError, "Unexpected joins arg type #{arg}" unless (RDL.type_cast(arg.val, "Object", force: true).class == Symbol)
548
+ arg_types = arg_types + [RDL::Type::NominalType.new(RDL.type_cast(arg.val, "Symbol", force: true).to_s.singularize.camelize)]
549
+ when RDL::Type::FiniteHashType
550
+ hsh = arg.elts
551
+ raise 'not supported' unless hsh.size == 1
552
+ key, val = RDL.type_cast(hsh.first, "[Symbol, RDL::Type::SingletonType<Symbol>]", force: true)
553
+ val = val.val
554
+ arg_types = arg_types + [RDL::Type::UnionType.new(RDL::Type::NominalType.new(key.to_s.singularize.camelize), RDL::Type::NominalType.new(val.to_s.singularize.camelize))]
555
+ else
556
+ raise "Unexpected arg type #{arg} to joins."
557
+ end
558
+ }
559
+ if arg_types.size > 1
560
+ return RDL::Type::UnionType.new(*arg_types)
561
+ elsif arg_types.size == 1
562
+ return arg_types[0]
563
+ else
564
+ raise "oops, didn't expect to get here."
565
+ end
566
+ end
567
+ RDL.type DBType, 'self.get_joined_args', "(Array<RDL::Type::Type>) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
568
+
569
+ def self.joins_output(trec, targs)
570
+ arg_type = get_joined_args(targs)
571
+ case trec
572
+ when RDL::Type::SingletonType
573
+ joined = arg_type
574
+ when RDL::Type::GenericType
575
+ raise "Unexpected type #{trec}." unless (trec.base.klass == ActiveRecord_Relation)
576
+ param = trec.params[0]
577
+ case param
578
+ when RDL::Type::GenericType
579
+ raise "Unexpected type #{trec}." unless (param.base.klass == JoinTable)
580
+ joined = RDL::Type::UnionType.new(param.params[1], arg_type)
581
+ when RDL::Type::NominalType
582
+ joined = arg_type
583
+ else
584
+ raise "unexpected parameter type in #{trec}"
585
+ end
586
+ else
587
+ raise "unexpected type #{trec}"
588
+ end
589
+ jt = RDL::Type::GenericType.new(RDL::Type::NominalType.new(JoinTable), rec_to_nominal(trec), joined)
590
+ ret = RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), jt)
591
+ return ret
592
+ end
593
+ RDL.type DBType, 'self.joins_output', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+]
594
+
595
+ def self.plus_output_type(trec, targs)
596
+ typs = RDL.type_cast([], "Array<RDL::Type::Type>", force: true)
597
+ [trec, targs[0]].each { |t|
598
+ case t
599
+ when RDL::Type::GenericType
600
+ raise "Expected ActiveRecord_Relation." unless t.base.name == "ActiveRecord_Relation"
601
+ param0 = t.params[0]
602
+ case param0
603
+ when RDL::Type::GenericType
604
+ raise "Unexpected paramter type in #{t}." unless param0.base.name == "JoinTable"
605
+ typs = typs + [param0.params[0]] ## base of join table
606
+ typs = typs + [param0.params[1]] ## joined tables
607
+ when RDL::Type::NominalType
608
+ typs = typs + [param0]
609
+ else
610
+ raise "unexpected paramater type in #{t}"
611
+ end
612
+ else
613
+ raise "unexpected type #{t}"
614
+ end
615
+ }
616
+ RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), RDL::Type::UnionType.new(*typs))
617
+ end
618
+ RDL.type DBType, 'self.plus_output_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+]
619
+
620
+ def self.count_input(trec, targs)
621
+ hash_type = rec_to_schema_type(trec, true)## Bug found here. orginally had: rec_to_schema_type(trec, targs).elts
622
+ typs = RDL.type_cast([], "Array<RDL::Type::Type>", force: true)
623
+ hash_type.elts.each { |k, v| ## bug here, originally had: hash_type.each { |k, v|
624
+ if v.is_a?(RDL::Type::FiniteHashType)
625
+ ## will reach this with joined tables, but we're only interested in column names
626
+ RDL.type_cast(v, 'RDL::Type::FiniteHashType', force: true).elts.each { |k1, v1|
627
+ typs = typs + [RDL::Type::SingletonType.new(k1)] unless v1.is_a?(RDL::Type::FiniteHashType) ## potentially two dimensions in joined table
628
+ }
629
+ else
630
+ typs = typs + [RDL::Type::SingletonType.new(k)]
631
+ end
632
+ }
633
+ return RDL::Type::OptionalType.new(RDL::Type::UnionType.new(*typs))
634
+ end
635
+ RDL.type DBType, 'self.count_input', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::OptionalType", wrap: false, typecheck: :type_code, effect: [:+, :+]
636
+
637
+ end