rdl 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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