From 01e7eb95d169ca06f1a547345be9899087e7ba89 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Sun, 9 Jun 2019 20:05:48 -0400 Subject: [PATCH 001/124] bump version in gemspec --- CHANGES.md | 2 +- rdl.gemspec | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7a139ed5..740a5ec6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## [Unreleased] -## [2.2.0] - 2019-06-07 +## [2.2.0] - 2019-06-09 ### Fixed - Dynamic type checking of initialize method diff --git a/rdl.gemspec b/rdl.gemspec index 34e5fe32..a2ab9c55 100644 --- a/rdl.gemspec +++ b/rdl.gemspec @@ -4,8 +4,8 @@ Gem::Specification.new do |s| s.name = 'rdl' - s.version = '2.1.0' - s.date = '2017-06-14' + s.version = '2.2.0' + s.date = '2019-06-09' s.summary = 'Ruby type and contract system' s.description = <<-EOF RDL is a gem that adds types and contracts to Ruby. RDL includes extensive @@ -16,7 +16,7 @@ EOF s.email = ['rdl-users@googlegroups.com'] s.files = `git ls-files`.split($/) s.executables << 'rdl_query' - s.homepage = 'https://github.com/plum-umd/rdl' + s.homepage = 'https://github.com/tupl-tufts/rdl' s.license = 'BSD-3-Clause' s.add_runtime_dependency 'parser', '~>2.3', '>= 2.3.1.4' s.add_runtime_dependency 'sql-parser', '~>0.0.2' From efc994bb61c29875c98cd85b374447d140e26661 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Sun, 9 Jun 2019 20:34:35 -0400 Subject: [PATCH 002/124] web page update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3db7c3aa..1c5833e3 100644 --- a/README.md +++ b/README.md @@ -989,7 +989,7 @@ it matches the latest gem version. # Authors -* [Jeffrey S. Foster](http://www.cs.umd.edu/~jfoster/) +* [Jeffrey S. Foster](http://www.cs.tufts.edu/~jfoster/) * [Brianna M. Ren](https://www.cs.umd.edu/~bren/) * [T. Stephen Strickland](https://www.cs.umd.edu/~sstrickl/) * Alexander T. Yu From dd1b568e296d26dad557bf1609a66d6ddf3a5741 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Wed, 4 Sep 2019 15:02:15 -0400 Subject: [PATCH 003/124] inference work initial commit --- lib/rdl/boot.rb | 14 +++ lib/rdl/config.rb | 7 +- lib/rdl/constraint.rb | 128 ++++++++++++++++++++ lib/rdl/typecheck.rb | 239 +++++++++++++++++++++++++++++++------ lib/rdl/types/singleton.rb | 2 + lib/rdl/types/type.rb | 40 ++++++- lib/rdl/types/var.rb | 90 ++++++++++++-- lib/rdl/wrap.rb | 126 ++++++++++++++++++- 8 files changed, 587 insertions(+), 59 deletions(-) create mode 100644 lib/rdl/constraint.rb diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 07a426d8..f6d9ec82 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -49,6 +49,17 @@ module RDL::Globals @to_typecheck = Hash.new @to_typecheck[:now] = Set.new + # Map from symbols to set of [class, method] pairs to infer when those symbols are rdl_do_infer'd + # (or the methods are defined, for the symbol :now) + @to_infer = Hash.new + @to_infer[:now] = Set.new + + ## List of [klass, method] pairs for which we have generated constraints. + ## That is, if we look up RDL::Globals.info.get(klass, meth, :type), we will get a single MethodType + ## composed of VarTypes with constraints. + ## TODO: add inst/class vars to this list? + @constrained_types = [] + # Map from symbols to Array where the Procs are called when those symbols are rdl_do_typecheck'd @to_do_at = Hash.new @@ -75,6 +86,8 @@ class << RDL::Globals # add accessors and readers for module variables attr_reader :aliases attr_accessor :to_wrap attr_accessor :to_typecheck + attr_accessor :to_infer + attr_accessor :constrained_types attr_accessor :to_do_at attr_accessor :deferred attr_accessor :dep_types @@ -136,6 +149,7 @@ class << RDL::Globals require 'rdl/wrap.rb' require 'rdl/query.rb' require 'rdl/typecheck.rb' +require 'rdl/constraint.rb' #require_relative 'rdl/stats.rb' class << RDL::Globals diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index 5b43b78a..6f369dcf 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -7,7 +7,8 @@ class RDL::Config attr_accessor :gather_stats attr_reader :report # writer is custom defined attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types - attr_accessor :type_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type + attr_accessor :type_defaults, :infer_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type + attr_accessor :practical_infer def initialize @nowrap = Set.new # Set of symbols @@ -17,7 +18,8 @@ def initialize @at_exit_installed = false @weak_update_promote = false @promote_widen = false - @type_defaults = { wrap: true, typecheck: false } + @type_defaults = { wrap: true, typecheck: false} + @infer_defaults = { time: nil } @pre_defaults = { wrap: true } @post_defaults = { wrap: true } @assume_dyn_type = false @@ -25,6 +27,7 @@ def initialize @use_comp_types = true @check_comp_types = false ## this for dynamically checking that the result of a computed type still holds @rerun_comp_types = false ## this is for dynamically checking that a type computation still evaluates to the same thing as it did at type checking time + @practical_infer = true end def report=(val) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb new file mode 100644 index 00000000..916ed166 --- /dev/null +++ b/lib/rdl/constraint.rb @@ -0,0 +1,128 @@ +module RDL::Typecheck + + def self.resolve_constraints + puts "Starting constraint resolution..." + RDL::Globals.constrained_types.each { |klass, name| + typ = RDL::Globals.info.get(klass, name, :type) + ## If typ is an Array, then it's an array of method types + ## but for inference, we only use a single method type. + ## Otherwise, it's a single VarType for an instance/class var. + var_types = typ.is_a?(Array) ? typ[0].args + [typ[0].ret] : [typ] + + var_types.each { |var_type| + var_type.lbounds.each { |lower_t, ast| + var_type.add_and_propagate_lower_bound(lower_t, ast) + } + var_type.ubounds.each { |upper_t, ast| + var_type.add_and_propagate_upper_bound(upper_t, ast) + } + } +=begin + if typ.is_a?(Array) + ## it's an array for method types (to handle intersections) + meth_type = typ[0] ## should only be one type though since we're inferring it + raise "Expected MethodType, got #{meth_type}." unless meth_type.is_a?(RDL::Type::MethodType) + (meth_type.args + [meth_type.ret]).each { |var_type| + var_type.lbounds.each { |lower_t, ast| + var_type.add_and_propagate_lower_bound(lower_t, ast) + } + var_type.ubounds.each { |upper_t, ast| + var_type.add_and_propagate_upper_bound(upper_t, ast) + } + } + else + ## variable type in this case + var_type = typ + raise "Expected VarType, got #{var_type}." unless var_type.is_a?(RDL::Type::VarType) + var_type.lbounds.each { |lower_t, ast| + var_type.add_and_propagate_lower_bound(lower_t, ast) + } + var_type.ubounds.each { |upper_t, ast| + var_type.add_and_propagate_upper_bound(upper_t, ast) + } + end +=end + } + + if RDL::Config.instance.practical_infer + puts "practical inference!!!" + RDL::Globals.constrained_types.each { |klass, name| + typ = RDL::Globals.info.get(klass, name, :type) + var_types = typ.is_a?(Array) ? typ[0].args + [typ[0].ret] : [typ] + var_types.each { |var_type| + puts "Applying struct_to_nominal to #{var_type}." + struct_to_nominal(var_type) + } + } + end + + puts "Done with constraint resolution." + end + + def self.struct_to_nominal(var_type) + return unless var_type.category == :arg ## this rule only applies to args + return unless var_type.ubounds.all? { |t, loc| t.is_a?(RDL::Type::StructuralType) || t.is_a?(RDL::Type::VarType) } ## all upper bounds must be struct types or var types + struct_types = var_type.ubounds.select { |t, loc| t.is_a?(RDL::Type::StructuralType) } + struct_types.map! { |t, loc| t } + return if struct_types.empty? + + meth_names = struct_types.map { |st| st.methods.keys }.flatten + matching_classes = ObjectSpace.each_object(Class).select { |c| (meth_names - c.instance_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods + + ## TODO: special handling for arrays/hashes/generics? + ## TODO: special handling for Rails models? see Bree's `active_record_match?` method + + raise "No matching classes found for structural types #{struct_types}." if matching_classes.empty? + + nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } + union = RDL::Type::UnionType.new(*nom_sing_types).canonical + struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds + var_type.ubounds << [union, "Not providing a location."] + end + + def self.extract_solution + puts "Starting solution extraction..." + RDL::Globals.constrained_types.each { |klass, name| + typ = RDL::Globals.info.get(klass, name, :type) + if typ.is_a?(Array) + meth_type = typ[0] + raise "Expected MethodType, got #{meth_type}." unless meth_type.is_a?(RDL::Type::MethodType) + + ## ARG SOLUTIONS + meth_type.args.each { |var_type| + non_vartype_ubounds = var_type.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + sol = RDL::Type::IntersectionType.new(*non_vartype_ubounds) + puts "Extracted solution for #{var_type} is #{sol}" + ## TODO: Eventually want to store this solution somewhere. + } + + ## RET SOLUTION + non_vartype_lbounds = meth_type.ret.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + sol = RDL::Type::UnionType.new(*non_vartype_lbounds).canonical + puts "Extracted solution for #{meth_type.ret} is #{sol}" + else + ## Instance/Class variables: TODO + ## There is no clear answer as to what to do in this case. + ## Just need to pick something in between bounds (inclusive). + ## For now, plan is to just use lower bound when it's not empty/%bot, + ## otherwise use upper bound. + ## Can improve later if desired. + var_type = typ + raise "Expected VarType, got #{var_type}." unless var_type.is_a?(RDL::Type::VarType) + if var_type.lbounds.empty? || (var_type.lbounds.size == 1 && var_type.lbounds[0][0] == RDL::Globals.types[:bot]) + ## use upper bounds in this case. + non_vartype_ubounds = var_type.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + sol = RDL::Type::IntersectionType.new(*non_vartype_ubounds) + puts "Extracted solution for #{var_type} is #{sol}" + else + ## use lower bounds + non_vartype_lbounds = meth_type.ret.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + sol = RDL::Type::UnionType.new(*non_vartype_lbounds).canonical + puts "Extracted solution for #{meth_type.ret} is #{sol}" + end + end + } + end + + +end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index e257e13b..56209fa4 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -213,6 +213,80 @@ def self.get_ast(klass, meth) return ast end + def self.infer(klass, meth) + puts "*************** Infering method #{meth} from class #{klass} ***************" + RDL::Config.instance.use_comp_types = false + @cur_meth = [klass, meth] + ast = get_ast(klass, meth) + types = RDL::Globals.info.get(klass, meth, :type) + if types == [] or types.nil? + ## in this case, have to create new arg/ret VarTypes for this method + meth_type = make_unknown_method_type(klass, meth) + arg_types = meth_type.args + ret_vartype = meth_type.ret + else + ## in this case, a MethodType was found for this method. + ## it had better be composed of VarTypes so we can infer something. + raise "Expected just one type composed of VarTypes for method to be inferred." if types.size > 1 + meth_type = types[0] + raise "Block params not yet supported." if meth_type.block + + (meth_type.args + [meth_type.ret]).each { |t| + if !t.is_a?(RDL::Type::VarType) + raise "Expected VarType in MethodType to be inferred, got #{t}." unless t.is_a?(RDL::Type::OptionalType) && t.type.is_a?(RDL::Type::VarType) + end + } + arg_types = meth_type.args + ret_vartype = meth_type.ret + end + + if ast.type == :def + name, args, body = *ast + elsif ast.type == :defs + _, name, args, body = *ast + else + raise RuntimeError, "Unexpected ast type #{ast.type}" + end + raise RuntimeError, "Method #{name} defined where method #{meth} expected" if name.to_sym != meth + context_types = RDL::Globals.info.get(klass, meth, :context_types) + + if RDL::Util.has_singleton_marker(klass) + # to_class gets the class object itself, so remove singleton marker to get class rather than singleton class + self_type = RDL::Type::SingletonType.new(RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass))) + else + self_type = RDL::Type::NominalType.new(klass) + end + + inst = {self: self_type} + + meth_type = meth_type.instantiate inst + + _, targs = args_hash({}, Env.new(inst), meth_type, args, ast, 'method') + targs[:self] = self_type + scope = { tret: meth_type.ret, tblock: meth_type.block, captured: Hash.new, context_types: context_types } + + begin + old_captured = scope[:captured].dup + if body.nil? + body_type = RDL.types[:nil] + else + _, body_type = tc(scope, Env.new(targs.merge(scope[:captured])), body) ## TODO: need separate argument indicating we're performing inference? or is this exactly the same as type checking... + end + end until old_captured == scope[:captured] + + body_type = self_type if meth == :initialize + if body_type.is_a?(RDL::Type::UnionType) + body_type.types.each { |t| RDL::Type::Type.leq(t, ret_vartype, ast: ast) } ## TODO: is this done automatically in <= method? check + else + RDL::Type::Type.leq(body_type, ret_vartype, ast: ast) + end + + RDL::Globals.info.set(klass, meth, :typechecked, true) + + RDL::Globals.constrained_types << [klass, meth] + puts "Done with constraint generation." + end + def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) @cur_meth = [klass, meth] ast = get_ast(klass, meth) unless ast @@ -262,7 +336,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) end old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured]) end until old_captured == scope[:captured] - error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || meth == :initialize || body_type <= type.ret + error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || meth == :initialize ||RDL::Type::Type.leq(body_type, type.ret, ast: ast) error :bad_effect, [body_effect, effect], body unless body.nil? || effect.nil? || effect_leq(body_effect, effect) } if RDL::Config.instance.check_comp_types @@ -376,7 +450,7 @@ def self.args_hash(scope, env, type, args, ast, kind) error :type_arg_kind_mismatch, [kind, 'vararg', 'optional'], arg if targ.vararg? error :type_arg_kind_mismatch, [kind, 'required', 'optional'], arg if !targ.optional? env, default_type = tc(scope, env, arg.children[1]) - error :optional_default_type, [default_type, targ.type], arg.children[1] unless default_type <= targ.type + error :optional_default_type, [default_type, targ.type], arg.children[1] unless RDL::Type::Type.leq(default_type, targ.type, ast: ast) targs[arg.children[0]] = targ.type env = env.merge(Env.new(arg.children[0] => targ.type)) tpos += 1 @@ -401,7 +475,7 @@ def self.args_hash(scope, env, type, args, ast, kind) tkw = targ.elts[kw] error :type_args_kw_mismatch, [kind, 'required', kw, 'optional'], arg if !tkw.is_a?(RDL::Type::OptionalType) env, default_type = tc(scope, env, arg.children[1]) - error :optional_default_kw_type, [kw, default_type, tkw.type], arg.children[1] unless default_type <= tkw.type + error :optional_default_kw_type, [kw, default_type, tkw.type], arg.children[1] unless RDL::Type::Type(default_type, tkw.type, ast: ast) kw_args_matched << kw targs[kw] = tkw.type env = env.merge(Env.new(kw => tkw.type)) @@ -520,8 +594,8 @@ def self.tc(scope, env, e) tis << RDL::Type::TupleType.new(*ti.params) elsif ti.is_a?(RDL::Type::SingletonType) && ti.val.nil? # nil gets thrown out - elsif (RDL::Globals.types[:array] <= ti) || (ti <= RDL::Globals.types[:array]) || - (RDL::Globals.types[:hash] <= ti) || (ti <= RDL::Globals.types[:hash]) + elsif RDL::Type::Type.leq(RDL::Globals.types[:array], ti, ast: e) || RDL::Type::Type.leq(ti, RDL::Globals.types[:array], ast: ast) || + RDL::Type::Type.leq(RDL::Globals.types[:hash], ti, ast: e) || RDL::Type::Type.leq(ti, RDL::Globals.types[:hash], ast: e) # might or might not be array...can't splat... error :cant_splat, [ti], ei else @@ -1062,7 +1136,7 @@ def self.tc(scope, env, e) else env1, t1, effi = [env, RDL::Globals.types[:nil], [:+, :+]] end - error :bad_return_type, [t1.to_s, scope[:tret]], e unless t1 <= scope[:tret] + error :bad_return_type, [t1.to_s, scope[:tret]], e unless RDL::Type::Type.leq(t1, scope[:tret], ast: e) error :bad_effect, [effi, scope[:eff]], e unless (scope[:eff].nil? || effect_leq(effi, scope[:eff])) [env1, RDL::Globals.types[:bot], effi] # return is a void value expression when :begin, :kwbegin # sequencing @@ -1201,6 +1275,9 @@ def self.tc(scope, env, e) # [+ name +] is the variable name, which should be a symbol # [+ e +] is the expression for which errors should be reported def self.tc_var(scope, env, kind, name, e) + kind_text = (if kind == :ivar then "instance variable" + elsif kind == :cvar then "class variable" + else "global variable" end) case kind when :lvar # local variable error :undefined_local_or_method, [name], e unless env.has_key? name @@ -1216,10 +1293,9 @@ def self.tc_var(scope, env, kind, name, e) type = RDL::Globals.info.get(klass, name, :type) elsif RDL::Config.instance.assume_dyn_type type = RDL::Globals.types[:dyn] + elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } + type = make_unknown_var_type(klass, name, kind_text) else - kind_text = (if kind == :ivar then "instance" - elsif kind == :cvar then "class" - else "global" end) error :untyped_var, [kind_text, name, klass], e end [env, type.canonical] @@ -1232,6 +1308,9 @@ def self.tc_var(scope, env, kind, name, e) # [+ tright +] is type of right-hand side def self.tc_vasgn(scope, env, kind, name, tright, e) error :empty_env, [name], e if env.nil? + kind_text = (if kind == :ivasgn then "instance variable" + elsif kind == :cvasgn then "class variable" + else "global variable" end) case kind when :lvasgn if ((scope[:captured] && scope[:captured].has_key?(name)) || @@ -1239,7 +1318,7 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) capture(scope, name, tright.canonical) [env, scope[:captured][name]] elsif (env.fixed? name) - error :vasgn_incompat, [tright, env[name]], e unless tright <= env[name] + error :vasgn_incompat, [tright, env[name]], e unless RDL::Type::Type.leq(tright, env[name], ast: e) [env, tright.canonical] else [env.bind(name, tright), tright.canonical] @@ -1250,13 +1329,13 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) tleft = RDL::Globals.info.get(klass, name, :type) elsif RDL::Config.instance.assume_dyn_type tleft = RDL::Globals.types[:dyn] + elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } + type = make_unknown_var_type(klass, name, kind_text) + else - kind_text = (if kind == :ivasgn then "instance" - elsif kind == :cvasgn then "class" - else "global" end) error :untyped_var, [kind_text, name, klass], e end - error :vasgn_incompat, [tright.to_s, tleft.to_s], e unless tright <= tleft + error :vasgn_incompat, [tright.to_s, tleft.to_s], e unless RDL::Type::Type.leq(tright, tleft, ast: e) [env, tright.canonical] when :send meth = e.children[1] # note method name include =! @@ -1515,11 +1594,26 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, trecv = trecv.canonical ts, es = lookup(scope, trecv.name, meth, e) error :no_instance_method_type, [trecv.name, meth], e unless ts - inst = trecv.to_inst.merge(self: trecv) + inst = { self: trecv } self_klass = RDL::Util.to_class(trecv.name) end when RDL::Type::VarType - error :recv_var_type, [trecv], e + if block + raise "Block arguments for VarType receiver not currently supported." + end + + ret_type = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :ret, name: "ret") + + meth_type = RDL::Type::MethodType.new(tactuals, nil, ret_type) + RDL::Type::Type.leq(trecv, RDL::Type::StructuralType.new({ meth => meth_type }), ast: e) + #tmeth_inter = [meth_type] + + #self_klass = nil + #error :recv_var_type, [trecv], e + return [[ret_type]] + ## MILOD TODO: Do we want to save the created MethodType some how? + ## this could be useful if the same method is called on a VarType + ## multiple times. But it's not exactly clear to me how we would save it. when RDL::Type::MethodType if meth == :call # Special case - invokes the Proc @@ -1568,7 +1662,10 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, end tmeth = tmeth.instantiate(inst) if inst tmeth_names << tmeth - tmeth_inst = tc_arg_types(tmeth, tactuals_expanded) + deferred_constraints = [] + tmeth_inst = tc_arg_types(tmeth, tactuals_expanded, deferred_constraints) + apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? + ## MILOD: Plan is to call tc_arg_types/Type.leq with deferred_constraints argument, then apply deferred_constraints if tmeth_inst effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block if es @@ -1643,7 +1740,19 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, error :arg_type_single_receiver_error, [name, meth, msg], e end # TODO: issue warning if trets.size > 1 ? - return [trets, es] + return [trets, es] + end + + def self.apply_deferred_constraints(deferred_constraints, e) + if deferred_constraints.all? { |t1, t2| t1.equal?(deferred_constraints[0][0]) && t2.is_a?(RDL::Type::NominalType) && t2 <= RDL::Globals.types[:numeric] } + ## This is a temporary hack for Numeric types. + ## If all the LHS types are the same single type, and all the RHS types + ## are Numeric types (which is the case for almost all arithmetic methods), + ## then only apply the single constraint that t1 <= Numeric. + RDL::Type::Type.leq(deferred_constraints[0][0], RDL::Globals.types[:numeric], ast: e) + else + deferred_constraints.each { |t1, t2| RDL::Type::Type.leq(t1, t2, ast: e) } + end end # Evaluates any ComputedTypes in a method type @@ -1723,13 +1832,18 @@ def self.tc_send_class(trecv, e) # [+ actuals +] is Array containing the actual argument types # return instiation (possibly empty) that makes actuals match method type (if any), nil otherwise # Very similar to MethodType#pre_cond? - def self.tc_arg_types(tmeth, tactuals) - states = [[0, 0, Hash.new]] # position in tmeth, position in tactuals, inst of free vars in tmeth + def self.tc_arg_types(tmeth, tactuals, deferred_constraints=[]) + states = [[0, 0, Hash.new, deferred_constraints]] # position in tmeth, position in tactuals, inst of free vars in tmeth tformals = tmeth.args until states.empty? - formal, actual, inst = states.pop + formal, actual, inst, deferred_constraints = states.pop inst = inst.dup # avoid aliasing insts in different states since Type.leq mutates inst arg if formal == tformals.size && actual == tactuals.size # Matched everything +=begin + deferred_constraints.each { |t1, t2| + t1 <= t2 + } +=end return inst end next if formal >= tformals.size # Too many actuals to match @@ -1741,34 +1855,34 @@ def self.tc_arg_types(tmeth, tactuals) when RDL::Type::OptionalType t = t.type if actual == tactuals.size - states << [formal+1, actual, inst] # skip over optinal formal - elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false) - states << [formal+1, actual+1, inst] # match - states << [formal+1, actual, inst] # skip + states << [formal+1, actual, inst, deferred_constraints] # skip over optinal formal + elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false, deferred_constraints) + states << [formal+1, actual+1, inst, deferred_constraints] # match + states << [formal+1, actual, inst, deferred_constraints] # skip else - states << [formal+1, actual, inst] # types don't match; must skip this formal + states << [formal+1, actual, inst, deferred_constraints] # types don't match; must skip this formal end when RDL::Type::VarargType if actual == tactuals.size - states << [formal+1, actual, inst] # skip to allow empty vararg at end - elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t.type, inst, false) - states << [formal, actual+1, inst] # match, more varargs coming - states << [formal+1, actual+1, inst] # match, no more varargs - states << [formal+1, actual, inst] # skip over even though matches - elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false) && - RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true) - states << [formal+1, actual+1, inst] # match, no more varargs; no other choices! + states << [formal+1, actual, inst, deferred_constraints] # skip to allow empty vararg at end + elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t.type, inst, false, deferred_constraints) + states << [formal, actual+1, inst, deferred_constraints] # match, more varargs coming + states << [formal+1, actual+1, inst, deferred_constraints] # match, no more varargs + states << [formal+1, actual, inst, deferred_constraints] # skip over even though matches + elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, deferred_constraints) && + RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, deferred_constraints) + states << [formal+1, actual+1, inst, deferred_constraints] # match, no more varargs; no other choices! else - states << [formal+1, actual, inst] # doesn't match, must skip + states << [formal+1, actual, inst, deferred_constraints] # doesn't match, must skip end else if actual == tactuals.size next unless t.instance_of? RDL::Type::FiniteHashType if @@empty_hash_type <= t - states << [formal+1, actual, inst] + states << [formal+1, actual, inst, deferred_constraints] end - elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false) - states << [formal+1, actual+1, inst] # match! + elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false, deferred_constraints) + states << [formal+1, actual+1, inst, deferred_constraints] # match! # no else case; if there is no match, this is a dead end end end @@ -1959,11 +2073,55 @@ def self.lookup(scope, klass, name, e) ret = RDL::Type::NominalType.new(the_klass) if name == :initialize return [[RDL::Type::MethodType.new(args, nil, ret)]] + elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } + ## method types are to be inferred + return [[make_unknown_method_type(klass, name)]] else return nil end end + def self.make_unknown_method_type(klass, meth) + raise "Tried to make unknown method type for class #{klass} method #{meth}, but no such method was found." unless RDL::Util.to_class(klass).instance_methods.include?(meth) + params = RDL::Util.to_class(klass).instance_method(meth).parameters + req_params = params.select {|p| p[0] == :req} + opt_params = params.select {|p| p[0] == :opt} + blk_params = params.select {|p| p[0] == :block} + vararg_params = params.select {|p| p[0] == :rest} + + raise "Block/VarArg params not yet supported" if !blk_params.empty? || !vararg_params.empty? + + req_arg_vartypes = req_params.map { |param| + RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1]) + } + opt_arg_vartypes = opt_params.map { |param| + RDL::Type::OptionatlType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) + } + + vararg_vartypes = vararg_params.map { |param| + RDL::Type::VarargType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) } + arg_types = req_arg_vartypes + opt_arg_vartypes + vararg_vartypes + + if meth == :initialize + ret_vartype = RDL::Type::VarType.new(:self) ## TODO: is this right? Or should it include klass/meth info? + else + ret_vartype = RDL::Type::VarType.new(cls: klass, meth: meth, category: :ret, name: "ret") + end + + ## TODO: handling of block types + + meth_type = RDL::Type::MethodType.new(arg_types, nil, ret_vartype) + RDL::Globals.info.add(klass, meth, :type, meth_type) + return meth_type + end + + def make_unknown_var_type(klass, name, kind_text) + var_type = RDL::Type::VarType.new(cls: klass, name: name, category: kind_text) + RDL::Globals.info.add(klass, name, :type, var_type) + RDL::Globals.constrained_types << [klass, var] + return var_type + end + def self.filter_comp_types(ts, use_dep_types) return nil unless ts dep_ts = [] @@ -2082,7 +2240,7 @@ def message no_instance_method_type: "no type information for instance method `%s#%s'", no_singleton_method_type: "no type information for class/singleton method `%s.%s'", arg_type_single_receiver_error: "argument type error for instance method `%s#%s'\n%s", - untyped_var: "no type for %s variable `%s' in class %s", + untyped_var: "no type for %s `%s' in class %s, and it is not designated to be inferred", vasgn_incompat: "incompatible types: `%s' can't be assigned to variable of type `%s'", inconsistent_var_type: "local variable `%s' has declared type on some paths but not all", inconsistent_var_type_type: "local variable `%s' declared with inconsistent types %s", @@ -2125,6 +2283,9 @@ def message no_type_for_symbol: "can't find type for method corresponding to `%s.to_proc'", no_non_dep_types: "no non-dependent types for receiver %s in call to method %s", empty_env: "for some reason, environment is nil when type checking assignment to variable %s.", + + + infer_constraint_error: "%s constraint generated here." } end diff --git a/lib/rdl/types/singleton.rb b/lib/rdl/types/singleton.rb index 7ebdb1b8..de27fcf7 100644 --- a/lib/rdl/types/singleton.rb +++ b/lib/rdl/types/singleton.rb @@ -46,6 +46,8 @@ def to_s ":#{@val}" elsif @val.nil? "nil" + elsif @val.is_a?(Class) + "[s]#{@val}" else @val.to_s # "Singleton(#{@val.to_s})" diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 50e90b09..9281fc38 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -32,13 +32,15 @@ def vararg?; return false; end # [+ other +] is a Type # [+ inst +] is a Hash representing an instantiation # [+ ileft +] is a %bool + # [+ deferred_constraints +] is an Array<[Type, Type]>. When provided, instead of applying + # constraints to VarTypes, we simply defer them by putting them in this array. # [+ no_constraint +] is a %bool indicating whether or not we should add to tuple/FHT constraints # if inst is nil, returns self <= other # if inst is non-nil and ileft, returns inst(self) <= other, possibly mutating inst to make this true # if inst is non-nil and !ileft, returns self <= inst(other), again possibly mutating inst - def self.leq(left, right, inst=nil, ileft=true, no_constraint: false) - left = inst[left.name] if inst && ileft && left.is_a?(VarType) && inst[left.name] - right = inst[right.name] if inst && !ileft && right.is_a?(VarType) && inst[right.name] + def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_constraint: false, ast: nil) + left = inst[left.name] if inst && ileft && left.is_a?(VarType) && !left.to_infer && inst[left.name] + right = inst[right.name] if inst && !ileft && right.is_a?(VarType) && !right.to_infer && inst[right.name] left = left.type if left.is_a? DependentArgType right = right.type if right.is_a? DependentArgType left = left.type if left.is_a? NonNullType # ignore nullness! @@ -55,10 +57,36 @@ def self.leq(left, right, inst=nil, ileft=true, no_constraint: false) return true if right.is_a? DynamicType # type variables - begin inst.merge!(left.name => right); return true end if inst && ileft && left.is_a?(VarType) - begin inst.merge!(right.name => left); return true end if inst && !ileft && right.is_a?(VarType) - if left.is_a?(VarType) && right.is_a?(VarType) + begin inst.merge!(left.name => right); return true end if inst && ileft && left.is_a?(VarType) && !left.to_infer + begin inst.merge!(right.name => left); return true end if inst && !ileft && right.is_a?(VarType) && !right.to_infer + if left.is_a?(VarType) && !left.to_infer && right.is_a?(VarType) && !right.to_infer return left.name == right.name + elsif left.is_a?(VarType) && left.to_infer && right.is_a?(VarType) && right.to_infer + if deferred_constraints.nil? + left.ubounds << [right, ast] unless (left.ubounds.any? { |t, loc| t == right } || left.equal?(right)) + #left.add_upper_bound(right) + right.lbounds << [left, ast] unless (right.lbounds.any? { |t, loc| t == left } || right.equal?(left)) + #right.add_lower_bound(left) + else + deferred_constraints << [left, right] + end + return true + elsif left.is_a?(VarType) && left.to_infer + if deferred_constraints.nil? + left.ubounds << [right, ast] unless (left.ubounds.any? { |t, loc| t == right } || left.equal?(right)) + #left.add_upper_bound(right) + else + deferred_constraints << [left, right] + end + return true + elsif right.is_a?(VarType) && right.to_infer + if deferred_constraints.nil? + right.lbounds << [left, ast] unless (right.lbounds.any? { |t, loc| t == left } || right.equal?(left)) + #right.add_lower_bound(left) + else + deferred_constraints << [left, right] + end + return true end # union diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index cd5cccb9..e7b738c6 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -1,27 +1,95 @@ module RDL::Type class VarType < Type - attr_reader :name - + attr_reader :name, :cls, :meth, :category, :to_infer + attr_accessor :lbounds, :ubounds + @@cache = {} class << self alias :__new__ :new end - def self.new(name) - name = name.to_s.to_sym - t = @@cache[name] - return t if t - t = self.__new__ name - return (@@cache[name] = t) # assignment evaluates to t + def self.new(name_or_hash) + if name_or_hash.is_a?(Symbol) || name_or_hash.is_a?(String) + name = name_or_hash.to_s.to_sym + t = @@cache[name_or_hash] + return t if t + t = self.__new__ name + return (@@cache[name_or_hash] = t) # assignment evaluates to t + else + # MILOD: I don't believe we want to cache these -- could result in clashes when we don't want them. + #t = @@cache[name_or_hash] + #return t if t + + t = self.__new__ name_or_hash + + #return (@@cache[name_or_hash] = t) + return t + end + end + + def initialize(name_or_hash) + if name_or_hash.is_a?(Symbol) || name_or_hash.is_a?(String) + @name = name_or_hash + @to_infer = false + elsif name_or_hash.is_a?(Hash) + @to_infer = true + @lbounds = [] + @ubounds = [] + + @cls = name_or_hash[:cls] + @name = name_or_hash[:name] ## might be nil if category is :ret + @meth = name_or_hash[:meth] ## might be nil if ccategory is :var + @category = name_or_hash[:category] + else + raise "Unexpected argument #{name_or_hash} to RDL::Type::VarType.new." + end + end + + + def add_and_propagate_upper_bound(typ, ast) + return if self.equal?(typ) + @ubounds << [typ, ast] if !ubounds.any? { |t, a| t == typ } + #typ.ubounds.each { |t| add_and_propagate_upper_bound(typ) } + @lbounds.each { |lower_t, a| + if lower_t.is_a?(VarType) + lower_t.add_and_propagate_upper_bound(typ, ast) + else + ## TODO: More precise error messages. + #raise "Type error found." unless RDL::Type::Type.leq(lower_t, typ, ast: ast) + unless RDL::Type::Type.leq(lower_t, typ, ast: ast) + d1 = (Diagnostic.new :note, :infer_constraint_error, [lower_t.to_s], a.loc.expression).render.join("\n") + d2 = (Diagnostic.new :note, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") + raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{lower_t} <= #{typ} generated during inference.\n #{d1}\n #{d2}") + end + end + } end - def initialize(name) - @name = name + def add_and_propagate_lower_bound(typ, ast) + return if self.equal?(typ) + @lbounds << [typ, ast] if !@lbounds.any? { |t, a| t == typ } + #typ.lbounds.each { |t| add_and_propagate_lower_bound(typ) } if typ.is_a?(VarType) + @ubounds.each { |upper_t, a| + if upper_t.is_a?(VarType) + upper_t.add_and_propagate_lower_bound(typ, ast) + else + ## TODO: More precise error messages. + unless RDL::Type::Type.leq(typ, upper_t, ast: ast) + d1 = (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") + d2 = (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") + raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{typ} <= #{upper_t} generated during inference.\n #{d1}\n #{d2}") + end + end + } end def to_s # :nodoc: - return @name.to_s + if @to_infer + return "{ #{@cls}##{@meth} #{@category}: #{@name} }" + else + return @name.to_s + end end def ==(other) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index cd289eee..942e8a02 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -150,6 +150,24 @@ def self.process_type_args(default_class, *args) return [klass, meth, type] end + def self.process_infer_args(default_class, *args) + klass = meth = nil + default_class = "Object" if (default_class.is_a? Object) && (default_class.to_s == "main") # special case for main + if args.size == 2 + klass = class_to_string args[0] + slf, meth = meth_to_sym args[1] + elsif args.size == 1 + klass = default_class.to_s + slf, meth = meth_to_sym args[0] + elsif args.size == 0 + klass = default_class.to_s + else + raise ArgumentError, "Invalid arguments to `infer`" + end + klass = RDL::Util.add_singleton_marker(klass) if slf + return [klass, meth] + end + private def self.wrapped_name(klass, meth) @@ -229,6 +247,14 @@ def self.do_method_added(the_self, sing, klass, meth) RDL::Globals.to_typecheck[h[:typecheck]] = Set.new unless RDL::Globals.to_typecheck[h[:typecheck]] RDL::Globals.to_typecheck[h[:typecheck]].add([klass, meth]) end + if kind == :infer + if contract == :now + RDL::Typecheck.infer(klass, meth) + else + RDL::Globals.to_infer[contract] = Set.new unless RDL::Globals.to_infer[contract] + RDL::Globals.to_infer[contract].add([klass, meth]) + end + end } end @@ -248,7 +274,7 @@ def self.do_method_added(the_self, sing, klass, meth) if RDL::Globals.to_typecheck[:now].member? [klass, meth] RDL::Globals.to_typecheck[:now].delete [klass, meth] RDL::Typecheck.typecheck(klass, meth) - end + end if RDL::Config.instance.guess_types.include?(the_self.to_s.to_sym) && !RDL::Globals.info.has?(klass, meth, :type) # Added a method with no type annotation from a class we want to guess types for @@ -384,6 +410,52 @@ def readd_comp_types RDL::Globals.dep_types.each { |klass, meth, t| RDL::Globals.info.add(klass, meth, :type, t) unless meth.nil? } end + # Very similar to `type`, but arguments are: + # [+ klass +] may be Class, Symbol, or String + # [+ method +] may be Symbol or String + # [+ time +] indicates a method type that should be statically inferred at the given time, as follows + # if :now, indicates method type should be inferred immediately + # if other-symbol, indicates method type should be inferred when RDL.do_infer(other-symbol) is called + # time is currently a *required* parameter + # possible invocations: + # infer(klass, meth, time: sym) + # infer(meth, time: sym) + # infer(time: sym) + def infer(*args, time: RDL::Config.instance.infer_defaults[:time]) + ## TODO: do we want to handle the case that time is :call? + raise RuntimeError, "Calls to `infer` must come with a specified time." if !time + ## might remove the above error later. + klass, meth = begin + RDL::Wrap.process_infer_args(self, *args) + rescue Racc::ParseError => err + # Remove enough backtrace to only include actual source line + # Warning: Adjust the -5 below if the code (or this comment) changes + bt = err.backtrace + bt.shift until bt[0] =~ /^#{__FILE__}:#{__LINE__-5}/ + bt.shift # remove RDL::Globals.contract_switch.off call + bt.shift # remove type call itself + err.set_backtrace bt + raise err + end + if meth + unless RDL::Globals.info.set(klass, meth, :infer, time) + raise RuntimeError, "Inconsistent infer time flag on #{RDL::Util.pp_klass_method(klass, meth)}" + end + if RDL::Util.method_defined?(klass, meth) #|| meth == :initialize + RDL::Globals.info.set(klass, meth, :source_location, RDL::Util.to_class(klass).instance_method(meth).source_location) + RDL::Typecheck.infer(klass, meth) if time == :now + end + RDL::Globals.to_infer[time] = Set.new unless RDL::Globals.to_infer[time] + RDL::Globals.to_infer[time].add([klass, meth]) + else + RDL::Globals.deferred << [klass, :infer, time, { }] + + end + nil + end + + + # [+ klass +] is the class containing the variable; self if omitted; ignored for local and global variables # [+ var +] is a symbol or string containing the name of the variable # [+ typ +] is a string containing the type @@ -397,6 +469,20 @@ def var_type(klass=self, var, typ) nil end + def infer_var_type(klass=self, var) + raise RuntimeError, "Variable cannot begin with capital" if var.to_s =~ /^[A-Z]/ + return if var.to_s =~ /^[a-z]/ # local variables handled specially, inside type checker + klass = RDL::Util::GLOBAL_NAME if var.to_s =~ /^\$/ + unless RDL::Globals.info.set(klass, var, :type, RDL::Type::VarType.new(cls: klass, name: var, category: "variable")) + raise RuntimeError, "Type already declared for #{var}" + end + ## RDL::Globals.constrained_types includes the list of all types we want to perform constraint resolution/ + ## solution extract for. VarTypes in methods get added to this list after calling RDL.do_infer. + ## Not sure when/where to add variable VarTypes so I'm doing it here. + RDL::Globals.constrained_types << [klass, var] + nil + end + # In the following three methods # [+ args +] is a sequence of symbol, typ. attr_reader is called for each symbol, # and var_type is called to assign the immediately following type to the @@ -598,6 +684,44 @@ def self.do_typecheck(sym) nil end + def self.do_infer(sym) + return unless RDL::Globals.to_infer[sym] + RDL::Globals.to_infer[sym].each { |klass, meth| + RDL::Typecheck.infer klass, meth + } + RDL::Globals.to_infer[sym] = Set.new + nil + RDL::Typecheck.resolve_constraints + RDL::Typecheck.extract_solution + #RDL::Globals.constrained_types = [] +=begin + puts "Here are the generated constraints: " + RDL::Globals.constrained_types.each { |klass, name| + typ = RDL::Globals.info.get(klass, name, :type) + if typ.is_a?(Array) + ## it's an array for method types (to handle intersections) + meth_type = typ[0] + raise "Expected MethodType, got #{meth_type}." unless meth_type.is_a?(RDL::Type::MethodType) + (meth_type.args + [meth_type.ret]).each { |var_type| + raise "Expected VarType, got #{var_type}." unless var_type.is_a?(RDL::Type::VarType) + puts "***** Lower bounds for #{var_type} *****" + var_type.lbounds.each { |t| puts t } + puts "***** Upper bounds for #{var_type} *****" + var_type.ubounds.each { |t| puts t } + } + else + ## variable type in this case + var_type = typ + raise "Expected VarType, got #{var_type}." unless var_type.is_a?(RDL::Type::VarType) + puts "***** Lower bounds for #{var_type} *****" + var_type.lbounds.each { |t| puts t } + puts "***** Upper bounds for #{var_type} *****" + var_type.ubounds.each { |t| puts t } + end + } +=end + end + def self.load_sequel_schema(db) db.tables.each { |table| hash_str = "{ " From d5fd3823dda6857d788cabc5ef6c542eddc21ebf Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 16 Sep 2019 09:56:38 -0400 Subject: [PATCH 004/124] add support for blocks, comp types, other updates --- lib/rdl/constraint.rb | 137 ++++++++++--------- lib/rdl/typecheck.rb | 140 ++++++++++++++------ lib/rdl/types/finite_hash.rb | 18 +-- lib/rdl/types/intersection.rb | 25 ++++ lib/rdl/types/method.rb | 15 ++- lib/rdl/types/tuple.rb | 2 +- lib/rdl/types/type.rb | 85 ++++++++---- lib/rdl/types/union.rb | 7 +- lib/rdl/types/var.rb | 8 +- lib/rdl/wrap.rb | 8 +- lib/types/core/hash.rb | 1 + lib/types/rails/active_record/comp_types.rb | 49 ++++--- lib/types/sequel/comp_types.rb | 46 +++++-- 13 files changed, 360 insertions(+), 181 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 916ed166..4b162873 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -7,7 +7,7 @@ def self.resolve_constraints ## If typ is an Array, then it's an array of method types ## but for inference, we only use a single method type. ## Otherwise, it's a single VarType for an instance/class var. - var_types = typ.is_a?(Array) ? typ[0].args + [typ[0].ret] : [typ] + var_types = typ.is_a?(Array) ? typ[0].args + [typ[0].block, typ[0].ret] : [typ] var_types.each { |var_type| var_type.lbounds.each { |lower_t, ast| @@ -17,38 +17,13 @@ def self.resolve_constraints var_type.add_and_propagate_upper_bound(upper_t, ast) } } -=begin - if typ.is_a?(Array) - ## it's an array for method types (to handle intersections) - meth_type = typ[0] ## should only be one type though since we're inferring it - raise "Expected MethodType, got #{meth_type}." unless meth_type.is_a?(RDL::Type::MethodType) - (meth_type.args + [meth_type.ret]).each { |var_type| - var_type.lbounds.each { |lower_t, ast| - var_type.add_and_propagate_lower_bound(lower_t, ast) - } - var_type.ubounds.each { |upper_t, ast| - var_type.add_and_propagate_upper_bound(upper_t, ast) - } - } - else - ## variable type in this case - var_type = typ - raise "Expected VarType, got #{var_type}." unless var_type.is_a?(RDL::Type::VarType) - var_type.lbounds.each { |lower_t, ast| - var_type.add_and_propagate_lower_bound(lower_t, ast) - } - var_type.ubounds.each { |upper_t, ast| - var_type.add_and_propagate_upper_bound(upper_t, ast) - } - end -=end } if RDL::Config.instance.practical_infer puts "practical inference!!!" RDL::Globals.constrained_types.each { |klass, name| typ = RDL::Globals.info.get(klass, name, :type) - var_types = typ.is_a?(Array) ? typ[0].args + [typ[0].ret] : [typ] + var_types = typ.is_a?(Array) ? typ[0].args + [typ[0].block, typ[0].ret] : [typ] var_types.each { |var_type| puts "Applying struct_to_nominal to #{var_type}." struct_to_nominal(var_type) @@ -61,7 +36,8 @@ def self.resolve_constraints def self.struct_to_nominal(var_type) return unless var_type.category == :arg ## this rule only applies to args - return unless var_type.ubounds.all? { |t, loc| t.is_a?(RDL::Type::StructuralType) || t.is_a?(RDL::Type::VarType) } ## all upper bounds must be struct types or var types + #return unless var_type.ubounds.all? { |t, loc| t.is_a?(RDL::Type::StructuralType) || t.is_a?(RDL::Type::VarType) } ## all upper bounds must be struct types or var types + return unless var_type.ubounds.any? { |t, loc| t.is_a?(RDL::Type::StructuralType) } ## upper bounds must include struct type(s) struct_types = var_type.ubounds.select { |t, loc| t.is_a?(RDL::Type::StructuralType) } struct_types.map! { |t, loc| t } return if struct_types.empty? @@ -69,57 +45,100 @@ def self.struct_to_nominal(var_type) meth_names = struct_types.map { |st| st.methods.keys }.flatten matching_classes = ObjectSpace.each_object(Class).select { |c| (meth_names - c.instance_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods + ## Because every object inherits from Object/BasicObject, + ## we end up with some really weird issues regarding the eigenclasses + ## of objects if we try to include all possible matching classes. + ## We can just narrow things down here right off the bat. + if matching_classes.include?(BasicObject) + matching_classes = [BasicObject] + elsif matching_classes.include?(Object) + matching_classes = [Object] + end + + ## TODO: not just look up classes with meth defined, but also classes + ## with type defined. e.g., `Table` is a made up class we use. ## TODO: special handling for arrays/hashes/generics? ## TODO: special handling for Rails models? see Bree's `active_record_match?` method raise "No matching classes found for structural types #{struct_types}." if matching_classes.empty? - nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } union = RDL::Type::UnionType.new(*nom_sing_types).canonical struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds - var_type.ubounds << [union, "Not providing a location."] + #var_type.ubounds << [union, "Not providing a location."] + var_type.add_and_propagate_upper_bound(union, nil) end - def self.extract_solution + def self.extract_var_sol(var, category) + #raise "Expected VarType, got #{var}." unless var.is_a?(RDL::Type::VarType) + return var unless var.is_a?(RDL::Type::VarType) + if category == :arg + non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + sol = non_vartype_ubounds.size == 1 ? non_vartype_ubounds[0] : RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + return sol + elsif category == :ret + non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + return RDL::Type::UnionType.new(*non_vartype_lbounds).canonical + elsif category == :var + if var.lbounds.empty? || (var.lbounds.size == 1 && var.lbounds[0][0] == RDL::Globals.types[:bot]) + ## use upper bounds in this case. + non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + return RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + else + ## use lower bounds + non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + return RDL::Type::UnionType.new(*non_vartype_lbounds).canonical + end + else + raise "Unexpected VarType category #{category}." + end + end + + def self.extract_meth_sol(tmeth) + raise "Expected MethodType, got #{tmeth}." unless tmeth.is_a?(RDL::Type::MethodType) + ## ARG SOLUTIONS + arg_sols = tmeth.args.map { |a| extract_var_sol(a, :arg) } + + ## BLOCK SOLUTION + if tmeth.block && !tmeth.block.ubounds.empty? + non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } + block_sol = non_vartype_ubounds.size > 1 ? RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical : non_vartype_bounds[0] ## doing this once and calling canonical to remove any supertypes that would be eliminated anyway + block_sols = [] + block_sol.types.each { |m| + raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) + block_sols << RDL::Type::MethodType.new(*extract_meth_sol(m)) + } + block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical + else + block_sol = nil + end + + ## RET SOLUTION + ret_sol = extract_var_sol(tmeth.ret, :ret) + + return [arg_sols, block_sol, ret_sol] + end + + def self.extract_solutions puts "Starting solution extraction..." RDL::Globals.constrained_types.each { |klass, name| typ = RDL::Globals.info.get(klass, name, :type) if typ.is_a?(Array) - meth_type = typ[0] - raise "Expected MethodType, got #{meth_type}." unless meth_type.is_a?(RDL::Type::MethodType) - - ## ARG SOLUTIONS - meth_type.args.each { |var_type| - non_vartype_ubounds = var_type.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } - sol = RDL::Type::IntersectionType.new(*non_vartype_ubounds) - puts "Extracted solution for #{var_type} is #{sol}" - ## TODO: Eventually want to store this solution somewhere. - } + raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 + tmeth = typ[0] + + arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) + block_string = block_sol ? " { #{block_sol} }" : nil + puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" - ## RET SOLUTION - non_vartype_lbounds = meth_type.ret.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } - sol = RDL::Type::UnionType.new(*non_vartype_lbounds).canonical - puts "Extracted solution for #{meth_type.ret} is #{sol}" else - ## Instance/Class variables: TODO + ## Instance/Class variables: ## There is no clear answer as to what to do in this case. ## Just need to pick something in between bounds (inclusive). ## For now, plan is to just use lower bound when it's not empty/%bot, ## otherwise use upper bound. ## Can improve later if desired. - var_type = typ - raise "Expected VarType, got #{var_type}." unless var_type.is_a?(RDL::Type::VarType) - if var_type.lbounds.empty? || (var_type.lbounds.size == 1 && var_type.lbounds[0][0] == RDL::Globals.types[:bot]) - ## use upper bounds in this case. - non_vartype_ubounds = var_type.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } - sol = RDL::Type::IntersectionType.new(*non_vartype_ubounds) - puts "Extracted solution for #{var_type} is #{sol}" - else - ## use lower bounds - non_vartype_lbounds = meth_type.ret.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } - sol = RDL::Type::UnionType.new(*non_vartype_lbounds).canonical - puts "Extracted solution for #{meth_type.ret} is #{sol}" - end + var_sol = extract_var_sol(typ, :var) + puts "Extracted solution for #{var} is #{var_sol}." end } end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 56209fa4..016117a9 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -152,7 +152,9 @@ def self.scope_merge(scope, **elts) # add x:t to the captured map in scope def self.capture(scope, x, t) if scope[:captured][x] - scope[:captured][x] = RDL::Type::UnionType.new(scope[:captured][x], t).canonical unless t <= scope[:captured][x] + if !RDL::Type::Type.leq(t, scope[:captured][x], inst={}, true)#t <= scope[:captured][x] + scope[:captured][x] = RDL::Type::UnionType.new(scope[:captured][x], t.instantiate(inst)).canonical #unless RDL::Type::Type.leq(t, scope[:captured][x], inst={}, true)#t <= scope[:captured][x] + end else scope[:captured][x] = t end @@ -215,7 +217,7 @@ def self.get_ast(klass, meth) def self.infer(klass, meth) puts "*************** Infering method #{meth} from class #{klass} ***************" - RDL::Config.instance.use_comp_types = false + RDL::Config.instance.use_comp_types = true @cur_meth = [klass, meth] ast = get_ast(klass, meth) types = RDL::Globals.info.get(klass, meth, :type) @@ -223,6 +225,7 @@ def self.infer(klass, meth) ## in this case, have to create new arg/ret VarTypes for this method meth_type = make_unknown_method_type(klass, meth) arg_types = meth_type.args + block_type = meth_type.block ret_vartype = meth_type.ret else ## in this case, a MethodType was found for this method. @@ -237,6 +240,7 @@ def self.infer(klass, meth) end } arg_types = meth_type.args + block_type = meth_type.block ret_vartype = meth_type.ret end @@ -270,12 +274,14 @@ def self.infer(klass, meth) if body.nil? body_type = RDL.types[:nil] else - _, body_type = tc(scope, Env.new(targs.merge(scope[:captured])), body) ## TODO: need separate argument indicating we're performing inference? or is this exactly the same as type checking... + targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this + _, body_type = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) ## TODO: need separate argument indicating we're performing inference? or is this exactly the same as type checking... end + old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured]) end until old_captured == scope[:captured] body_type = self_type if meth == :initialize - if body_type.is_a?(RDL::Type::UnionType) + if false#body_type.is_a?(RDL::Type::UnionType) body_type.types.each { |t| RDL::Type::Type.leq(t, ret_vartype, ast: ast) } ## TODO: is this done automatically in <= method? check else RDL::Type::Type.leq(body_type, ret_vartype, ast: ast) @@ -393,25 +399,25 @@ def self.widen_scopes(h1, h2) hash.each { |k, v| case v when RDL::Type::TupleType - if v.params.size > 50 + if v.params.size > 10 newhash[k] = v.promote else newhash[k] = v end when RDL::Type::FiniteHashType - if v.elts.size > 50 + if v.elts.size > 10 newhash[k] = v.promote else newhash[k] = v end when RDL::Type::PreciseStringType - if v.vals.size > 50 || (v.vals.size == 1 && v.vals[0].size > 50) + if v.vals.size > 10 || (v.vals.size == 1 && v.vals[0].size > 10) newhash[k] = RDL::Globals.types[:string] else newhash[k] = v end when RDL::Type::UnionType - if v.types.size > 50 + if v.types.size > 10 newhash[k] = v.widen else newhash[k] = v @@ -848,6 +854,8 @@ def self.tc(scope, env, e) tactuals = [] eff = [:+, :+] block = scope[:block] + map_case = false + e_map_case = ti_map_case = nil scope_merge(scope, block: nil, break: env, next: env) { |sscope| e.children[2..-1].each { |ei| if ei.type == :splat @@ -863,9 +871,21 @@ def self.tc(scope, env, e) raise RuntimeError, "impossible to pass block arg and literal block" if scope[:block] envi, ti = tc(sscope, envi, ei.children[0]) # convert using to_proc if necessary - ti, effi = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType - eff = effect_union(eff, effi) - block = [ti, ei] + if e.children[1] == :map + ## block_pass calling map is a weird case: + ## it takes a symbol representing method being called, + ## where receiver is Array elements. + ## But we haven't type checked the receiver yet, + ## so we can't really determine the type of the block yet. + ## So we do that below. + map_case = true + e_map_case = ei + ti_map_case = ti + else + ti, effi = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType + eff = effect_union(eff, effi) + block = [ti, ei] + end else envi, ti, effi = tc(sscope, envi, ei) eff = effect_union(eff, effi) @@ -874,6 +894,15 @@ def self.tc(scope, env, e) } envi, trecv, effrec = if e.children[0].nil? then [envi, envi[:self], [:+, :+]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver eff = effect_union(effrec, eff) + + if map_case + raise "Expected GenericType, got #{trecv}." unless trecv.is_a?(RDL::Type::GenericType) + ti_map_case, effi = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) + map_block_type = RDL::Type::MethodType.new([trecv.params[0]], nil, ti_map_case.canonical.ret) + eff = effect_union(eff, effi) + block = [map_block_type, e_map_case] + end + tres, effres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e) [envi, tres.canonical, effect_union(effres, eff) ] } @@ -881,21 +910,28 @@ def self.tc(scope, env, e) ## TODO: effects # very similar to send except the callee is the method's block error :no_block, [], e unless scope[:tblock] - error :block_block, [], e if scope[:tblock].block + error :block_block, [], e if scope[:tblock].is_a?(RDL::Type::MethodType) && scope[:tblock].block scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception envi = env tactuals = [] eff = [:+, :+] e.children[0..-1].each { |ei| envi, ti, effi = tc(scope, envi, ei); tactuals << ti ; eff = effect_union(effi, eff)} - unless tc_arg_types(scope[:tblock], tactuals) - msg = < meth_type }), ast: e) #tmeth_inter = [meth_type] #self_klass = nil #error :recv_var_type, [trecv], e return [[ret_type]] - ## MILOD TODO: Do we want to save the created MethodType some how? - ## this could be useful if the same method is called on a VarType - ## multiple times. But it's not exactly clear to me how we would save it. when RDL::Type::MethodType if meth == :call # Special case - invokes the Proc @@ -1645,12 +1695,12 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, comp_type = false if tmeth.is_a? RDL::Type::DynamicType trets_tmp << RDL::Type::DynamicType.new - elsif ((tmeth.block && block) || (tmeth.block.nil? && block.nil?)) + elsif ((tmeth.block && block) || (tmeth.block.nil? && block.nil?) || tmeth.block.is_a?(RDL::Type::VarType)) if trecv.is_a?(RDL::Type::FiniteHashType) && trecv.the_hash trecv = trecv.canonical inst = trecv.to_inst.merge(self: trecv) end - block_types = (if tmeth.block then tmeth.block.args + [tmeth.block.ret] else [] end) + block_types = (if tmeth.block.is_a?(RDL::Type::MethodType) then tmeth.block.args + [tmeth.block.ret] else [] end) unless (tmeth.args+[tmeth.ret]+block_types).all? { |t| !t.instance_of?(RDL::Type::ComputedType) } tmeth_old = tmeth trecv_old = trecv.copy @@ -1665,15 +1715,21 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, deferred_constraints = [] tmeth_inst = tc_arg_types(tmeth, tactuals_expanded, deferred_constraints) apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? - ## MILOD: Plan is to call tc_arg_types/Type.leq with deferred_constraints argument, then apply deferred_constraints if tmeth_inst effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block if es es = es.map { |es_effect| if es_effect.nil? then es_effect else es_effect.clone end } es.each { |es_effect| ## expecting just one effect per method right now. can clean this up later. if !es_effect.nil? && (es_effect[1] == :blockdep || es_effect[0] == :blockdep) - raise "Got block-dependent effect, but no block." unless block && effblock - if effblock[0] == :+ or effblock[0] == :~ + #raise "Got block-dependent effect for method #{meth}, but no block." unless block && effblock + if !(block && effblock) + ## In this case we called a block-dependent method, + ## but with no block. It could, e.g., return an enumerator. + ## Could have more intricate handling, but for now will just + ## be conseervative and return [:-, :-] + es_effect[0] = :- + es_effect[1] = :- + elsif effblock[0] == :+ or effblock[0] == :~ es_effect[1] = :+ es_effect[0] = :+ elsif effblock[0] == :- @@ -1914,7 +1970,7 @@ def self.tc_bind_arg_types(tmeth, tactuals) t = t.type if actual == tactuals.size states << [formal+1, actual, inst, binds] # skip over optinal formal - elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false) + elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false, []) states << [formal+1, actual+1, inst, binds] # match states << [formal+1, actual, inst, binds] # skip else @@ -1923,12 +1979,12 @@ def self.tc_bind_arg_types(tmeth, tactuals) when RDL::Type::VarargType if actual == tactuals.size states << [formal+1, actual, inst, binds] # skip to allow empty vararg at end - elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t.type, inst, false) + elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t.type, inst, false, []) states << [formal, actual+1, inst, binds] # match, more varargs coming states << [formal+1, actual+1, inst, binds] # match, no more varargs states << [formal+1, actual, inst, binds] # skip over even though matches - elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false) && - RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true) + elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, []) && + RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, []) states << [formal+1, actual+1, inst, binds] # match, no more varargs; no other choices! else states << [formal+1, actual, inst, binds] # doesn't match, must skip @@ -1959,7 +2015,7 @@ def self.tc_bind_arg_types(tmeth, tactuals) binds[t.name.to_sym] = tactuals[actual] t = t.type end - states << [formal+1, actual+1, inst, binds] if (t.is_a?(RDL::Type::ComputedType) || RDL::Type::Type.leq(tactuals[actual], t, inst, false))# match! + states << [formal+1, actual+1, inst, binds] if (t.is_a?(RDL::Type::ComputedType) || RDL::Type::Type.leq(tactuals[actual], t, inst, false, []))# match! # no else case; if there is no match, this is a dead end end end @@ -1977,7 +2033,7 @@ def self.tc_block(scope, env, tblock, block, inst) raise RuntimeError, "block with block arg?" unless tblock.block.nil? tblock = tblock.instantiate(inst) if block[0].is_a? RDL::Type::MethodType - error :bad_block_arg_type, [block[0], tblock], block[1] unless block[0] <= tblock + error :bad_block_arg_type, [block[0], tblock], block[1] unless RDL::Type::Type.leq(block[0], tblock, inst, false)#block[0] <= tblock elsif block[0].is_a?(RDL::Type::NominalType) && block[0].name == 'Proc' error :proc_block_arg_type, [tblock], block[1] else # must be [block-args, block-body] @@ -1988,7 +2044,7 @@ def self.tc_block(scope, env, tblock, block, inst) targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this env = env.merge(Env.new(targs_dup)) _, body_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], [:+, :+]] else tc(bscope, env.merge(Env.new(targs)), body) end - error :bad_return_type, [body_type, tblock.ret], body unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false) + error :bad_return_type, [body_type, tblock.ret], body unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false, ast: body) # eff } @@ -2082,7 +2138,7 @@ def self.lookup(scope, klass, name, e) end def self.make_unknown_method_type(klass, meth) - raise "Tried to make unknown method type for class #{klass} method #{meth}, but no such method was found." unless RDL::Util.to_class(klass).instance_methods.include?(meth) + raise "Tried to make unknown method type for class #{klass} method #{meth}, but no such method was found." unless (RDL::Util.to_class(klass).instance_methods + RDL::Util.to_class(klass).private_instance_methods).include?(meth) params = RDL::Util.to_class(klass).instance_method(meth).parameters req_params = params.select {|p| p[0] == :req} opt_params = params.select {|p| p[0] == :opt} @@ -2108,9 +2164,9 @@ def self.make_unknown_method_type(klass, meth) ret_vartype = RDL::Type::VarType.new(cls: klass, meth: meth, category: :ret, name: "ret") end - ## TODO: handling of block types - - meth_type = RDL::Type::MethodType.new(arg_types, nil, ret_vartype) + block_type = RDL::Type::VarType.new(cls: klass, meth: meth, category: :block, name: "block") + + meth_type = RDL::Type::MethodType.new(arg_types, block_type, ret_vartype) RDL::Globals.info.add(klass, meth, :type, meth_type) return meth_type end @@ -2129,7 +2185,7 @@ def self.filter_comp_types(ts, use_dep_types) ts.each { |typ| case typ when RDL::Type::MethodType - block_types = (if typ.block then typ.block.args + [typ.block.ret] else [] end) + block_types = (if typ.block.is_a?(RDL::Type::MethodType) then typ.block.args + [typ.block.ret] else [] end) typs = typ.args + block_types + [typ.ret] if typs.any? { |t| t.is_a?(RDL::Type::ComputedType) || (t.is_a?(RDL::Type::BoundArgType) && t.type.is_a?(RDL::Type::ComputedType)) } dep_ts << typ diff --git a/lib/rdl/types/finite_hash.rb b/lib/rdl/types/finite_hash.rb index 63fe7367..2af53fab 100644 --- a/lib/rdl/types/finite_hash.rb +++ b/lib/rdl/types/finite_hash.rb @@ -60,17 +60,9 @@ def match(other) def promote(key=nil, value=nil) return false if @cant_promote - # TODO look at key types - if key - domain_type = UnionType.new(*(@elts.keys.map { |k| NominalType.new(k.class) }), key) - else - domain_type = UnionType.new(*(@elts.keys.map { |k| NominalType.new(k.class) })) - end - if value - range_type = UnionType.new(*@elts.values, value) - else - range_type = UnionType.new(*@elts.values) - end + domain_type = (@elts.empty? && !key) ? RDL::Type::VarType.new(:k) : UnionType.new(*(@elts.keys.map { |k| NominalType.new(k.class) }), key) + range_type = (@elts.empty? && !value) ? RDL::Type::VarType.new(:v) : UnionType.new(*@elts.values, value) + if @rest domain_type = UnionType.new(domain_type, RDL::Globals.types[:symbol]) range_type = UnionType.new(range_type, @rest) @@ -105,8 +97,8 @@ def check_bounds(no_promote=false) return (@lbounds.all? { |lbound| lbound.<=(self, no_promote) }) && (@ubounds.all? { |ubound| self.<=(ubound, no_promote) }) end - def <=(other, no_constraint=false) - return Type.leq(self, other, no_constraint: no_constraint) + def <=(other, no_constraint=false, ast: nil) + return Type.leq(self, other, no_constraint: no_constraint, ast: ast) end def member?(obj, *args) diff --git a/lib/rdl/types/intersection.rb b/lib/rdl/types/intersection.rb index 21505afa..593b336d 100644 --- a/lib/rdl/types/intersection.rb +++ b/lib/rdl/types/intersection.rb @@ -36,9 +36,34 @@ def self.new(*types) def initialize(types) @types = types + @canonical = false + @canonicalized = false super() end + def canonical + canonicalize! + return @canonical if @canonical + return self + end + + def canonicalize! + return if @canonicalized + # for any type such that a subtype is already in ts, set its position to nil + for i in 0..(@types.length-1) + for j in (i+1)..(@types.length-1) + next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType)) || (@types[j].is_a?(VarType)) + (@types[j] = nil; break) if Type.leq(@types[i], @types[j], nil, true, []) + (@types[i] = nil) if Type.leq(@types[j], @types[i], nil, true, []) + end + end + @types.delete(nil) + @types.sort! { |a, b| a.object_id <=> b.object_id } # canonicalize order + @types.uniq! + @canonical = @types[0] if @types.size == 1 + @canonicalized = true + end + def to_s # :nodoc: return "(#{@types.map { |t| t.to_s }.join(' and ')})" end diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index 6d5550b8..67922d05 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -42,9 +42,9 @@ def initialize(args, block, ret) @args = *args if block.instance_of? OptionalType - raise "Block must be MethodType" unless block.type.is_a? MethodType + raise "Block must be MethodType" unless block.type.is_a? MethodType or block.type.is_a?(VarType) else - raise "Block must be MethodType" unless (not block) or (block.instance_of? MethodType) + raise "Block must be MethodType" unless (not block) or (block.instance_of? MethodType) or block.instance_of?(VarType) end @block = block @@ -302,6 +302,17 @@ def widen @ret.widen) end + def canonical + canonicalize! + return self + end + + def canonicalize! + @args.map { |a| a.canonical } + @block.canonical if @block + @ret.canonical + end + def copy return MethodType.new(@args.map { |arg| arg.copy }, @block ? @block.copy : nil, diff --git a/lib/rdl/types/tuple.rb b/lib/rdl/types/tuple.rb index c9ab3fa2..68453e1b 100644 --- a/lib/rdl/types/tuple.rb +++ b/lib/rdl/types/tuple.rb @@ -46,7 +46,7 @@ def match(other) def promote(t=nil) return false if @cant_promote - param = UnionType.new(*@params, t) + param = (@params.empty? && !t) ? RDL::Type::VarType.new(:t) : UnionType.new(*@params, t) param = param.widen if RDL::Config.instance.promote_widen GenericType.new(RDL::Globals.types[:array], param) end diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 9281fc38..7d1f5d2c 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -90,12 +90,12 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end # union - return left.types.all? { |t| leq(t, right, inst, ileft) } if left.is_a?(UnionType) + return left.types.all? { |t| leq(t, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) } if left.is_a?(UnionType) if right.instance_of?(UnionType) right.types.each { |t| # return true at first match, updating inst accordingly to first succeessful match new_inst = inst.dup unless inst.nil? - if leq(left, t, new_inst, ileft) + if leq(left, t, new_inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) inst.update(new_inst) unless inst.nil? return true end @@ -117,16 +117,26 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con lklass = left.klass end right.methods.each_pair { |m, t| - return false unless lklass.method_defined? m + return false unless lklass.method_defined?(m) || !(RDL::Globals.info.get(lklass, m, :type).empty?) ## Added the second condition because Rails lazily defines some methods. types = RDL::Globals.info.get(lklass, m, :type) if types - non_dep_types = RDL::Typecheck.filter_comp_types(types, false) - raise "Need non-dependent types for method #{m} of class #{lklass} in order to use a structural type." if non_dep_types.empty? - return false unless non_dep_types.all? { |tlm| leq(tlm, t, nil, ileft) } - # inst above is nil because the method types inside the class and - # inside the structural type have an implicit quantifier on them. So - # even if we're allowed to instantiate type variables we can't do that - # inside those types + #non_dep_types = RDL::Typecheck.filter_comp_types(types, false) + #raise "Need non-dependent types for method #{m} of class #{lklass} in order to use a structural type." if non_dep_types.empty? + return false unless types.all? { |tlm| + if (tlm.args + [tlm.ret]).any? { |t| t.is_a? ComputedType }## TODO: Include blocks. + ## In this case, need to actually evaluate the ComputedType. + ## Going to do this using the receiver `left` and the args from `t` + ## If subtyping holds for this, then we know `left` does indeed have a method of the relevant type. + computed_tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) + leq(computed_tlm.instantiate({ self: left }), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) + else + leq(tlm, t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) + # inst above is nil because the method types inside the class and + # inside the structural type have an implicit quantifier on them. So + # even if we're allowed to instantiate type variables we can't do that + # inside those types + end + } end } return true @@ -135,7 +145,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # singleton return left.val == right.val if left.is_a?(SingletonType) && right.is_a?(SingletonType) return true if left.is_a?(SingletonType) && left.val.nil? # right cannot be a SingletonType due to above conditional - return leq(left.nominal, right, inst, ileft) if left.is_a?(SingletonType) # fall through case---use nominal type for reasoning + return leq(left.nominal, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) if left.is_a?(SingletonType) # fall through case---use nominal type for reasoning # generic if left.is_a?(GenericType) && right.is_a?(GenericType) @@ -148,11 +158,11 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con return variance.zip(left.params, right.params).all? { |v, tl, tr| case v when :+ - leq(tl, tr, inst, ileft) + leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) when :- - leq(tr, tl, inst, !ileft) + leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) when :~ - leq(tl, tr, inst, ileft) && leq(tr, tl, inst, !ileft) + leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) && leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) else raise RuntimeError, "Unexpected variance #{v}" # shouldn't happen end @@ -165,10 +175,21 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con base_inst = Hash[*formals.zip(left.params).flatten] # instantiation for methods in base's class klass = left.base.klass right.methods.each_pair { |meth, t| - return false unless klass.method_defined? meth + return false unless klass.method_defined?(meth) || !(RDL::Globals.info.get(klass, meth, :type).empty?) ## Added the second condition because Rails lazily defines some methods. types = RDL::Globals.info.get(klass, meth, :type) if types - return false unless types.all? { |tlm| leq(tlm.instantiate(base_inst), t, nil, ileft) } + + return false unless types.all? { |tlm| + if (tlm.args + [tlm.ret]).any? { |t| t.is_a? ComputedType }## TODO: Include blocks. + ## In this case, need to actually evaluate the ComputedType. + ## Going to do this using the receiver `left` and the args from `t` + ## If subtyping holds for this, then we know `left` does indeed have a method of the relevant type. + computed_tlm = RDL::Typecheck.compute_types(tlm, klass, left, t.args) + leq(computed_tlm.instantiate({ self: left }), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) + else + leq(tlm.instantiate(base_inst), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) + end + } end } return true @@ -177,11 +198,16 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # method if left.is_a?(MethodType) && right.is_a?(MethodType) + if (left.args.size == 1) && left.args[0].is_a?(VarargType) + ## hack + new_args = Array.new(right.args.size, left.args[0].type) + left = RDL::Type::MethodType.new(new_args, left.block, left.ret) + end return false unless left.args.size == right.args.size - return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr, tl, inst, !ileft) } # contravariance - return false unless leq(left.ret, right.ret, inst, ileft) # covariance + return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) } # contravariance + return false unless leq(left.ret, right.ret, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # covariance if left.block && right.block - return leq(right.block, left.block, inst, !ileft) # contravariance + return leq(right.block, left.block, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # contravariance elsif left.block.nil? && right.block.nil? return true else @@ -195,7 +221,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # allow width subtyping - methods of right have to be in left, but not vice-versa return right.methods.all? { |m, t| # in recursive call set inst to nil since those method types have implicit quantifier - left.methods.has_key?(m) && leq(left.methods[m], t, nil, ileft) + left.methods.has_key?(m) && leq(left.methods[m], t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) } end # Note we do not allow a structural type to be a subtype of a nominal type or generic type, @@ -205,7 +231,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(TupleType) && right.is_a?(TupleType) # Tuples are immutable, so covariant subtyping allowed return false unless left.params.length == right.params.length - return false unless left.params.zip(right.params).all? { |lt, rt| leq(lt, rt, inst, ileft) } + return false unless left.params.zip(right.params).all? { |lt, rt| leq(lt, rt, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) } # subyping check passed left.ubounds << right unless no_constraint right.lbounds << left unless no_constraint @@ -214,7 +240,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(TupleType) && right.is_a?(GenericType) && right.base == RDL::Globals.types[:array] # TODO !ileft and right carries a free variable return false unless left.promote! - return leq(left, right, inst, ileft) # recheck for promoted type + return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # recheck for promoted type end # finite hash @@ -225,13 +251,20 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con left.elts.each_pair { |k, tl| if right_elts.has_key? k tr = right_elts[k] + if k == :action && tl.is_a?(RDL::Type::VarType) + if !$blah + $blah = true + else + #raise + end + end return false if tl.is_a?(OptionalType) && !tr.is_a?(OptionalType) # optional left, required right not allowed, since left may not have key tl = tl.type if tl.is_a? OptionalType tr = tr.type if tr.is_a? OptionalType - return false unless leq(tl, tr, inst, ileft) + return false unless leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) right_elts.delete k else - return false unless right.rest && leq(tl, right.rest, inst, ileft) + return false unless right.rest && leq(tl, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) end } right_elts.each_pair { |k, t| @@ -239,7 +272,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con } unless left.rest.nil? # If left has optional stuff, right needs to accept it - return false unless !(right.rest.nil?) && leq(left.rest, right.rest, inst, ileft) + return false unless !(right.rest.nil?) && leq(left.rest, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) end left.ubounds << right unless no_constraint right.lbounds << left unless no_constraint @@ -248,7 +281,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(FiniteHashType) && right.is_a?(GenericType) && right.base == RDL::Globals.types[:hash] # TODO !ileft and right carries a free variable return false unless left.promote! - return leq(left, right, inst, ileft) # recheck for promoted type + return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # recheck for promoted type end ## precise string diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index ce15d575..aad40d23 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -44,9 +44,10 @@ def canonicalize! # for any type such that a supertype is already in ts, set its position to nil for i in 0..(@types.length-1) for j in (i+1)..(@types.length-1) - next if (@types[j].nil?) || (@types[i].nil?) - (@types[i] = nil; break) if @types[i] <= @types[j] - (@types[j] = nil) if @types[j] <= @types[i] + next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType) && @types[i].to_infer) || (@types[j].is_a?(VarType) && @types[j].to_infer) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union + #next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType)) || (@types[j].is_a?(VarType)) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union + (@types[i] = nil; break) if Type.leq(@types[i], @types[j], {}, true, []) + (@types[j] = nil) if Type.leq(@types[j], @types[i], {}, true, []) end end @types.delete(nil) # eliminate any "deleted" elements diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index e7b738c6..e8193ca2 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -55,8 +55,6 @@ def add_and_propagate_upper_bound(typ, ast) if lower_t.is_a?(VarType) lower_t.add_and_propagate_upper_bound(typ, ast) else - ## TODO: More precise error messages. - #raise "Type error found." unless RDL::Type::Type.leq(lower_t, typ, ast: ast) unless RDL::Type::Type.leq(lower_t, typ, ast: ast) d1 = (Diagnostic.new :note, :infer_constraint_error, [lower_t.to_s], a.loc.expression).render.join("\n") d2 = (Diagnostic.new :note, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") @@ -74,10 +72,9 @@ def add_and_propagate_lower_bound(typ, ast) if upper_t.is_a?(VarType) upper_t.add_and_propagate_lower_bound(typ, ast) else - ## TODO: More precise error messages. unless RDL::Type::Type.leq(typ, upper_t, ast: ast) - d1 = (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") - d2 = (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") + d1 = ast.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") + d2 = a.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{typ} <= #{upper_t} generated during inference.\n #{d1}\n #{d2}") end end @@ -127,7 +124,6 @@ def instantiate(inst) end def widen - return inst[@name] if inst[@name] return self end diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 942e8a02..481e609f 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -692,7 +692,7 @@ def self.do_infer(sym) RDL::Globals.to_infer[sym] = Set.new nil RDL::Typecheck.resolve_constraints - RDL::Typecheck.extract_solution + RDL::Typecheck.extract_solutions #RDL::Globals.constrained_types = [] =begin puts "Here are the generated constraints: " @@ -785,11 +785,13 @@ def self.load_rails_schema add_ar_assoc(assoc, a.macro, a.name) if a.name.to_s.pluralize == a.name.to_s ## plural association ## This actually returns an Associations CollectionProxy, which is a descendant of ActiveRecord_Relation (see below actual type). This makes no difference in practice. - RDL.type model, a.name, "() -> ActiveRecord_Relation<#{a.name.to_s.camelize.singularize}>", wrap: false + RDL.type model, a.name, "() -> ActiveRecord_Relation<#{a.class_name}>", wrap: false + RDL.type model, "#{a.name}=", "(ActiveRecord_Relation<#{a.class_name}> or Array<#{a.class_name}>) -> ``targs[0]``", wrap: false #ActiveRecord_Associations_CollectionProxy<#{a.name.to_s.camelize.singularize}>' else ## association is singular, we just return an instance of associated class - RDL.type model, a.name, "() -> #{a.name.to_s.camelize.singularize}", wrap: false + RDL.type model, a.name, "() -> #{a.class_name}", wrap: false + RDL.type model, "#{a.name}=", "(#{a.class_name}) -> #{a.class_name}", wrap: false end } s2[:__associations] = RDL::Type::FiniteHashType.new(assoc, nil) diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index a45b4882..e3622108 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -10,6 +10,7 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_de res = RDL.type_cast(trec.elts.send(meth_name, *vals), "Object", force: true) if nil_default && res.nil? if default1 == :promoted_val + ret = trec.promote.params[1] return trec.promote.params[1] elsif default1 == :promoted_key return trec.promote.params[0] diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index ac636456..8df8194e 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -6,8 +6,8 @@ class ActiveRecord::Base type Object, :try, "(Symbol) -> Object", wrap: false type Object, :present?, "() -> %bool", wrap: false type :initialize, '(``DBType.rec_to_schema_type(trec, true)``) -> self', wrap: false - type 'self.create', '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false - type 'self.create!', '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false + type 'self.create', '(``DBType.rec_to_schema_type(trec, true, include_assocs: true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false + type 'self.create!', '(``DBType.rec_to_schema_type(trec, true, include_assocs: true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false type :initialize, '() -> self', wrap: false type 'self.create', '() -> ``DBType.rec_to_nominal(trec)``', wrap: false type 'self.create!', '() -> ``DBType.rec_to_nominal(trec)``', wrap: false @@ -246,9 +246,9 @@ class ActiveRecord_Relation type :each, '() { (t) -> %any } -> Array', wrap: false type :empty?, '() -> %bool', wrap: false type :present?, '() -> %bool', wrap: false - type :create, '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false + type :create, '(``DBType.rec_to_schema_type(trec, true, include_assocs: true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false type :create, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false - type :create!, '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false + type :create!, '(``DBType.rec_to_schema_type(trec, true, include_assocs: true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false type :create!, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false type :new, '(``DBType.rec_to_schema_type(trec, true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false type :new, '() -> ``DBType.rec_to_nominal(trec)``', wrap: false @@ -264,6 +264,7 @@ class ActiveRecord_Relation type :[], "(Integer) -> t", wrap: false type :size, "() -> Integer", wrap: false type :update_all, '(``DBType.rec_to_schema_type(trec, true)``) -> Integer', wrap: false + type :valid, "() -> self", wrap: false end @@ -304,7 +305,8 @@ def self.rec_to_array(trec) ## or a union of types if the receiver represents joined tables. ## [+ trec +] is the type of the receiver in the method call. ## [+ check_col +] is a boolean indicating whether or not the column types (i.e., values in the finite hash type) will be checked. - def self.rec_to_schema_type(trec, check_col, takes_array=false) + ## [+ include_assocs +] is a bool indicating whether or not to include associations in returned hash, e.g., for `create` method. + def self.rec_to_schema_type(trec, check_col, takes_array=false, include_assocs: false) case trec when RDL::Type::GenericType raise "Unexpected type #{trec}." unless (trec.base.klass == ActiveRecord_Relation) || (trec.base.klass == ActiveRecord::QueryMethods::WhereChain) @@ -314,19 +316,19 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false) ## should be JoinTable raise "unexpected type #{trec}" unless param.base.klass == JoinTable 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 - type_hash = table_name_to_schema_type(base_name, check_col, takes_array).elts + type_hash = table_name_to_schema_type(base_name, check_col, takes_array, include_assocs: include_assocs).elts pp1 = param.params[1] case pp1 when RDL::Type::NominalType ## just one table joined to base table joined_name = pp1.klass.to_s.singularize.to_sym - joined_type = RDL::Type::OptionalType.new(table_name_to_schema_type(joined_name, check_col, takes_array)) + joined_type = RDL::Type::OptionalType.new(table_name_to_schema_type(joined_name, check_col, takes_array, include_assocs: include_assocs)) type_hash = type_hash.merge({ joined_name.to_s.pluralize.underscore.to_sym => joined_type }) when RDL::Type::UnionType ## multiple tables joined to base table joined_hash = RDL.type_cast(Hash[pp1.types.map { |t| joined_name = RDL.type_cast(t, "RDL::Type::NominalType", force: true).klass.to_s.singularize.to_sym - joined_type = table_name_to_schema_type(joined_name, check_col, takes_array) + joined_type = table_name_to_schema_type(joined_name, check_col, takes_array, include_assocs: include_assocs) [joined_name.to_s.pluralize.underscore.to_sym, joined_type] } ], "Hash", force: true) @@ -336,7 +338,7 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false) return RDL::Type::FiniteHashType.new(type_hash, nil) when RDL::Type::NominalType tname = param.klass.to_s.to_sym - return table_name_to_schema_type(tname, check_col, takes_array) + return table_name_to_schema_type(tname, check_col, takes_array, include_assocs: include_assocs) else raise RDL::Typecheck::StaticTypeError, "Unexpected type parameter in #{trec}." end @@ -344,10 +346,10 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false) val = RDL.type_cast(trec.val, 'Class', force: true) raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}." unless val.is_a?(Class) tname = val.to_s.to_sym - return table_name_to_schema_type(tname, check_col, takes_array) + return table_name_to_schema_type(tname, check_col, takes_array, include_assocs: include_assocs) when RDL::Type::NominalType tname = trec.name.to_sym - return table_name_to_schema_type(tname, check_col, takes_array) + return table_name_to_schema_type(tname, check_col, takes_array, include_assocs: include_assocs) else raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}." end @@ -357,7 +359,7 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false) ## turns a given table name into the appropriate finite hash type based on table schema, with optional or top-type values ## [+ tname +] is the table name as a symbol ## [+ check_col +] is a boolean indicating whether or not column types will eventually be checked - def self.table_name_to_schema_type(tname, check_col, takes_array=false) + def self.table_name_to_schema_type(tname, check_col, takes_array=false, include_assocs: false) #h = RDL.type_cast({}, "Hash<%any, RDL::Type::Type>", force: true) ttype = RDL::Globals.ar_db_schema[tname] raise RDL::Typecheck::StaticTypeError, "No table type for #{tname} found." unless ttype @@ -370,6 +372,25 @@ def self.table_name_to_schema_type(tname, check_col, takes_array=false) [k, RDL::Type::OptionalType.new(RDL::Globals.types[:top])] end }] + if include_assocs + ## include associations in schema hash + table_class = Object.const_get(tname) + assoc_hash = {} + table_class.reflect_on_all_associations.each { |a| + if check_col + assoc_type = RDL::Type::NominalType.new(a.class_name) + if a.name.to_s == a.plural_name + ## association is plural + assoc_hash[a.name] = RDL::Type::OptionalType.new(RDL::Type::GenericType.new(RDL::Globals.types[:array], assoc_type)) + else + assoc_hash[a.name] = RDL::Type::OptionalType.new(assoc_type) + end + else + assoc_hash[a.name] = RDL::Type::OptionalType.new(RDL::Globals.types[:top]) + end + } + h.merge!(assoc_hash) + end RDL::Type::FiniteHashType.new(RDL.type_cast(h, "Hash<%any, RDL::Type::Type>", force: true), nil) end RDL.type DBType, 'self.table_name_to_schema_type', "(Symbol, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code, effect: [:+, :+] @@ -451,9 +472,7 @@ def self.find_output_type(trec, targs) else raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to ActiveRecord::Base#find." end - when RDL::Type::GenericType - RDL::Type::GenericType.new(RDL::Globals.types[:array], DBType.rec_to_nominal(trec)) - when RDL::Type::TupleType + when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::VarType RDL::Type::GenericType.new(RDL::Globals.types[:array], DBType.rec_to_nominal(trec)) else raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to ActiveRecord::Base#find." diff --git a/lib/types/sequel/comp_types.rb b/lib/types/sequel/comp_types.rb index 691ca201..699ae704 100644 --- a/lib/types/sequel/comp_types.rb +++ b/lib/types/sequel/comp_types.rb @@ -190,7 +190,7 @@ def self.join_ret_type(trec, targs) result_fht = RDL::Type::FiniteHashType.new(result_schema, nil) ## resulting schema as FiniteHashType return RDL::Type::GenericType.new(trec.base, result_fht) - when RDL::Type::NominalType + when RDL::Type::NominalType, RDL::Type::VarType ## TODO: this will catch case that first argument is a non-singleton Symbol raise "not implemented, likely not needed in practice" else @@ -244,6 +244,8 @@ def self.order_input(trec, targs) when RDL::Type::GenericType return RDL::Globals.types[:bot] unless a.base.name == "SeqQualIdent" check_qual_column(a, all_joined) + else + raise "unexpected arg type #{a}" end } return RDL::Type::VarargType.new(RDL::Type::UnionType.new(*targs)) @@ -435,6 +437,8 @@ def self.get_nominal_where_type(type) type = type.promote.params[0] raise "`where` passed tuple containing different types." if type.is_a?(RDL::Type::UnionType) type + when RDL::Type::VarType + raise "not implemented" else type end @@ -460,8 +464,13 @@ def self.schema_arg_type(trec, targs, meth) check_qual_column(cn, all_joined, type) else raise RDL::Typecheck::StaticTypeError, "No column #{column_name} for receiver #{trec}." unless receiver_schema.has_key?(cn) - type = get_nominal_where_type(type) if (meth == :where) - raise RDL::Typecheck::StaticTypeError, "Incompatible column types #{type} and #{receiver_schema[column_name]} for column #{cn} in call to #{meth}." unless RDL::Type::Type.leq(type, receiver_schema[cn]) + if meth == :where + type = get_nominal_where_type(type) + upper_type = RDL::Type::UnionType.new(receiver_schema[cn], RDL::Type::GenericType.new(RDL::Globals.types[:array], receiver_schema[cn])) + else + upper_type = receiver_schema[cn] + end + raise RDL::Typecheck::StaticTypeError, "Incompatible column types #{type} and #{receiver_schema[column_name]} for column #{cn} in call to #{meth}." unless RDL::Type::Type.leq(type, upper_type) end } return arg0 @@ -477,7 +486,7 @@ def self.schema_arg_type(trec, targs, meth) end RDL.type Table, 'self.schema_arg_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] - ## [+ column_name +] if a symbol or SeqQualIdent Generic type of the qualified column name, e.g. :person__age + ## [+ column_name +] is a symbol or SeqQualIdent Generic type of the qualified column name, e.g. :person__age ## [+ all_joined +] is an array of symbols of joined tables (must check if qualifying table is a member) ## [+ type +] is optional RDL type. If given, we check that it matches the type of the column in the schema. ## returns type of given column @@ -496,11 +505,12 @@ def self.check_qual_column(column_name, all_joined, type=nil) raise RDL::Typecheck::StaticTypeError, "qualified table #{qual_table} is not joined in receiver table, cannot reference its columns" unless all_joined.include?(qual_table) qual_table_schema = get_schema(RDL::Globals.seq_db_schema[qual_table].elts) raise RDL::Typecheck::StaticTypeError, "No column #{qual_column} in table #{qual_table}." if qual_table_schema[qual_column].nil? - if type + if type #&& !type.is_a?(RDL::Type::VarType) types = (if type.is_a?(RDL::Type::UnionType) then RDL.type_cast(type, "RDL::Type::UnionType", force: true).types else [type] end) types.each { |t| t = RDL.type_cast(t, "RDL::Type::GenericType", force: true).params[0] if t.is_a?(RDL::Type::GenericType) && (RDL.type_cast(t, "RDL::Type::GenericType", force: true).base == RDL::Globals.types[:array]) ## only happens if meth is where, don't need to check - raise RDL::Typecheck::StaticTypeError, "Incompatible column types. Given #{t} but expected #{qual_table_schema[qual_column]} for column #{column_name}." unless RDL::Type::Type.leq(t, qual_table_schema[qual_column]) + union_with_array = RDL::Type::UnionType.new(qual_table_schema[qual_column], RDL::Type::GenericType.new(RDL::Globals.types[:array], qual_table_schema[qual_column])) + raise RDL::Typecheck::StaticTypeError, "Incompatible column types. Given #{t} but expected #{qual_table_schema[qual_column]} for column #{column_name}." unless RDL::Type::Type.leq(t, union_with_array) } end return qual_table_schema[qual_column] @@ -524,8 +534,13 @@ def self.schema_arg_tuple_type(trec, targs, meth) check_qual_column(cn.val, all_joined, type) else raise RDL::Typecheck::StaticTypeError, "No column #{column_name} for receiver in call to `insert`." unless receiver_schema.has_key?(cn.val) - type = get_nominal_where_type(type) if (meth == :where) - raise RDL::Typecheck::StaticTypeError, "Incompatible column types." unless RDL::Type::Type.leq(type, receiver_schema[cn.val]) + if meth == :where + type = get_nominal_where_type(type) + upper_type = RDL::Type::UnionType.new(receiver_schema[cn.val], RDL::Type::GenericType.new(RDL::Globals.types[:array], receiver_schema[cn.val])) + else + upper_type = receiver_schema[cn.val] + end + raise RDL::Typecheck::StaticTypeError, "Incompatible column types." unless RDL::Type::Type.leq(type, upper_type) end } return targs[0] @@ -549,13 +564,22 @@ def self.where_arg_type(trec, targs, tuple=false) when RDL::Type::FiniteHashType raise "Ambigious identifier in call to where." unless unique_ids?(RDL.type_cast(arg0.elts.keys, "Array", force: true), trp0.elts[:__all_joined]) else - raise "unexpected arg type #{arg0}" + if arg0.is_a?(RDL::Type::GenericType) && arg0.base.name == "Hash" && arg0.params[0].is_a?(RDL::Type::GenericType) && arg0.params[0].base.name == "SeqQualIdent" + ## this case is for when the given column name is a Sequel Qualified Identifier (SeqQualIdent) + ## If so, we will convert it to a symbol. + check_qual_column(arg0.params[0], get_all_joined(trp0.elts[:__all_joined]), arg0.params[1]) + return arg0 + else + raise "unexpected arg type #{arg0} of kind #{arg0.class}" + end end end if tuple - schema_arg_tuple_type(trec, targs, :where) + ret = schema_arg_tuple_type(trec, targs, :where) + return ret else - schema_arg_type(trec, targs, :where) + ret = schema_arg_type(trec, targs, :where) + return ret end end RDL.type Table, 'self.where_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] From 5902e7e7609c6c3e4dbf7b9ce827a49f92d3fb19 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 30 Sep 2019 11:09:56 -0400 Subject: [PATCH 005/124] add support for heuristic inference rules. some bug fixes, refactoring. --- lib/rdl/boot.rb | 1 + lib/rdl/constraint.rb | 112 ++++++++++--- lib/rdl/heuristics.rb | 65 ++++++++ lib/rdl/info.rb | 7 + lib/rdl/typecheck.rb | 156 +++++++++++------- lib/rdl/types/method.rb | 2 +- lib/rdl/types/type.rb | 34 +++- lib/rdl/types/union.rb | 9 +- lib/rdl/types/var.rb | 24 ++- lib/rdl/wrap.rb | 12 +- lib/types/core/hash.rb | 19 ++- .../action_controller/instrumentation.rb | 1 - lib/types/rails/active_record/comp_types.rb | 37 +++-- lib/types/rails/active_record/model_schema.rb | 7 +- lib/types/sequel/comp_types.rb | 32 +++- 15 files changed, 393 insertions(+), 125 deletions(-) create mode 100644 lib/rdl/heuristics.rb diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index f6d9ec82..4434a8e9 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -150,6 +150,7 @@ class << RDL::Globals require 'rdl/query.rb' require 'rdl/typecheck.rb' require 'rdl/constraint.rb' +require 'rdl/heuristics.rb' #require_relative 'rdl/stats.rb' class << RDL::Globals diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 4b162873..fff558a0 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -7,15 +7,34 @@ def self.resolve_constraints ## If typ is an Array, then it's an array of method types ## but for inference, we only use a single method type. ## Otherwise, it's a single VarType for an instance/class var. - var_types = typ.is_a?(Array) ? typ[0].args + [typ[0].block, typ[0].ret] : [typ] - + if typ.is_a?(Array) + var_types = name == :initialize ? typ[0].args + [typ[0].block] : typ[0].args + [typ[0].block, typ[0].ret] + else + var_types = [typ] + end + var_types.each { |var_type| + if var_type.var_type? || var_type.optional_var_type? + var_type = var_type.type if var_type.is_a?(RDL::Type::OptionalType) var_type.lbounds.each { |lower_t, ast| var_type.add_and_propagate_lower_bound(lower_t, ast) } var_type.ubounds.each { |upper_t, ast| var_type.add_and_propagate_upper_bound(upper_t, ast) } + elsif var_type.fht_var_type? + var_type.elts.values.each { |v| + vt = v.is_a?(RDL::Type::OptionalType) ? v.type : v + vt.lbounds.each { |lower_t, ast| + vt.add_and_propagate_lower_bound(lower_t, ast) + } + vt.ubounds.each { |upper_t, ast| + vt.add_and_propagate_upper_bound(upper_t, ast) + } + } + else + raise "Got unexpected type #{var_type}." + end } } @@ -23,10 +42,24 @@ def self.resolve_constraints puts "practical inference!!!" RDL::Globals.constrained_types.each { |klass, name| typ = RDL::Globals.info.get(klass, name, :type) - var_types = typ.is_a?(Array) ? typ[0].args + [typ[0].block, typ[0].ret] : [typ] + if typ.is_a?(Array) + var_types = name == :initialize ? typ[0].args + [typ[0].block] : typ[0].args + [typ[0].block, typ[0].ret] + else + var_types = [typ] + end + var_types.each { |var_type| - puts "Applying struct_to_nominal to #{var_type}." - struct_to_nominal(var_type) + if var_type.fht_var_type? + var_type.elts.values.each { |v| + vt = v.is_a?(RDL::Type::OptionalType) ? v.type : v + puts "Applying struct_to_nominal to #{vt}." + struct_to_nominal(vt) + } + else + var_type = var_type.type if var_type.is_a?(RDL::Type::OptionalType) + puts "Applying struct_to_nominal to #{var_type}." + struct_to_nominal(var_type) + end } } end @@ -35,16 +68,16 @@ def self.resolve_constraints end def self.struct_to_nominal(var_type) - return unless var_type.category == :arg ## this rule only applies to args + return unless (var_type.category == :arg) || (var_type.category == :var)#(var_type.category == :ivar) || (var_type.category == :cvar) || (var_type.category == :gvar) ## this rule only applies to args and (instance/class/global) variables #return unless var_type.ubounds.all? { |t, loc| t.is_a?(RDL::Type::StructuralType) || t.is_a?(RDL::Type::VarType) } ## all upper bounds must be struct types or var types return unless var_type.ubounds.any? { |t, loc| t.is_a?(RDL::Type::StructuralType) } ## upper bounds must include struct type(s) struct_types = var_type.ubounds.select { |t, loc| t.is_a?(RDL::Type::StructuralType) } struct_types.map! { |t, loc| t } return if struct_types.empty? - meth_names = struct_types.map { |st| st.methods.keys }.flatten - matching_classes = ObjectSpace.each_object(Class).select { |c| (meth_names - c.instance_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods - + matching_classes = ObjectSpace.each_object(Class).select { |c| + class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) + (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods ## Because every object inherits from Object/BasicObject, ## we end up with some really weird issues regarding the eigenclasses ## of objects if we try to include all possible matching classes. @@ -55,12 +88,11 @@ def self.struct_to_nominal(var_type) matching_classes = [Object] end - ## TODO: not just look up classes with meth defined, but also classes - ## with type defined. e.g., `Table` is a made up class we use. ## TODO: special handling for arrays/hashes/generics? ## TODO: special handling for Rails models? see Bree's `active_record_match?` method raise "No matching classes found for structural types #{struct_types}." if matching_classes.empty? + return if matching_classes.size > 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } union = RDL::Type::UnionType.new(*nom_sing_types).canonical struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds @@ -70,33 +102,67 @@ def self.struct_to_nominal(var_type) def self.extract_var_sol(var, category) #raise "Expected VarType, got #{var}." unless var.is_a?(RDL::Type::VarType) - return var unless var.is_a?(RDL::Type::VarType) + return var.canonical unless var.is_a?(RDL::Type::VarType) if category == :arg non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } sol = non_vartype_ubounds.size == 1 ? non_vartype_ubounds[0] : RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical - return sol + #return sol elsif category == :ret non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } - return RDL::Type::UnionType.new(*non_vartype_lbounds).canonical + sol = RDL::Type::UnionType.new(*non_vartype_lbounds) + sol = sol.drop_vars!.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. + #return sol elsif category == :var if var.lbounds.empty? || (var.lbounds.size == 1 && var.lbounds[0][0] == RDL::Globals.types[:bot]) ## use upper bounds in this case. non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } - return RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + sol = RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + #return sol else ## use lower bounds non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } - return RDL::Type::UnionType.new(*non_vartype_lbounds).canonical + sol = RDL::Type::UnionType.new(*non_vartype_lbounds) + sol = sol.drop_vars!.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. + #return sol#RDL::Type::UnionType.new(*non_vartype_lbounds).canonical end else raise "Unexpected VarType category #{category}." end + + if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || sol.is_a?(RDL::Type::StructuralType) + ## Try each rule. Return first non-nil result. + ## If no non-nil results, return original solution. + ## TODO: check constraints. + RDL::Heuristic.rules.each { |name, rule| + #puts "Trying rule `#{name}` for variable #{var}." + typ = rule.call(var) + return typ if typ + } + return sol + else + return sol + end end def self.extract_meth_sol(tmeth) raise "Expected MethodType, got #{tmeth}." unless tmeth.is_a?(RDL::Type::MethodType) ## ARG SOLUTIONS - arg_sols = tmeth.args.map { |a| extract_var_sol(a, :arg) } + arg_sols = tmeth.args.map { |a| + if a.optional_var_type? + RDL::Type::OptionalType.new(extract_var_sol(a.type, :arg)) + elsif a.fht_var_type? + hash_sol = a.elts.transform_values { |v| + if v.is_a?(RDL::Type::OptionalType) + RDL::Type::OptionalType.new(extract_var_sol(v.type, :arg)) + else + extract_var_sol(v, :arg) + end + } + RDL::Type::FiniteHashType.new(hash_sol, nil) + else + extract_var_sol(a, :arg) + end + } ## BLOCK SOLUTION if tmeth.block && !tmeth.block.ubounds.empty? @@ -113,13 +179,14 @@ def self.extract_meth_sol(tmeth) end ## RET SOLUTION - ret_sol = extract_var_sol(tmeth.ret, :ret) + ret_sol = tmeth.ret.is_a?(RDL::Type::VarType) ? extract_var_sol(tmeth.ret, :ret) : tmeth.ret return [arg_sols, block_sol, ret_sol] end def self.extract_solutions puts "Starting solution extraction..." + ## Go through once to come up with solution for all var types. RDL::Globals.constrained_types.each { |klass, name| typ = RDL::Globals.info.get(klass, name, :type) if typ.is_a?(Array) @@ -130,18 +197,19 @@ def self.extract_solutions block_string = block_sol ? " { #{block_sol} }" : nil puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" + elsif name.to_s == "splat_param" else - ## Instance/Class variables: + ## Instance/Class (also some times splat parameter) variables: ## There is no clear answer as to what to do in this case. ## Just need to pick something in between bounds (inclusive). ## For now, plan is to just use lower bound when it's not empty/%bot, ## otherwise use upper bound. ## Can improve later if desired. var_sol = extract_var_sol(typ, :var) - puts "Extracted solution for #{var} is #{var_sol}." + #typ.solution = var_sol + + puts "Extracted solution for #{typ} is #{var_sol}." end } end - - end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb new file mode 100644 index 00000000..42a7d9d9 --- /dev/null +++ b/lib/rdl/heuristics.rb @@ -0,0 +1,65 @@ +class RDL::Heuristic + + @rules = {} + + def self.add(name, &blk) + raise RuntimeError, "Expected heuristic name to be Symbol, given #{name}." unless name.is_a? Symbol + raise RuntimeError, "Expected block to be provided for heuristic." if blk.nil? + @rules[name] = blk + end + +end + +class << RDL::Heuristic + attr_reader :rules +end + +class String + def is_rails_model? + return false unless defined? Rails + ActiveRecord::Base.descendants.map { |d| d.to_s }.include? self + end + + def to_type + camelized = self.camelize + RDL::Type::NominalType.new(RDL::Util.to_class(camelized)) + end + + def is_pluralized_model? + return false unless defined? Rails + return false unless pluralize == self + is_rails_model? + end + + def model_set_type + RDL::Globals.parser.scan_str "#T Array<#{singularize}> or ActiveRecord_Relation<#{singularize}>" + end + +end + + +RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } +RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } +RDL::Heuristic.add(:int_names) { |var| if var.base_name.end_with?("id") || (var.base_name == "num") || (var.base_name == "count") then RDL::Globals.types[:integer] end } +RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") then RDL::Globals.parser.scan_str "#T Array" end } +RDL::Heuristic.add(:predicate_method) { |var| if var.meth.to_s.end_with?("?") && var.category == :ret then RDL::Globals.types[:bool] end } +RDL::Heuristic.add(:hash_access) { |var| + if !var.ubounds.empty? && var.ubounds.all? { |u| u.is_a?(RDL::Type::StructuralType) && u.methods.all? { |meth, typ| ((meth == :[]) || (meth == :[]=)) && typ.args.all? { |t| t.is_a?(RDL::Type::SingletonType) && t.val.is_a?(Symbol) } } } + hash_typ = {} + var.ubounds.each { |struct| + struct.methods.each { |meth, typ| + if meth == :[] + hash_typ[typ.args[0].val] = typ.ret ## TODO: if ret is var type, should we extract solution first? + elsif meth == :[]= + hash_typ[typ.args[0].val] = hash_typ[typ.args[1]] + else + raise "Method should be one of :[] or :[]=, got #{meth}." + end + } + } + RDL::Type::FiniteHashType.new(hash_typ, nil) + end +} + + +### For rules involving :include?, :==, :!=, etc. we would need to track the exact receiver/args used in the method call, and somehow store these in the bounds created for a var type. diff --git a/lib/rdl/info.rb b/lib/rdl/info.rb index f2619ccd..fc43a6e6 100644 --- a/lib/rdl/info.rb +++ b/lib/rdl/info.rb @@ -87,4 +87,11 @@ def remove(klass, label, kind) @info[klass][label].delete kind end + def get_methods_from_class(klass) + klass = klass.to_s + return [] unless @info[klass] + a = @info[klass].keys.reject { |k| !@info[klass][k][:type].is_a?(Array) } + return a + end + end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 016117a9..06e5b759 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -150,9 +150,9 @@ def self.scope_merge(scope, **elts) end # add x:t to the captured map in scope - def self.capture(scope, x, t) + def self.capture(scope, x, t, ast: nil) if scope[:captured][x] - if !RDL::Type::Type.leq(t, scope[:captured][x], inst={}, true)#t <= scope[:captured][x] + if !RDL::Type::Type.leq(t, scope[:captured][x], inst={}, true, ast: ast)#t <= scope[:captured][x] scope[:captured][x] = RDL::Type::UnionType.new(scope[:captured][x], t.instantiate(inst)).canonical #unless RDL::Type::Type.leq(t, scope[:captured][x], inst={}, true)#t <= scope[:captured][x] end else @@ -232,16 +232,12 @@ def self.infer(klass, meth) ## it had better be composed of VarTypes so we can infer something. raise "Expected just one type composed of VarTypes for method to be inferred." if types.size > 1 meth_type = types[0] - raise "Block params not yet supported." if meth_type.block - - (meth_type.args + [meth_type.ret]).each { |t| - if !t.is_a?(RDL::Type::VarType) - raise "Expected VarType in MethodType to be inferred, got #{t}." unless t.is_a?(RDL::Type::OptionalType) && t.type.is_a?(RDL::Type::VarType) - end - } + arg_types = meth_type.args block_type = meth_type.block ret_vartype = meth_type.ret + + raise "Expected VarTypes in MethodType to be inferred, got #{t}." unless (arg_types + [block_type] + [ret_vartype]).all? { |t| t.kind_of_var_input? } end if ast.type == :def @@ -481,7 +477,7 @@ def self.args_hash(scope, env, type, args, ast, kind) tkw = targ.elts[kw] error :type_args_kw_mismatch, [kind, 'required', kw, 'optional'], arg if !tkw.is_a?(RDL::Type::OptionalType) env, default_type = tc(scope, env, arg.children[1]) - error :optional_default_kw_type, [kw, default_type, tkw.type], arg.children[1] unless RDL::Type::Type(default_type, tkw.type, ast: ast) + error :optional_default_kw_type, [kw, default_type, tkw.type], arg.children[1] unless RDL::Type::Type.leq(default_type, tkw.type, ast: ast) kw_args_matched << kw targs[kw] = tkw.type env = env.merge(Env.new(kw => tkw.type)) @@ -836,6 +832,28 @@ def self.tc(scope, env, e) case c when TrueClass, FalseClass, Complex, Rational, Integer, Float, Symbol, Class, Module [env, RDL::Type::SingletonType.new(c), [:+, :+]] + when Hash + fh = c.transform_keys { |k| + case k + when Symbol + k ## symbol keys in FHTs are used directly + when TrueClass, FalseClass, Complex, Rational, Integer, Float, Class, Module + RDL::Type::SingletonType.new(k) + else + RDL::Type::NominalType.new(k.class) + end + } + + fh = fh.transform_values { |v| + case v + when TrueClass, FalseClass, Complex, Rational, Integer, Float, Symbol, Class, Module + RDL::Type::SingletonType.new(v) + else + RDL::Type::NominalType.new(v.class) + end + } + + [env, RDL::Type::FiniteHashType.new(fh, nil), [:+, :+]] else [env, RDL::Type::NominalType.new(c.class), [:+, :+]] end @@ -864,6 +882,11 @@ def self.tc(scope, env, e) tactuals.concat ti.params elsif ti.is_a?(RDL::Type::GenericType) && ti.base == RDL::Globals.types[:array] tactuals << RDL::Type::VarargType.new(ti.params[0]) # Turn Array into *t + elsif ti.is_a?(RDL::Type::VarType) + new_arr_type = RDL::Type::GenericType.new(RDL::Globals.types[:array], v = make_unknown_var_type(ti, :splat_param, "splat param")) + RDL::Type::Type.leq(ti, new_arr_type, ast: e) + tactuals << RDL::Type::VarargType.new(v) + #tactuals << RDL::Type::VarargType.new(RDL::Globals.types[:top]) else error :cant_splat, [ti], ei.children[0] end @@ -947,6 +970,10 @@ def self.tc(scope, env, e) else # e.type == :or if tleft.val then [envleft, tleft, effleft] else [envright, tright, effright] end end + elsif e.type == :and && !(tleft == RDL::Globals.types[:bool] || tleft == RDL::Globals.types[:false]) + ## when :and and left is NOT false, then we always get back tright (or nil, which is a subtype of tright) + ## no equivalent for :or, because if left is nil, could still get right + [Env.join(e, envleft, envright), tright, effect_union(effleft, effright)] else [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical, effect_union(effleft, effright)] end @@ -1317,20 +1344,21 @@ def self.tc_var(scope, env, kind, name, e) case kind when :lvar # local variable error :undefined_local_or_method, [name], e unless env.has_key? name - capture(scope, name, env[name].canonical) if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name)) + capture(scope, name, env[name].canonical, ast: e) if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name)) if scope[:captured] && scope[:captured].has_key?(name) then [env, scope[:captured][name]] else [env, env[name].canonical] end when :ivar, :cvar, :gvar - klass = (if kind == :gvar then RDL::Util::GLOBAL_NAME else env[:self] end) + klass = (if kind == :gvar then RDL::Util::GLOBAL_NAME else env[:self].to_s end) + klass = RDL::Util.remove_singleton_marker klass if RDL::Util.has_singleton_marker klass if RDL::Globals.info.has?(klass, name, :type) type = RDL::Globals.info.get(klass, name, :type) elsif RDL::Config.instance.assume_dyn_type type = RDL::Globals.types[:dyn] elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } - type = make_unknown_var_type(klass, name, kind_text) + type = make_unknown_var_type(klass, name, :var) else error :untyped_var, [kind_text, name, klass], e end @@ -1351,7 +1379,7 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) when :lvasgn if ((scope[:captured] && scope[:captured].has_key?(name)) || (scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name)))) - capture(scope, name, tright.canonical) + capture(scope, name, tright.canonical, ast: e) [env, scope[:captured][name]] elsif (env.fixed? name) error :vasgn_incompat, [tright, env[name]], e unless RDL::Type::Type.leq(tright, env[name], inst={}, true, ast: e) @@ -1361,13 +1389,14 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) [env.bind(name, tright), tright.canonical] end when :ivasgn, :cvasgn, :gvasgn - klass = (if kind == :gvasgn then RDL::Util::GLOBAL_NAME else env[:self] end) + klass = (if kind == :gvasgn then RDL::Util::GLOBAL_NAME else env[:self].to_s end) + klass = RDL::Util.remove_singleton_marker klass if RDL::Util.has_singleton_marker klass if RDL::Globals.info.has?(klass, name, :type) tleft = RDL::Globals.info.get(klass, name, :type) elsif RDL::Config.instance.assume_dyn_type tleft = RDL::Globals.types[:dyn] elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } - type = make_unknown_var_type(klass, name, kind_text) + type = make_unknown_var_type(klass, name, :var) else error :untyped_var, [kind_text, name, klass], e @@ -1691,6 +1720,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, RDL::Type.expand_product(tactuals).each { |tactuals_expanded| # AT LEAST ONE of the possible intesection arms must match trets_tmp = [] + deferred_constraints = [] ts.each_with_index { |tmeth, ind| # MethodType comp_type = false if tmeth.is_a? RDL::Type::DynamicType @@ -1712,9 +1742,9 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, end tmeth = tmeth.instantiate(inst) if inst tmeth_names << tmeth - deferred_constraints = [] + #deferred_constraints = [] tmeth_inst = tc_arg_types(tmeth, tactuals_expanded, deferred_constraints) - apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? + #apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? if tmeth_inst effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block if es @@ -1771,6 +1801,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, trets = [] break else + apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? trets.concat(trets_tmp) end } @@ -1800,7 +1831,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, end def self.apply_deferred_constraints(deferred_constraints, e) - if deferred_constraints.all? { |t1, t2| t1.equal?(deferred_constraints[0][0]) && t2.is_a?(RDL::Type::NominalType) && t2 <= RDL::Globals.types[:numeric] } + if deferred_constraints.size > 2 && deferred_constraints.all? { |t1, t2| t1.equal?(deferred_constraints[0][0]) && t2.is_a?(RDL::Type::NominalType) && t2 <= RDL::Globals.types[:numeric] } ## This is a temporary hack for Numeric types. ## If all the LHS types are the same single type, and all the RHS types ## are Numeric types (which is the case for almost all arithmetic methods), @@ -1823,7 +1854,7 @@ def self.compute_types(tmeth, self_klass, trecv, tactuals, binds={}) self_klass.class_eval { bind = binding() } bind.local_variable_set(:trec, trecv) bind.local_variable_set(:targs, tactuals) - binds.each { |name, t| bind.local_variable_set(name, t) } + binds.each { |name, t| bind.local_variable_set(name, t) } unless binds.nil? new_args = [] tmeth.args.each { |targ| case targ @@ -1925,9 +1956,10 @@ def self.tc_arg_types(tmeth, tactuals, deferred_constraints=[]) states << [formal, actual+1, inst, deferred_constraints] # match, more varargs coming states << [formal+1, actual+1, inst, deferred_constraints] # match, no more varargs states << [formal+1, actual, inst, deferred_constraints] # skip over even though matches - elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, deferred_constraints) && - RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, deferred_constraints) + elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, deferred_constraints) #&& + #RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, deferred_constraints) states << [formal+1, actual+1, inst, deferred_constraints] # match, no more varargs; no other choices! + states << [formal, actual+1, inst, deferred_constraints] else states << [formal+1, actual, inst, deferred_constraints] # doesn't match, must skip end @@ -1983,9 +2015,10 @@ def self.tc_bind_arg_types(tmeth, tactuals) states << [formal, actual+1, inst, binds] # match, more varargs coming states << [formal+1, actual+1, inst, binds] # match, no more varargs states << [formal+1, actual, inst, binds] # skip over even though matches - elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, []) && - RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, []) + elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, []) #&& + #RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, []) states << [formal+1, actual+1, inst, binds] # match, no more varargs; no other choices! + states << [formal, actual+1, inst, binds] else states << [formal+1, actual, inst, binds] # doesn't match, must skip end @@ -2033,7 +2066,7 @@ def self.tc_block(scope, env, tblock, block, inst) raise RuntimeError, "block with block arg?" unless tblock.block.nil? tblock = tblock.instantiate(inst) if block[0].is_a? RDL::Type::MethodType - error :bad_block_arg_type, [block[0], tblock], block[1] unless RDL::Type::Type.leq(block[0], tblock, inst, false)#block[0] <= tblock + error :bad_block_arg_type, [block[0], tblock], block[1] unless RDL::Type::Type.leq(block[0], tblock, inst, false, ast: block[1])#block[0] <= tblock elsif block[0].is_a?(RDL::Type::NominalType) && block[0].name == 'Proc' error :proc_block_arg_type, [tblock], block[1] else # must be [block-args, block-body] @@ -2076,6 +2109,7 @@ def self.lookup(scope, klass, name, e) t = RDL::Globals.info.get_with_aliases(klass, name, :type) e = RDL::Globals.info.get_with_aliases(klass, name, :effect) return [t, e] if t # simplest case, no need to walk inheritance hierarchy + return [[make_unknown_method_type(klass, name)]] if RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } the_klass = RDL::Util.to_class(klass) is_singleton = RDL::Util.has_singleton_marker(klass) included = RDL::Util.to_class(klass.gsub("[s]", "")).included_modules @@ -2093,6 +2127,7 @@ def self.lookup(scope, klass, name, e) tancestor = RDL::Globals.info.get_with_aliases(anc_lookup, name, :type) eancestor = RDL::Globals.info.get_with_aliases(anc_lookup, name, :effect) return [tancestor, eancestor] if tancestor + return [[make_unknown_method_type(anc_lookup, name)]] if RDL::Globals.to_infer.values.any? { |set| set.include?([anc_lookup, name]) } # special caes: Kernel's singleton methods are *also* added when included?! if ancestor == Kernel tancestor = RDL::Globals.info.get_with_aliases(RDL::Util.add_singleton_marker('Kernel'), name, :type) @@ -2129,9 +2164,6 @@ def self.lookup(scope, klass, name, e) ret = RDL::Type::NominalType.new(the_klass) if name == :initialize return [[RDL::Type::MethodType.new(args, nil, ret)]] - elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } - ## method types are to be inferred - return [[make_unknown_method_type(klass, name)]] else return nil end @@ -2140,41 +2172,53 @@ def self.lookup(scope, klass, name, e) def self.make_unknown_method_type(klass, meth) raise "Tried to make unknown method type for class #{klass} method #{meth}, but no such method was found." unless (RDL::Util.to_class(klass).instance_methods + RDL::Util.to_class(klass).private_instance_methods).include?(meth) params = RDL::Util.to_class(klass).instance_method(meth).parameters - req_params = params.select {|p| p[0] == :req} - opt_params = params.select {|p| p[0] == :opt} - blk_params = params.select {|p| p[0] == :block} - vararg_params = params.select {|p| p[0] == :rest} - - raise "Block/VarArg params not yet supported" if !blk_params.empty? || !vararg_params.empty? - req_arg_vartypes = req_params.map { |param| - RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1]) - } - opt_arg_vartypes = opt_params.map { |param| - RDL::Type::OptionatlType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) - } - - vararg_vartypes = vararg_params.map { |param| - RDL::Type::VarargType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) } - arg_types = req_arg_vartypes + opt_arg_vartypes + vararg_vartypes - - if meth == :initialize - ret_vartype = RDL::Type::VarType.new(:self) ## TODO: is this right? Or should it include klass/meth info? + arg_types = [] + keyword_args = {} + params.each { |param| + case param[0] + when :req + arg_types << RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1]) + when :opt + arg_types << RDL::Type::OptionalType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) + when :rest + arg_types << RDL::Type::VarargType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) + when :key + keyword_args[param[1]] = RDL::Type::OptionalType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) + when :keyreq + keyword_args[param[1]] = RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1]) + when :block + ## all method types will be given a variable type for blocks anyway, so no need to add a new param here else - ret_vartype = RDL::Type::VarType.new(cls: klass, meth: meth, category: :ret, name: "ret") + raise "Unexpected parameter type #{param[0]}." + end + } + keyword_args = keyword_args.empty? ? [] : [RDL::Type::FiniteHashType.new(keyword_args, nil)] + arg_types = arg_types + keyword_args + if meth == :initialize + if RDL::Util.has_singleton_marker(klass) + # to_class gets the class object itself, so remove singleton marker to get class rather than singleton class + ret_vartype = RDL::Type::SingletonType.new(RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass))) + else + ret_vartype = RDL::Type::NominalType.new(klass) end - - block_type = RDL::Type::VarType.new(cls: klass, meth: meth, category: :block, name: "block") - meth_type = RDL::Type::MethodType.new(arg_types, block_type, ret_vartype) - RDL::Globals.info.add(klass, meth, :type, meth_type) - return meth_type + #ret_vartype = RDL::Type::VarType.new(:self) ## TODO: is this right? Or should it include klass/meth info? + else + ret_vartype = RDL::Type::VarType.new(cls: klass, meth: meth, category: :ret, name: "ret") + end + + block_type = RDL::Type::VarType.new(cls: klass, meth: meth, category: :block, name: "block") + + meth_type = RDL::Type::MethodType.new(arg_types, block_type, ret_vartype) + RDL::Globals.info.add(klass, meth, :type, meth_type) + return meth_type end - def make_unknown_var_type(klass, name, kind_text) + def self.make_unknown_var_type(klass, name, kind_text) var_type = RDL::Type::VarType.new(cls: klass, name: name, category: kind_text) - RDL::Globals.info.add(klass, name, :type, var_type) - RDL::Globals.constrained_types << [klass, var] + RDL::Globals.info.set(klass, name, :type, var_type) + RDL::Globals.constrained_types << [klass, name] return var_type end diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index 67922d05..4f88f1d9 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -24,7 +24,7 @@ def initialize(args, block, ret) case arg when OptionalType raise "Optional arguments not allowed after varargs" if state == :vararg - raise "Optional arguments not allowed after named arguments" if state == :hash + #raise "Optional arguments not allowed after named arguments" if state == :hash state = :optional when VarargType raise "Multiple varargs not allowed" if state == :vararg diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 7d1f5d2c..cafa6deb 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -24,6 +24,22 @@ def nil_type? is_a?(SingletonType) && @val.nil? end + def var_type? + is_a?(VarType) + end + + def optional_var_type? + is_a?(OptionalType) && @type.var_type? + end + + def fht_var_type? + is_a?(FiniteHashType) && @elts.keys.all? { |k| k.is_a?(Symbol) } && @elts.values.all? { |v| v.optional_var_type? || v.var_type? } + end + + def kind_of_var_input? + var_type? || optional_var_type? || fht_var_type? + end + # default behavior, override in appropriate subclasses def canonical; return self; end def optional?; return false; end @@ -117,13 +133,14 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con lklass = left.klass end right.methods.each_pair { |m, t| - return false unless lklass.method_defined?(m) || !(RDL::Globals.info.get(lklass, m, :type).empty?) ## Added the second condition because Rails lazily defines some methods. + return false unless lklass.method_defined?(m) || RDL::Globals.info.get(lklass, m, :type) ## Added the second condition because Rails lazily defines some methods. types = RDL::Globals.info.get(lklass, m, :type) if types #non_dep_types = RDL::Typecheck.filter_comp_types(types, false) #raise "Need non-dependent types for method #{m} of class #{lklass} in order to use a structural type." if non_dep_types.empty? - return false unless types.all? { |tlm| - if (tlm.args + [tlm.ret]).any? { |t| t.is_a? ComputedType }## TODO: Include blocks. + return false unless types.any? { |tlm| + blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] + if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } ## In this case, need to actually evaluate the ComputedType. ## Going to do this using the receiver `left` and the args from `t` ## If subtyping holds for this, then we know `left` does indeed have a method of the relevant type. @@ -175,12 +192,13 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con base_inst = Hash[*formals.zip(left.params).flatten] # instantiation for methods in base's class klass = left.base.klass right.methods.each_pair { |meth, t| - return false unless klass.method_defined?(meth) || !(RDL::Globals.info.get(klass, meth, :type).empty?) ## Added the second condition because Rails lazily defines some methods. + return false unless klass.method_defined?(meth) || RDL::Globals.info.get(klass, meth, :type) ## Added the second condition because Rails lazily defines some methods. types = RDL::Globals.info.get(klass, meth, :type) if types - return false unless types.all? { |tlm| - if (tlm.args + [tlm.ret]).any? { |t| t.is_a? ComputedType }## TODO: Include blocks. + return false unless types.any? { |tlm| + blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] + if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } ## In this case, need to actually evaluate the ComputedType. ## Going to do this using the receiver `left` and the args from `t` ## If subtyping holds for this, then we know `left` does indeed have a method of the relevant type. @@ -210,6 +228,10 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con return leq(right.block, left.block, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # contravariance elsif left.block.nil? && right.block.nil? return true + elsif left.block.is_a?(VarType) || right.block.is_a?(VarType) + ## methods to be inferred all have a VarType for a block type, so it's okay if + ## the other method type doesn't have a block. + return true else return false # one has a block and the other doesn't end diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index aad40d23..d097fae2 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -35,6 +35,7 @@ def initialize(types) def canonical canonicalize! + return RDL::Globals.types[:bot] if types.size == 0 return @canonical if @canonical return self end @@ -52,14 +53,20 @@ def canonicalize! end @types.delete(nil) # eliminate any "deleted" elements @types.sort! { |a, b| a.object_id <=> b.object_id } # canonicalize order + @types.map { |t| t.canonical } @types.uniq! @canonical = @types[0] if @types.size == 1 @canonicalized = true end + def drop_vars! + @types.reject! { |t| t.is_a? VarType } + self + end + def to_s # :nodoc: return @canonical.to_s if @canonical - return "#{@types.map { |t| t.to_s }.join(' or ')}" + return "(#{@types.map { |t| t.to_s }.join(' or ')})" end def ==(other) # :nodoc: diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index e8193ca2..c099d9da 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -1,7 +1,7 @@ module RDL::Type class VarType < Type attr_reader :name, :cls, :meth, :category, :to_infer - attr_accessor :lbounds, :ubounds + attr_accessor :lbounds, :ubounds, :solution @@cache = {} @@ -36,6 +36,7 @@ def initialize(name_or_hash) @to_infer = true @lbounds = [] @ubounds = [] + @solution = nil @cls = name_or_hash[:cls] @name = name_or_hash[:name] ## might be nil if category is :ret @@ -55,9 +56,10 @@ def add_and_propagate_upper_bound(typ, ast) if lower_t.is_a?(VarType) lower_t.add_and_propagate_upper_bound(typ, ast) else - unless RDL::Type::Type.leq(lower_t, typ, ast: ast) - d1 = (Diagnostic.new :note, :infer_constraint_error, [lower_t.to_s], a.loc.expression).render.join("\n") - d2 = (Diagnostic.new :note, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") + puts "1. ABOUT TO COMPARE #{lower_t} TO #{typ} " if @meth == :random_secrets + unless RDL::Type::Type.leq(lower_t, typ, {}, false, ast: ast, no_constraint: true) + d1 = a.nil? ? "" : (Diagnostic.new :note, :infer_constraint_error, [lower_t.to_s], a.loc.expression).render.join("\n") + d2 = ast.nil? ? "" : (Diagnostic.new :note, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{lower_t} <= #{typ} generated during inference.\n #{d1}\n #{d2}") end end @@ -72,7 +74,8 @@ def add_and_propagate_lower_bound(typ, ast) if upper_t.is_a?(VarType) upper_t.add_and_propagate_lower_bound(typ, ast) else - unless RDL::Type::Type.leq(typ, upper_t, ast: ast) + puts "2. ABOUT TO COMPARE #{typ} TO #{upper_t} " if @meth == :random_secrets + unless RDL::Type::Type.leq(typ, upper_t, {}, false, ast: ast, no_constraint: true) d1 = ast.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") d2 = a.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{typ} <= #{upper_t} generated during inference.\n #{d1}\n #{d2}") @@ -83,12 +86,21 @@ def add_and_propagate_lower_bound(typ, ast) def to_s # :nodoc: if @to_infer - return "{ #{@cls}##{@meth} #{@category}: #{@name} }" + if @solution + return @solution.to_s + else + return "{ #{@cls}##{@meth} #{@category}: #{@name} }" + end else return @name.to_s end end + def base_name + return nil unless @name + @name.to_s.delete("@") + end + def ==(other) return false if other.nil? other = other.canonical diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 481e609f..ac5410cf 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -473,7 +473,7 @@ def infer_var_type(klass=self, var) raise RuntimeError, "Variable cannot begin with capital" if var.to_s =~ /^[A-Z]/ return if var.to_s =~ /^[a-z]/ # local variables handled specially, inside type checker klass = RDL::Util::GLOBAL_NAME if var.to_s =~ /^\$/ - unless RDL::Globals.info.set(klass, var, :type, RDL::Type::VarType.new(cls: klass, name: var, category: "variable")) + unless RDL::Globals.info.set(klass, var, :type, RDL::Type::VarType.new(cls: klass, name: var, category: :var)) raise RuntimeError, "Type already declared for #{var}" end ## RDL::Globals.constrained_types includes the list of all types we want to perform constraint resolution/ @@ -756,6 +756,7 @@ def self.load_rails_schema end } models.each { |model| + next if !model.table_exists? next if model.to_s == "ApplicationRecord" next if model.to_s == "GroupManager" RDL.nowrap model @@ -781,17 +782,18 @@ def self.load_rails_schema s2 = s1.transform_keys { |k| k.to_sym } assoc = {} model.reflect_on_all_associations.each { |a| + class_name = a.class_name.starts_with?("::") ? a.class_name[2..-1] : a.class_name ## Generate method types based on associations add_ar_assoc(assoc, a.macro, a.name) if a.name.to_s.pluralize == a.name.to_s ## plural association ## This actually returns an Associations CollectionProxy, which is a descendant of ActiveRecord_Relation (see below actual type). This makes no difference in practice. - RDL.type model, a.name, "() -> ActiveRecord_Relation<#{a.class_name}>", wrap: false - RDL.type model, "#{a.name}=", "(ActiveRecord_Relation<#{a.class_name}> or Array<#{a.class_name}>) -> ``targs[0]``", wrap: false + RDL.type model, a.name, "() -> ActiveRecord_Relation<#{class_name}>", wrap: false + RDL.type model, "#{a.name}=", "(ActiveRecord_Relation<#{class_name}> or Array<#{class_name}>) -> ``targs[0]``", wrap: false #ActiveRecord_Associations_CollectionProxy<#{a.name.to_s.camelize.singularize}>' else ## association is singular, we just return an instance of associated class - RDL.type model, a.name, "() -> #{a.class_name}", wrap: false - RDL.type model, "#{a.name}=", "(#{a.class_name}) -> #{a.class_name}", wrap: false + RDL.type model, a.name, "() -> #{class_name}", wrap: false + RDL.type model, "#{a.name}=", "(#{class_name}) -> #{class_name}", wrap: false end } s2[:__associations] = RDL::Type::FiniteHashType.new(assoc, nil) diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index e3622108..bc30d127 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -229,25 +229,27 @@ def Hash.invert_output(trec) RDL.type :Hash, :keys, '() -> ``output_type(trec, targs, :keys, "Array")``' RDL.type :Hash, :length, '() -> ``output_type(trec, targs, :length, "Integer")``' RDL.type :Hash, :size, '() -> ``output_type(trec, targs, :size, "Integer")``' -RDL.type :Hash, :merge, '(``merge_input(targs)``) -> ``merge_output(trec, targs)``' -RDL.type :Hash, :merge!, '(``merge_input(targs, true)``) -> ``merge_output(trec, targs, true)``' +RDL.type :Hash, :merge, '(``merge_input(trec, targs)``) -> ``merge_output(trec, targs)``' +RDL.type :Hash, :merge!, '(``merge_input(trec, targs, true)``) -> ``merge_output(trec, targs, true)``' -def Hash.merge_input(targs, mutate=false) +def Hash.merge_input(trec, targs, mutate=false) case targs[0] when RDL::Type::FiniteHashType return targs[0] - when RDL::Type::GenericType + else #when RDL::Type::GenericType if mutate - return RDL::Globals.parser.scan_str "#T Hash" + raise "Unable to promote #{trec}." unless trec.promote! + return trec.canonical + #return RDL::Globals.parser.scan_str "#T Hash" else return RDL::Globals.parser.scan_str "#T Hash" end - else - RDL::Globals.types[:hash] + #else + # RDL::Globals.types[:hash] end end -RDL.type Hash, 'self.merge_input', "(Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.merge_input', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] def Hash.merge_output(trec, targs, mutate=false) @@ -304,6 +306,7 @@ def Hash.merge_output(trec, targs, mutate=false) else ## targs[0] should just be Hash here return RDL::Globals.types[:hash] + #return RDL::Globals.parser.scan_str "#T Hash" end end diff --git a/lib/types/rails/action_controller/instrumentation.rb b/lib/types/rails/action_controller/instrumentation.rb index fddf5c55..95da5afc 100644 --- a/lib/types/rails/action_controller/instrumentation.rb +++ b/lib/types/rails/action_controller/instrumentation.rb @@ -1,6 +1,5 @@ RDL.nowrap :'ActionController::Instrumentation' RDL.type :'ActionController::Instrumentation', :render, '(String or Symbol) -> Array' RDL.type :'ActionController::Instrumentation', :render, '(?String or Symbol, {content_type: ?String, layout: ?%bool or String, action: ?String or Symbol, location: ?String, nothing: ?%bool, text: ?[to_s: () -> String], status: ?Symbol, content_type: ?String, formats: ?Symbol or Array, locals: ?Hash}) -> Array' -RDL.type :'ActionController::Instrumentation', :redirect_to, '(:back) -> String' RDL.type :'ActionController::Instrumentation', :redirect_to, '({controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' RDL.type :'ActionController::Instrumentation', :redirect_to, '(String or Symbol or ActiveRecord::Base, ?{controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index 8df8194e..ff6f4707 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -153,6 +153,9 @@ module ActiveRecord::Querying type :order, '(%any) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false type :includes, '(``DBType.joins_one_input_type(trec, targs)``) -> ``DBType.joins_output(trec, targs)``', wrap: false type :includes, '(``DBType.joins_multi_input_type(trec, targs)``, %any, *%any) -> ``DBType.joins_output(trec, targs)``', wrap: false + type :preload, '(``DBType.joins_one_input_type(trec, targs)``) -> ``DBType.joins_output(trec, targs)``', wrap: false + type :preload, '(``DBType.joins_multi_input_type(trec, targs)``, %any, *%any) -> ``DBType.joins_output(trec, targs)``', wrap: false + type :limit, '(Integer) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), DBType.rec_to_nominal(trec))``', wrap: false type :count, '() -> Integer', wrap: false type :count, '(``DBType.count_input(trec, targs)``) -> Integer', wrap: false @@ -179,6 +182,9 @@ module ActiveRecord::Relation::QueryMethods 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 type :includes, '(``DBType.joins_one_input_type(trec, targs)``) -> ``DBType.joins_output(trec, targs)``', wrap: false type :includes, '(``DBType.joins_multi_input_type(trec, targs)``, %any, *%any) -> ``DBType.joins_output(trec, targs)``', wrap: false + type :preload, '(``DBType.joins_one_input_type(trec, targs)``) -> ``DBType.joins_output(trec, targs)``', wrap: false + type :preload, '(``DBType.joins_multi_input_type(trec, targs)``, %any, *%any) -> ``DBType.joins_output(trec, targs)``', wrap: false + type :limit, '(Integer) -> ``trec``', wrap: false type :references, '(Symbol, *Symbol) -> self', wrap: false end @@ -328,10 +334,11 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false, include_assocs: ## multiple tables joined to base table joined_hash = RDL.type_cast(Hash[pp1.types.map { |t| joined_name = RDL.type_cast(t, "RDL::Type::NominalType", force: true).klass.to_s.singularize.to_sym - joined_type = table_name_to_schema_type(joined_name, check_col, takes_array, include_assocs: include_assocs) + joined_type = RDL::Type::OptionalType.new(table_name_to_schema_type(joined_name, check_col, takes_array, include_assocs: include_assocs)) [joined_name.to_s.pluralize.underscore.to_sym, joined_type] } - ], "Hash", force: true) + ], "Hash", force: true) + type_hash = type_hash.merge(joined_hash) else raise "unexpected type #{trec}" end @@ -365,12 +372,13 @@ def self.table_name_to_schema_type(tname, check_col, takes_array=false, include_ raise RDL::Typecheck::StaticTypeError, "No table type for #{tname} found." unless ttype tschema = RDL.type_cast(ttype.params[0], "RDL::Type::FiniteHashType", force: true).elts.except(:__associations) h = Hash[tschema.map { |k, v| - if check_col - v = RDL::Type::UnionType.new(v, RDL::Type::GenericType.new(RDL::Globals.types[:array], v)) if takes_array - [k, RDL::Type::OptionalType.new(v)] - else - [k, RDL::Type::OptionalType.new(RDL::Globals.types[:top])] - end + if check_col + v = RDL::Type::UnionType.new(v, RDL::Globals.types[:symbol]) if v == RDL::Globals.types[:string] ## ran into cases where symbols can be accepted in addition to string values. + v = RDL::Type::UnionType.new(v, RDL::Type::GenericType.new(RDL::Globals.types[:array], v)).canonical if takes_array + [k, RDL::Type::OptionalType.new(v)] + else + [k, RDL::Type::OptionalType.new(RDL::Globals.types[:top])] + end }] if include_assocs ## include associations in schema hash @@ -380,7 +388,7 @@ def self.table_name_to_schema_type(tname, check_col, takes_array=false, include_ if check_col assoc_type = RDL::Type::NominalType.new(a.class_name) if a.name.to_s == a.plural_name - ## association is plural + ## association is plural assoc_hash[a.name] = RDL::Type::OptionalType.new(RDL::Type::GenericType.new(RDL::Globals.types[:array], assoc_type)) else assoc_hash[a.name] = RDL::Type::OptionalType.new(assoc_type) @@ -567,10 +575,12 @@ def self.get_joined_args(targs) arg_types = arg_types + [RDL::Type::NominalType.new(RDL.type_cast(arg.val, "Symbol", force: true).to_s.singularize.camelize)] when RDL::Type::FiniteHashType hsh = arg.elts - raise 'not supported' unless hsh.size == 1 - key, val = RDL.type_cast(hsh.first, "[Symbol, RDL::Type::SingletonType]", force: true) - val = val.val - 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))] + #raise "got #{hsh} but it's not supported" unless hsh.size == 1 + hsh.each { |key, val| + #key, val = RDL.type_cast(hsh.first, "[Symbol, RDL::Type::SingletonType]", force: true) + val = val.val + 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))] + } else raise "Unexpected arg type #{arg} to joins." end @@ -654,3 +664,4 @@ def self.count_input(trec, targs) RDL.type DBType, 'self.count_input', "(RDL::Type::Type, Array) -> RDL::Type::OptionalType", wrap: false, typecheck: :type_code, effect: [:+, :+] end + diff --git a/lib/types/rails/active_record/model_schema.rb b/lib/types/rails/active_record/model_schema.rb index 6e7cccd3..dc4690cb 100644 --- a/lib/types/rails/active_record/model_schema.rb +++ b/lib/types/rails/active_record/model_schema.rb @@ -4,9 +4,9 @@ class ActiveRecord::Base module ActiveRecord::ModelSchema::ClassMethods extend RDL::RDLAnnotate - - rdl_post(:load_schema!) { |ret| # load_schema! doesn't return anything interesting =begin + rdl_post(:load_schema!) { |ret| # load_schema! doesn't return anything interesting + columns_hash.each { |name, col| t = RDL::Rails.column_to_rdl(col.type) if col.null @@ -42,8 +42,7 @@ module ActiveRecord::ModelSchema::ClassMethods rdl_type :'self.none', "() -> ActiveRecord::Associations::CollectionProxy<#{self.to_s}>" rdl_type :'self.where', '(String, *%any) -> ActiveRecord::Associations::CollectionProxy' rdl_type :'self.where', '(**%any) -> ActiveRecord::Associations::CollectionProxy' -=end true } - +=end end diff --git a/lib/types/sequel/comp_types.rb b/lib/types/sequel/comp_types.rb index 699ae704..928c23c3 100644 --- a/lib/types/sequel/comp_types.rb +++ b/lib/types/sequel/comp_types.rb @@ -293,6 +293,8 @@ def self.all_output(trec) RDL.type Table, 'self.all_output', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] def self.select_map_output(trec, targs, meth) + raise "VarargType not supported for select_map." if meth == :select_map && targs[0].is_a?(RDL::Type::VarargType) + return trec if targs[0].is_a?(RDL::Type::VarargType) case trec when RDL::Type::GenericType raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table" @@ -409,6 +411,8 @@ def self.import_arg_type(trec, targs) raise "expected Array, got #{arg1}" unless (arg1.base == RDL::Globals.types[:array]) raise "`import` type not yet implemented for type #{arg1}" unless arg1.params[0].is_a?(RDL::Type::TupleType) schema_arg_tuple_type(trec, [targs[0], arg1.params[0]], :import) + when RDL::Type::VarType + schema_arg_tuple_type(trec, targs, :import) else raise "Not yet implemented for type #{arg1}." end @@ -465,7 +469,7 @@ def self.schema_arg_type(trec, targs, meth) else raise RDL::Typecheck::StaticTypeError, "No column #{column_name} for receiver #{trec}." unless receiver_schema.has_key?(cn) if meth == :where - type = get_nominal_where_type(type) + type = get_nominal_where_type(type) unless type.is_a?(RDL::Type::VarType) upper_type = RDL::Type::UnionType.new(receiver_schema[cn], RDL::Type::GenericType.new(RDL::Globals.types[:array], receiver_schema[cn])) else upper_type = receiver_schema[cn] @@ -474,12 +478,18 @@ def self.schema_arg_type(trec, targs, meth) end } return arg0 + when RDL::Type::GenericType + raise RDL::Typecheck::StaticTypeError, "unexpected type #{arg0}" unless arg0.base.name == "Hash" + return RDL::Globals.parser.scan_str "#T Hash" + when RDL::Type::NominalType + raise RDL::Typecheck::StaticTypeError, "unexpected type #{arg0}" unless arg0.name == "Hash" + return arg0 else return arg0 if (meth==:where) && targs[0] <= RDL::Globals.types[:string] raise "TODO WITH #{trec} AND #{targs} AND #{meth}" end when RDL::Type::NominalType - ##TODO + raise "TODO" else end @@ -544,6 +554,24 @@ def self.schema_arg_tuple_type(trec, targs, meth) end } return targs[0] + elsif targs[0].is_a?(RDL::Type::TupleType) && targs[1].is_a?(RDL::Type::VarType) + new_tuple = [] + targs[0].params.each_with_index { |column_name, i| + raise "Expected singleton symbol in call to insert, got #{column_name}" unless column_name.is_a?(RDL::Type::SingletonType) && column_name.val.is_a?(Symbol) + if column_name.val.to_s.include?("__") && (meth == :where) + raise "not yet implemented" + else + raise RDL::Typecheck::StaticTypeError, "No column #{column_name} for receiver in call to `insert`." unless receiver_schema.has_key?(column_name.val) + if meth == :where + upper_type = RDL::Type::UnionType.new(receiver_schema[column_name.val], RDL::Type::GenericType.new(RDL::Globals.types[:array], receiver_schema[column_name.val])) + else + upper_type = receiver_schema[column_name.val] + end + new_tuple << upper_type + end + } + RDL::Type::Type.leq(targs[1], RDL::Type::TupleType.new(*new_tuple)) + return targs[0] else raise "not yet implemented for types #{targs[0]} and #{targs[1]}" end From bd9f6fe70764f2e131eef4baa385149d70506826 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 17 Oct 2019 21:13:52 -0400 Subject: [PATCH 006/124] refactoring and updates to constraint resolution/heuristics --- lib/rdl/constraint.rb | 176 +++++++++--------- lib/rdl/heuristics.rb | 66 ++++++- lib/rdl/typecheck.rb | 83 ++++++--- lib/rdl/types/finite_hash.rb | 19 +- lib/rdl/types/intersection.rb | 6 +- lib/rdl/types/type.rb | 112 ++++++----- lib/rdl/types/union.rb | 1 + lib/rdl/types/var.rb | 67 +++++-- lib/rdl/wrap.rb | 1 + lib/types/core/array.rb | 4 +- lib/types/core/hash.rb | 63 +++++-- lib/types/core/integer.rb | 2 + lib/types/core/kernel.rb | 2 +- lib/types/core/random.rb | 2 +- lib/types/core/string.rb | 5 +- .../action_controller/instrumentation.rb | 2 +- lib/types/rails/action_controller/metal.rb | 2 +- lib/types/rails/active_model/errors.rb | 2 +- lib/types/rails/active_record/comp_types.rb | 59 +++++- lib/types/rails/active_record/sql-strings.rb | 2 +- lib/types/sequel/comp_types.rb | 13 +- 21 files changed, 467 insertions(+), 222 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index fff558a0..aeb24f3e 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -3,6 +3,7 @@ module RDL::Typecheck def self.resolve_constraints puts "Starting constraint resolution..." RDL::Globals.constrained_types.each { |klass, name| + puts "Resolving constraints from #{klass} and #{name}" typ = RDL::Globals.info.get(klass, name, :type) ## If typ is an Array, then it's an array of method types ## but for inference, we only use a single method type. @@ -17,9 +18,11 @@ def self.resolve_constraints if var_type.var_type? || var_type.optional_var_type? var_type = var_type.type if var_type.is_a?(RDL::Type::OptionalType) var_type.lbounds.each { |lower_t, ast| + #puts "1. Gonna apply #{lower_t} <= #{var_type}" var_type.add_and_propagate_lower_bound(lower_t, ast) } var_type.ubounds.each { |upper_t, ast| + #puts "2. Gonna apply #{var_type} <= #{upper_t}" var_type.add_and_propagate_upper_bound(upper_t, ast) } elsif var_type.fht_var_type? @@ -37,67 +40,6 @@ def self.resolve_constraints end } } - - if RDL::Config.instance.practical_infer - puts "practical inference!!!" - RDL::Globals.constrained_types.each { |klass, name| - typ = RDL::Globals.info.get(klass, name, :type) - if typ.is_a?(Array) - var_types = name == :initialize ? typ[0].args + [typ[0].block] : typ[0].args + [typ[0].block, typ[0].ret] - else - var_types = [typ] - end - - var_types.each { |var_type| - if var_type.fht_var_type? - var_type.elts.values.each { |v| - vt = v.is_a?(RDL::Type::OptionalType) ? v.type : v - puts "Applying struct_to_nominal to #{vt}." - struct_to_nominal(vt) - } - else - var_type = var_type.type if var_type.is_a?(RDL::Type::OptionalType) - puts "Applying struct_to_nominal to #{var_type}." - struct_to_nominal(var_type) - end - } - } - end - - puts "Done with constraint resolution." - end - - def self.struct_to_nominal(var_type) - return unless (var_type.category == :arg) || (var_type.category == :var)#(var_type.category == :ivar) || (var_type.category == :cvar) || (var_type.category == :gvar) ## this rule only applies to args and (instance/class/global) variables - #return unless var_type.ubounds.all? { |t, loc| t.is_a?(RDL::Type::StructuralType) || t.is_a?(RDL::Type::VarType) } ## all upper bounds must be struct types or var types - return unless var_type.ubounds.any? { |t, loc| t.is_a?(RDL::Type::StructuralType) } ## upper bounds must include struct type(s) - struct_types = var_type.ubounds.select { |t, loc| t.is_a?(RDL::Type::StructuralType) } - struct_types.map! { |t, loc| t } - return if struct_types.empty? - meth_names = struct_types.map { |st| st.methods.keys }.flatten - matching_classes = ObjectSpace.each_object(Class).select { |c| - class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) - (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods - ## Because every object inherits from Object/BasicObject, - ## we end up with some really weird issues regarding the eigenclasses - ## of objects if we try to include all possible matching classes. - ## We can just narrow things down here right off the bat. - if matching_classes.include?(BasicObject) - matching_classes = [BasicObject] - elsif matching_classes.include?(Object) - matching_classes = [Object] - end - - ## TODO: special handling for arrays/hashes/generics? - ## TODO: special handling for Rails models? see Bree's `active_record_match?` method - - raise "No matching classes found for structural types #{struct_types}." if matching_classes.empty? - return if matching_classes.size > 10 ## in this case, just keep the struct types - nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } - union = RDL::Type::UnionType.new(*nom_sing_types).canonical - struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds - #var_type.ubounds << [union, "Not providing a location."] - var_type.add_and_propagate_upper_bound(union, nil) end def self.extract_var_sol(var, category) @@ -110,7 +52,7 @@ def self.extract_var_sol(var, category) elsif category == :ret non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } sol = RDL::Type::UnionType.new(*non_vartype_lbounds) - sol = sol.drop_vars!.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. + sol = sol.drop_vars!.canonical if sol.is_a?(RDL::Type::UnionType) && !sol.types.all? { |t| t.is_a?(RDL::Type::VarType) } ## could be, e.g., nominal type if only one type used to create union. #return sol elsif category == :var if var.lbounds.empty? || (var.lbounds.size == 1 && var.lbounds[0][0] == RDL::Globals.types[:bot]) @@ -122,26 +64,70 @@ def self.extract_var_sol(var, category) ## use lower bounds non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } sol = RDL::Type::UnionType.new(*non_vartype_lbounds) - sol = sol.drop_vars!.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. + sol = sol.drop_vars!.canonical if sol.is_a?(RDL::Type::UnionType) && !sol.types.all? { |t| t.is_a?(RDL::Type::VarType) } ## could be, e.g., nominal type if only one type used to create union. #return sol#RDL::Type::UnionType.new(*non_vartype_lbounds).canonical end else raise "Unexpected VarType category #{category}." end - - if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || sol.is_a?(RDL::Type::StructuralType) + if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) ## Try each rule. Return first non-nil result. ## If no non-nil results, return original solution. ## TODO: check constraints. + RDL::Heuristic.rules.each { |name, rule| #puts "Trying rule `#{name}` for variable #{var}." typ = rule.call(var) - return typ if typ + new_cons = {} + begin + if typ + typ = typ.canonical + var.add_and_propagate_upper_bound(typ, nil, new_cons) + var.add_and_propagate_lower_bound(typ, nil, new_cons) + @new_constraints = true if !new_cons.empty? + return typ + #sol = typ + end + rescue RDL::Typecheck::StaticTypeError => e + puts "Attempted to apply rule #{name} to var #{var}, but go the following error: " + puts e + undo_constraints(new_cons) + ## no new constraints in this case so we'll leave it as is + end } - return sol - else - return sol + end + ## out here, none of the heuristics applied. + ## Try to use `sol` as solution -- there is a chance it will + begin + new_cons = {} + sol = var if sol == RDL::Globals.types[:bot] # just use var itself when result of solution extraction was %bot. + sol = sol.canonical + var.add_and_propagate_upper_bound(sol, nil, new_cons) + var.add_and_propagate_lower_bound(sol, nil, new_cons) + @new_constraints = true if !new_cons.empty? + rescue RDL::Typecheck::StaticTypeError => e + puts "Attempted to apply solution #{sol} for var #{var} but got the following error: " + puts e + undo_constraints(new_cons) + ## no new constraints in this case so we'll leave it as is + ### MILOD TODO TOMORROW: set sol = the original var here. + end + + return sol + end + + # [+ cons +] is Hash of constraints to be undone. + def self.undo_constraints(cons) + cons.each_key { |var_type| + cons[var_type].each { |upper_or_lower, bound_t, ast| + if upper_or_lower == :upper + var_type.ubounds.delete([bound_t, ast]) + elsif upper_or_lower == :lower + var_type.lbounds.delete([bound_t, ast]) + end + } + } end def self.extract_meth_sol(tmeth) @@ -185,31 +171,37 @@ def self.extract_meth_sol(tmeth) end def self.extract_solutions - puts "Starting solution extraction..." ## Go through once to come up with solution for all var types. - RDL::Globals.constrained_types.each { |klass, name| - typ = RDL::Globals.info.get(klass, name, :type) - if typ.is_a?(Array) - raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 - tmeth = typ[0] + #until !@new_constraints + loop do + @new_constraints = false + puts "\n\nRunning solution extraction..." + RDL::Globals.constrained_types.each { |klass, name| + typ = RDL::Globals.info.get(klass, name, :type) + if typ.is_a?(Array) + raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 + tmeth = typ[0] - arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) - block_string = block_sol ? " { #{block_sol} }" : nil - puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" + arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) - elsif name.to_s == "splat_param" - else - ## Instance/Class (also some times splat parameter) variables: - ## There is no clear answer as to what to do in this case. - ## Just need to pick something in between bounds (inclusive). - ## For now, plan is to just use lower bound when it's not empty/%bot, - ## otherwise use upper bound. - ## Can improve later if desired. - var_sol = extract_var_sol(typ, :var) - #typ.solution = var_sol - - puts "Extracted solution for #{typ} is #{var_sol}." - end - } + block_string = block_sol ? " { #{block_sol} }" : nil + puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" + + elsif name.to_s == "splat_param" + else + ## Instance/Class (also some times splat parameter) variables: + ## There is no clear answer as to what to do in this case. + ## Just need to pick something in between bounds (inclusive). + ## For now, plan is to just use lower bound when it's not empty/%bot, + ## otherwise use upper bound. + ## Can improve later if desired. + var_sol = extract_var_sol(typ, :var) + #typ.solution = var_sol + + puts "Extracted solution for #{typ} is #{var_sol}." + end + } + break if !@new_constraints + end end end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 42a7d9d9..5b43d34f 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -8,6 +8,35 @@ def self.add(name, &blk) @rules[name] = blk end + + def self.struct_to_nominal(var_type) + return unless (var_type.category == :arg) || (var_type.category == :var)#(var_type.category == :ivar) || (var_type.category == :cvar) || (var_type.category == :gvar) ## this rule only applies to args and (instance/class/global) variables + #return unless var_type.ubounds.all? { |t, loc| t.is_a?(RDL::Type::StructuralType) || t.is_a?(RDL::Type::VarType) } ## all upper bounds must be struct types or var types + return unless var_type.ubounds.any? { |t, loc| t.is_a?(RDL::Type::StructuralType) } ## upper bounds must include struct type(s) + struct_types = var_type.ubounds.select { |t, loc| t.is_a?(RDL::Type::StructuralType) } + struct_types.map! { |t, loc| t } + return if struct_types.empty? + meth_names = struct_types.map { |st| st.methods.keys }.flatten + matching_classes = ObjectSpace.each_object(Class).select { |c| + class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) + (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods + matching_classes.reject! { |c| c.to_s.start_with?("# 10 ## in this case, just keep the struct types + nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } + union = RDL::Type::UnionType.new(*nom_sing_types).canonical + struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds + + + return union + ## used to add and propagate here. Now that this is a heuristic, this should be done after running the rule. + #var_type.add_and_propagate_upper_bound(union, nil) + end + + end class << RDL::Heuristic @@ -28,36 +57,57 @@ def to_type def is_pluralized_model? return false unless defined? Rails return false unless pluralize == self - is_rails_model? + singularize.camelize.is_rails_model? end def model_set_type - RDL::Globals.parser.scan_str "#T Array<#{singularize}> or ActiveRecord_Relation<#{singularize}>" + RDL::Globals.parser.scan_str "#T Array<#{singularize.camelize}> or ActiveRecord_Relation<#{singularize.camelize}>" end end RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } +RDL::Heuristic.add(:struct_to_nominal) { |var| RDL::Heuristic.struct_to_nominal(var) } RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } RDL::Heuristic.add(:int_names) { |var| if var.base_name.end_with?("id") || (var.base_name == "num") || (var.base_name == "count") then RDL::Globals.types[:integer] end } RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") then RDL::Globals.parser.scan_str "#T Array" end } -RDL::Heuristic.add(:predicate_method) { |var| if var.meth.to_s.end_with?("?") && var.category == :ret then RDL::Globals.types[:bool] end } +RDL::Heuristic.add(:predicate_method) { |var| if var.base_name.end_with?("?") then RDL::Globals.types[:bool] end } + RDL::Heuristic.add(:hash_access) { |var| - if !var.ubounds.empty? && var.ubounds.all? { |u| u.is_a?(RDL::Type::StructuralType) && u.methods.all? { |meth, typ| ((meth == :[]) || (meth == :[]=)) && typ.args.all? { |t| t.is_a?(RDL::Type::SingletonType) && t.val.is_a?(Symbol) } } } + puts "trying hash_access!" + old_var = var + var = var.type if old_var.is_a?(RDL::Type::OptionalType) + types = [] + var.ubounds.reject { |t, ast| t.is_a?(RDL::Type::VarType) }.each { |t, ast| + if t.is_a?(RDL::Type::IntersectionType) + types = types + t.types + else + types << t + end + } + if !types.empty? && types.all? { |t| t.is_a?(RDL::Type::StructuralType) && t.methods.all? { |meth, typ| ((meth == :[]) || (meth == :[]=)) && typ.args[0].is_a?(RDL::Type::SingletonType) && typ.args[0].val.is_a?(Symbol) } } hash_typ = {} - var.ubounds.each { |struct| + types.each { |struct| struct.methods.each { |meth, typ| if meth == :[] - hash_typ[typ.args[0].val] = typ.ret ## TODO: if ret is var type, should we extract solution first? + value_type = typ.ret.is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.ret, :arg).canonical : typ.ret.canonical + hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: if ret is var type, should we extract solution first? elsif meth == :[]= - hash_typ[typ.args[0].val] = hash_typ[typ.args[1]] + value_type = typ.args[1].is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.args[1], :arg).canonical : typ.args[1].canonical + hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(typ.args[1]) else raise "Method should be one of :[] or :[]=, got #{meth}." end } } - RDL::Type::FiniteHashType.new(hash_typ, nil) + #var.ubounds.delete_if { |t| t.is_a?(RDL::Type::StructuralType) } #= [] ## might have to change this later, in particular to take advantage of comp types when performing solution extraction + fht = RDL::Type::FiniteHashType.new(hash_typ, nil) + if old_var.is_a?(RDL::Type::OptionalType) + RDL::Type::OptionalType.new(fht) + else + fht + end end } diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 06e5b759..72314ba5 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -237,7 +237,7 @@ def self.infer(klass, meth) block_type = meth_type.block ret_vartype = meth_type.ret - raise "Expected VarTypes in MethodType to be inferred, got #{t}." unless (arg_types + [block_type] + [ret_vartype]).all? { |t| t.kind_of_var_input? } + raise "Expected VarTypes in MethodType to be inferred, got #{meth_type}." unless (arg_types + [block_type] + [ret_vartype]).all? { |t| !t.nil? && (t.kind_of_var_input? || (meth == :initialize)) } end if ast.type == :def @@ -277,9 +277,8 @@ def self.infer(klass, meth) end until old_captured == scope[:captured] body_type = self_type if meth == :initialize - if false#body_type.is_a?(RDL::Type::UnionType) - body_type.types.each { |t| RDL::Type::Type.leq(t, ret_vartype, ast: ast) } ## TODO: is this done automatically in <= method? check - else + if body_type.is_a?(RDL::Type::UnionType) + body_type.types.each { |t| RDL::Type::Type.leq(t, ret_vartype, ast: ast) } else RDL::Type::Type.leq(body_type, ret_vartype, ast: ast) end @@ -443,7 +442,7 @@ def self.args_hash(scope, env, type, args, ast, kind) targ = type.args[tpos] (if (targ.is_a?(RDL::Type::AnnotatedArgType) || targ.is_a?(RDL::Type::DependentArgType) || targ.is_a?(RDL::Type::BoundArgType)) then targ = targ.type end) if arg.type == :arg - error :type_arg_kind_mismatch, [kind, 'optional', 'required'], arg if targ.optional? + error :type_arg_kind_mismatch, [kind, 'optional', 'required'], arg if (targ.optional? && !kind == 'block') ## block arg type can be optional while actual arg is required error :type_arg_kind_mismatch, [kind, 'vararg', 'required'], arg if targ.vararg? targs[arg.children[0]] = targ env = env.merge(Env.new(arg.children[0] => targ)) @@ -739,6 +738,18 @@ def self.tc(scope, env, e) end } [env, tright, effi] + elsif tright.is_a?(RDL::Type::VarType) + splat_ind = lhs.index { |lhs_elt| lhs_elt.type == :splat } + raise "not yet implemented" if splat_ind + new_tuple = [] + count = 0 + lhs.length.times { new_tuple << RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "tuple_element_#{count}") } + lhs.zip(new_tuple).each { |left, right| + envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left) + } + tuple_type = RDL::Type::TupleType.new(*new_tuple) + RDL::Type::Type.leq(tright, tuple_type, ast: e) + [envi, tright, effi] else error :masgn_bad_rhs, [tright], e.children[1] end @@ -813,8 +824,13 @@ def self.tc(scope, env, e) else # e.type == :or_asgn if tleft.val then [envleft, tleft] else [envright, tright] end end - else - [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical] + else + if trecv.is_a?(RDL::Type::VarType) + ## we get no new information from including VarType in union of return type. In fact, we can lose info due to promotion. So, leave it out. + [envright, tright] + else + [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical] + end end) if e.children[0].type == :send mutation_meth = (meth.to_s + '=').to_sym @@ -918,8 +934,9 @@ def self.tc(scope, env, e) envi, trecv, effrec = if e.children[0].nil? then [envi, envi[:self], [:+, :+]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver eff = effect_union(effrec, eff) - if map_case - raise "Expected GenericType, got #{trecv}." unless trecv.is_a?(RDL::Type::GenericType) + if map_case && trecv.is_a?(RDL::Type::GenericType) + #raise "Expected GenericType, got #{trecv}." unless trecv.is_a?(RDL::Type::GenericType) + trecv.is_a?(RDL::Type::GenericType) ti_map_case, effi = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) map_block_type = RDL::Type::MethodType.new([trecv.params[0]], nil, ti_map_case.canonical.ret) eff = effect_union(eff, effi) @@ -1265,7 +1282,7 @@ def self.tc(scope, env, e) if e.children[1] envi, _ = tc_vasgn(scope, envi, :lvasgn, e.children[1].children[0], RDL::Type::UnionType.new(*texns), e.children[1]) end - env_fin, t_fin, eff_fin = tc(scope, envi, e.children[2]) + env_fin, t_fin, eff_fin = if e.children[2].nil? then [envi, RDL::Globals.types[:nil], [:+, :+]] else tc(scope, envi, e.children[2]) end [env_fin, t_fin, effect_union(eff_fin, effi)] when :super envi = env @@ -1670,7 +1687,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, if block blk_args = block[0].children.map {|a| a.children[0]} blk_arg_vartypes = blk_args.map { |a| - RDL::Type::VarType.new(cls: trecv, meth: meth, category: :block_arg, name: block[0].children[0].to_s) } + RDL::Type::VarType.new(cls: trecv, meth: meth, category: :block_arg, name: a.to_s ) }#block[0].children[0].to_s) } blk_ret_vartype = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :block_ret, name: "block_ret") block_type = RDL::Type::MethodType.new(blk_arg_vartypes, nil, blk_ret_vartype) @@ -2063,12 +2080,25 @@ def self.tc_bind_arg_types(tmeth, tactuals) # otherwise throws an exception with a type error def self.tc_block(scope, env, tblock, block, inst) # TODO self is the same *except* instance_exec or instance_eval - raise RuntimeError, "block with block arg?" unless tblock.block.nil? + raise RuntimeError, "block with block arg?" unless tblock.is_a?(RDL::Type::VarType) || tblock.block.nil? tblock = tblock.instantiate(inst) if block[0].is_a? RDL::Type::MethodType error :bad_block_arg_type, [block[0], tblock], block[1] unless RDL::Type::Type.leq(block[0], tblock, inst, false, ast: block[1])#block[0] <= tblock elsif block[0].is_a?(RDL::Type::NominalType) && block[0].name == 'Proc' error :proc_block_arg_type, [tblock], block[1] + elsif tblock.is_a?(RDL::Type::VarType) + args, body = block + arg_names = args.children.map { |a| a.children[0] } + args_hash = {} + arg_vartypes = arg_names.map { |a| + v = RDL::Type::VarType.new(cls: inst[:self], meth: "block", category: :block_arg, name: a.to_s ) + args_hash[a] = v + v + } + _, ret_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], nil] else tc(scope, env.merge(Env.new(args_hash)), body) end + block_type = RDL::Type::MethodType.new(arg_vartypes, nil, ret_type) + RDL::Type::Type.leq(block_type, tblock, inst, false, ast: body) + eff else # must be [block-args, block-body] args, body = block env, targs = args_hash(scope, env, tblock, args, block, 'block') @@ -2094,7 +2124,7 @@ def self.tc_block(scope, env, tblock, block, inst) # if included, those methods are added to instance_methods # if extended, those methods are added to singleton_methods # (except Kernel is special...) - def self.lookup(scope, klass, name, e) + def self.lookup(scope, klass, name, e, make_unknown: true) if scope[:context_types] # return array of all matching types from context_types, if any ts = [] @@ -2135,11 +2165,13 @@ def self.lookup(scope, klass, name, e) return [tancestor, eancestor] if tancestor end if ancestor.instance_methods(false).member?(name) +## Milod: Not sure what the purpose of the below lines is. +=begin if RDL::Util.has_singleton_marker klass klass = RDL::Util.remove_singleton_marker klass klass = '(singleton) ' + klass end - +=end return nil if the_klass.to_s.start_with?('#= left.args.size + new_args = right.args[(left.args.size - 1) ..-1] + if left.args.size == 1 + left = RDL::Type::MethodType.new(new_args, left.block, left.ret) + else + left = RDL::Type::MethodType.new(left.args[0..(left.args.size-2)]+new_args, left.block, left.ret) + end + end + if left.args.last.is_a?(OptionalType) + left = RDL::Type::MethodType.new(left.args.map { |t| if t.is_a?(RDL::Type::OptionalType) then t.type else t end }, left.block, left.ret) + if left.args.size == right.args.size + 1 + ## A method with an optional type in the last position can be used in place + ## of a method without the optional type. So drop it and then check subtyping. + left = RDL::Type::MethodType.new(left.args.slice(0, left.args.size-1), left.block, left.ret) + end end return false unless left.args.size == right.args.size - return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) } # contravariance - return false unless leq(left.ret, right.ret, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # covariance + return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr.instantiate(inst), tl.instantiate(inst), inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } # contravariance + if left.block && right.block - return leq(right.block, left.block, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # contravariance - elsif left.block.nil? && right.block.nil? - return true - elsif left.block.is_a?(VarType) || right.block.is_a?(VarType) - ## methods to be inferred all have a VarType for a block type, so it's okay if - ## the other method type doesn't have a block. - return true - else + return false unless leq(right.block.instantiate(inst), left.block.instantiate(inst), inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # contravariance + elsif (left.block && !left.block.is_a?(VarType) && !right.block) || (right.block && !right.block.is_a?(VarType) && !left.block) return false # one has a block and the other doesn't end + return leq(left.ret.instantiate(inst), right.ret.instantiate(inst), inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # covariance + end return true if left.is_a?(MethodType) && right.is_a?(NominalType) && right.name == 'Proc' @@ -243,7 +265,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # allow width subtyping - methods of right have to be in left, but not vice-versa return right.methods.all? { |m, t| # in recursive call set inst to nil since those method types have implicit quantifier - left.methods.has_key?(m) && leq(left.methods[m], t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) + left.methods.has_key?(m) && leq(left.methods[m], t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } end # Note we do not allow a structural type to be a subtype of a nominal type or generic type, @@ -253,7 +275,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(TupleType) && right.is_a?(TupleType) # Tuples are immutable, so covariant subtyping allowed return false unless left.params.length == right.params.length - return false unless left.params.zip(right.params).all? { |lt, rt| leq(lt, rt, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) } + return false unless left.params.zip(right.params).all? { |lt, rt| leq(lt, rt, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } # subyping check passed left.ubounds << right unless no_constraint right.lbounds << left unless no_constraint @@ -262,7 +284,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(TupleType) && right.is_a?(GenericType) && right.base == RDL::Globals.types[:array] # TODO !ileft and right carries a free variable return false unless left.promote! - return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # recheck for promoted type + return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # recheck for promoted type end # finite hash @@ -283,10 +305,10 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con return false if tl.is_a?(OptionalType) && !tr.is_a?(OptionalType) # optional left, required right not allowed, since left may not have key tl = tl.type if tl.is_a? OptionalType tr = tr.type if tr.is_a? OptionalType - return false unless leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) + return false unless leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) right_elts.delete k else - return false unless right.rest && leq(tl, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) + return false unless right.rest && leq(tl, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) end } right_elts.each_pair { |k, t| @@ -294,7 +316,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con } unless left.rest.nil? # If left has optional stuff, right needs to accept it - return false unless !(right.rest.nil?) && leq(left.rest, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) + return false unless !(right.rest.nil?) && leq(left.rest, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) end left.ubounds << right unless no_constraint right.lbounds << left unless no_constraint @@ -303,7 +325,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(FiniteHashType) && right.is_a?(GenericType) && right.base == RDL::Globals.types[:hash] # TODO !ileft and right carries a free variable return false unless left.promote! - return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast) # recheck for promoted type + return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # recheck for promoted type end ## precise string diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index d097fae2..3e4e2dcf 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -43,6 +43,7 @@ def canonical def canonicalize! return if @canonicalized # for any type such that a supertype is already in ts, set its position to nil + @types.map! { |t| t.canonical } for i in 0..(@types.length-1) for j in (i+1)..(@types.length-1) next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType) && @types[i].to_infer) || (@types[j].is_a?(VarType) && @types[j].to_infer) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index c099d9da..830ff7c8 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -48,16 +48,26 @@ def initialize(name_or_hash) end - def add_and_propagate_upper_bound(typ, ast) + ## Adds an upper bound to self, and transitively pushes it to all of self's lower bounds. + # [+ typ +] is the Type to add as upper bound. + # [+ ast +] is the AST where the bound originates from, used for error messages. + # [+ new_cons +] is a Hash. When provided, can be used to roll back constraints in case an error pops up. + def add_and_propagate_upper_bound(typ, ast, new_cons = {}) return if self.equal?(typ) - @ubounds << [typ, ast] if !ubounds.any? { |t, a| t == typ } + #puts "About to add upper bound #{self} <= #{typ}" + if !@ubounds.any? { |t, a| t == typ } + @ubounds << [typ, ast] + new_cons[self] = new_cons[self] ? new_cons[self] | [[:upper, typ, ast]] : [[:upper, typ, ast]] + end #typ.ubounds.each { |t| add_and_propagate_upper_bound(typ) } @lbounds.each { |lower_t, a| if lower_t.is_a?(VarType) - lower_t.add_and_propagate_upper_bound(typ, ast) + lower_t.add_and_propagate_upper_bound(typ, ast, new_cons) else - puts "1. ABOUT TO COMPARE #{lower_t} TO #{typ} " if @meth == :random_secrets - unless RDL::Type::Type.leq(lower_t, typ, {}, false, ast: ast, no_constraint: true) + if typ.is_a?(VarType) + new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:lower, lower_t, ast]] : [[:lower, lower_t, ast]] + end + unless RDL::Type::Type.leq(lower_t, typ, {}, false, ast: ast, no_constraint: true, propagate: true, new_cons: new_cons) d1 = a.nil? ? "" : (Diagnostic.new :note, :infer_constraint_error, [lower_t.to_s], a.loc.expression).render.join("\n") d2 = ast.nil? ? "" : (Diagnostic.new :note, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{lower_t} <= #{typ} generated during inference.\n #{d1}\n #{d2}") @@ -66,16 +76,25 @@ def add_and_propagate_upper_bound(typ, ast) } end - def add_and_propagate_lower_bound(typ, ast) + + ## Similar to above. + def add_and_propagate_lower_bound(typ, ast, new_cons = {}) + raise if typ.to_s == "v" return if self.equal?(typ) - @lbounds << [typ, ast] if !@lbounds.any? { |t, a| t == typ } + #puts "About to add lower bound #{typ} <= #{self}" + if !@lbounds.any? { |t, a| t == typ } + @lbounds << [typ, ast] + new_cons[self] = new_cons[self] ? new_cons[self] | [[:lower, typ, ast]] : [[:lower, typ, ast]] + end #typ.lbounds.each { |t| add_and_propagate_lower_bound(typ) } if typ.is_a?(VarType) @ubounds.each { |upper_t, a| if upper_t.is_a?(VarType) - upper_t.add_and_propagate_lower_bound(typ, ast) + upper_t.add_and_propagate_lower_bound(typ, ast, new_cons) else - puts "2. ABOUT TO COMPARE #{typ} TO #{upper_t} " if @meth == :random_secrets - unless RDL::Type::Type.leq(typ, upper_t, {}, false, ast: ast, no_constraint: true) + if typ.is_a?(VarType) + new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:upper, upper_t, ast]] : [[:upper, upper_t, ast]] + end + unless RDL::Type::Type.leq(typ, upper_t, {}, false, ast: ast, no_constraint: true, propagate: true, new_cons: new_cons) d1 = ast.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") d2 = a.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{typ} <= #{upper_t} generated during inference.\n #{d1}\n #{d2}") @@ -84,6 +103,27 @@ def add_and_propagate_lower_bound(typ, ast) } end + def add_ubound(typ, ast, new_cons = {}, propagate: false) + if propagate + add_and_propagate_upper_bound(typ, ast, new_cons) + else + #puts "1. About to add upper bound #{self} <= #{typ}" + new_cons[self] = new_cons[self] ? new_cons[self] | [[:upper, typ, ast]] : [[:upper, typ, ast]] + @ubounds << [typ, ast] unless @ubounds.any? { |t, a| t == typ } + end + end + + def add_lbound(typ, ast, new_cons = {}, propagate: false) + if propagate + add_and_propagate_lower_bound(typ, ast, new_cons) + else + #puts "2. About to add lower bound #{typ} <= #{self}" + raise "blah" if typ.to_s == "Array" + new_cons[self] = new_cons[self] ? new_cons[self] | [[:lower, typ, ast]] : [[:lower, typ, ast]] + @lbounds << [typ, ast] unless @lbounds.any? { |t, a| t == typ } + end + end + def to_s # :nodoc: if @to_infer if @solution @@ -98,13 +138,14 @@ def to_s # :nodoc: def base_name return nil unless @name - @name.to_s.delete("@") + ## if var represents returned value, then method name is closest thing we have to variable's name. + if @category == :ret then @meth.to_s else @name.to_s.delete("@") end end def ==(other) return false if other.nil? other = other.canonical - return (other.instance_of? self.class) && (other.name.to_s == @name.to_s) + return (other.instance_of? self.class) && other.to_s == to_s#(other.name.to_s == @name.to_s) end alias eql? == @@ -122,7 +163,7 @@ def match(other) end def hash # :nodoc: - return @name.to_s.hash + return to_s.hash#@name.to_s.hash end def member?(obj, vars_wild: false) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index ac5410cf..e49fa1ef 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -778,6 +778,7 @@ def self.load_rails_schema end RDL.type model, (k+"=").to_sym, "(#{t_name}) -> #{t_name}", wrap: false ## create method type for column setter RDL.type model, (k).to_sym, "() -> #{t_name}", wrap: false ## create method type for column getter + RDL.type model, (k+"?").to_sym, "() -> %bool", wrap: false if t_name == "%bool" ## boolean column attributes get automatic `?` method } s2 = s1.transform_keys { |k| k.to_sym } assoc = {} diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 7df12358..2a090ee5 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -254,7 +254,7 @@ def Array.map_output(trec) RDL.type :Array, :count, '() { (``promoted_or_t(trec)``) -> %bool } -> Integer' RDL.type :Array, :cycle, '(?Integer) { (``promoted_or_t(trec)``) -> %any } -> %any' RDL.type :Array, :cycle, '(?Integer) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' -RDL.type :Array, :delete, '(u) -> ``promote_tuple!(trec); targs[0]``' +RDL.type :Array, :delete, '(%any) -> ``promote_tuple!(trec); targs[0]``' RDL.type :Array, :delete, '(u) { () -> v } -> ``promote_tuple!(trec); RDL::Globals.parser.scan_str "#T u or v"``' RDL.type :Array, :delete_at, '(Integer) -> ``promote_tuple!(trec)``' RDL.type :Array, :delete_if, '() { (``promoted_or_t(trec)``) -> %bool } -> ``promote_tuple!(trec)``' @@ -414,7 +414,7 @@ def Array.reverse_output(trec) RDL.type :Array, :unshift, '(``any_or_t(trec, true)``) -> ``promote_tuple!(trec)``' RDL.type :Array, :values_at, '(*Integer) -> ``output_type(trec, targs, :values_at, :promoted_array, "Array")``' RDL.type :Array, :values_at, '(Range) -> ``promote_tuple(trec)``' -RDL.type :Array, :zip, '(*Array) -> ``RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(promoted_or_t(trec), RDL::Globals.parser.scan_str("#T u"))))``' +#RDL.type :Array, :zip, '(*Array) -> ``RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(promoted_or_t(trec), RDL::Globals.parser.scan_str("#T z"))))``' RDL.type :Array, :|, '(*Array) -> ``RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(promoted_or_t(trec), RDL::Globals.parser.scan_str("#T u")))``' diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index bc30d127..d8b65ed8 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -30,7 +30,13 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_de end end else - RDL::Globals.parser.scan_str "#T #{default2}" + if default2 == "k" + trec.params[0] ## equivalent of k in Hash + elsif default2 == "v" + trec.params[1] ## equivalent of v in Hash + else + RDL::Globals.parser.scan_str "#T #{default2}" + end end end RDL.type Hash, 'self.output_type', "(RDL::Type::Type, Array, Symbol, Symbol or String, ?(Symbol or String), { nil_default: ?%bool, use_sing_val: ?%bool } ) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] @@ -53,8 +59,11 @@ def Hash.any_or_k(trec) case trec when RDL::Type::FiniteHashType RDL::Globals.types[:top] + when RDL::Type::GenericType + #RDL::Globals.parser.scan_str "#T k" + trec.params[0] ## equivalent of k in Hash else - RDL::Globals.parser.scan_str "#T k" + raise "unexpected, got #{trec}" end end RDL.type Hash, 'self.any_or_k', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] @@ -63,8 +72,11 @@ def Hash.any_or_v(trec) case trec when RDL::Type::FiniteHashType RDL::Globals.types[:top] + when RDL::Type::GenericType + #RDL::Globals.parser.scan_str "#T v" + trec.params[1] ## equivalent of v in Hash else - RDL::Globals.parser.scan_str "#T v" + raise "unexpected" end end RDL.type Hash, 'self.any_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] @@ -73,8 +85,11 @@ def Hash.promoted_or_v(trec) case trec when RDL::Type::FiniteHashType trec.promote.params[1] + when RDL::Type::GenericType + #RDL::Globals.parser.scan_str "#T v" + trec.params[1] else - RDL::Globals.parser.scan_str "#T v" + raise "unexpected" end end RDL.type Hash, 'self.promoted_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] @@ -145,7 +160,8 @@ def Hash.assign_output(trec, targs) return targs[1] end else - RDL::Globals.parser.scan_str "#T v" + #RDL::Globals.parser.scan_str "#T v" + trec.params[1] end end RDL.type Hash, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] @@ -218,7 +234,8 @@ def Hash.invert_output(trec) hash = Hash[hash.map { |k, v| if !RDL.type_cast(v, "Object", force: true).is_a?(RDL::Type::Type) then [k, RDL::Type::SingletonType.new(v)] else [k, v] end }] RDL::Type::FiniteHashType.new(RDL.type_cast(hash, "Hash<%any, RDL::Type::Type>", force: true), nil) else - RDL::Globals.parser.scan_str "#T Hash" + RDL::Type::GenericType.new(RDL::Globals.types[:hash], trec.params[1], trec.params[0]) + #RDL::Globals.parser.scan_str "#T Hash" end end RDL.type Hash, 'self.invert_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] @@ -330,14 +347,37 @@ def Hash.shift_output(trec) promoted = trec.promote RDL::Type::TupleType.new(*promoted.params) ## Type error found by type checker here. else - RDL::Globals.parser.scan_str "#T [k, v]" + #RDL::Globals.parser.scan_str "#T [k, v]" + RDL::Type::TupleType.new(trec.params[0], trec.params[1]) end end RDL.type Hash, 'self.shift_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] -RDL.type :Hash, :to_a, '() -> ``output_type(trec, targs, :to_a, "Array<[k, v]>")``' +#RDL.type :Hash, :to_a, '() -> ``output_type(trec, targs, :to_a, "Array<[k, v]>")``' +RDL.type :Hash, :to_a, '() -> ``to_a_output_type(trec)")``' + +def Hash.to_a_output_type(trec) + case trec + when RDL::Type::FiniteHashType + to_type(trec.elts.to_a) + else + RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::TupleType.new(trec,params[0], trec.params[1])) + end +end + + RDL.type :Hash, :to_hash, '() -> self' -RDL.type :Hash, :values, '() -> ``output_type(trec, targs, :values, "Array")``' +#RDL.type :Hash, :values, '() -> ``output_type(trec, targs, :values, "Array")``' +RDL.type :Hash, :values, '() -> ``values_output(trec)``' +def Hash.values_output(trec) + case trec + when RDL::Type::FiniteHashType + to_type(trec.elts.values) + else + RDL::Type::GenericType.new(RDL::Globals.types[:array], trec.params[1]) + end +end + RDL.type :Hash, :values_at, '(``values_at_input(trec)``) -> ``values_at_output(trec, targs)``' @@ -346,7 +386,7 @@ def Hash.values_at_input(trec) when RDL::Type::FiniteHashType RDL::Type::VarargType.new(RDL::Globals.types[:top]) else - RDL::Type::VarargType.new(RDL::Type::VarType.new("k")) + RDL::Type::VarargType.new(trec.params[0]) end end RDL.type Hash, 'self.values_at_input', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] @@ -366,7 +406,7 @@ def Hash.values_at_output(trec, targs) RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), trec.promote.params[1]) end else - RDL::Globals.parser.scan_str "#T Array" + RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), trec.params[1]) end end RDL.type Hash, 'self.values_at_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] @@ -449,3 +489,4 @@ def Hash.values_at_output(trec, targs) RDL.type :Hash, :to_hash, '() -> Hash' RDL.type :Hash, :values, '() -> Array' RDL.type :Hash, :values_at, '(*k) -> Array', effect: [:+, :+] + diff --git a/lib/types/core/integer.rb b/lib/types/core/integer.rb index 27f38a04..73924680 100644 --- a/lib/types/core/integer.rb +++ b/lib/types/core/integer.rb @@ -85,6 +85,8 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :==, '(Object) -> ``sing_or_type(trec, targs, :==, "%bool")``' +RDL.type :Integer, :!=, '(``targs[0]``) -> ``sing_or_type(trec, targs, :==, "%bool")``' + RDL.type :Integer, :===, '(Object) -> ``sing_or_type(trec, targs, :===, "%bool")``' RDL.type :Integer, :>, '(Integer) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index a4c72bea..c271e501 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -63,7 +63,7 @@ RDL.type :Kernel, 'raise', '() -> %bot', effect: [:+, :+] # RDL.type :Kernel, 'self.raise', '(String or [exception : () -> String], ?String, ?Array) -> %any' # TODO: above same as fail? -RDL.type :Kernel, 'self.rand', '(Integer or Range max) -> Numeric' +RDL.type :Kernel, 'self.rand', '(Integer or Range max) -> Numeric' RDL.type :Kernel, 'self.readline', '(?String, ?Integer) -> String' RDL.type :Kernel, 'self.readlines', '(?String, ?Integer) -> Array' RDL.type :Kernel, 'self.require', '(String name) -> %bool' diff --git a/lib/types/core/random.rb b/lib/types/core/random.rb index 4b96d380..ba5593c6 100644 --- a/lib/types/core/random.rb +++ b/lib/types/core/random.rb @@ -2,7 +2,7 @@ RDL.type :Random, :initialize, '(?Integer seed) -> self' # Floats can be passed also, but just truncated to int? RDL.type :Random, 'self.new_seed', '() -> Integer' -RDL.type :Random, 'self.rand', '(?Integer max) -> Numeric' +RDL.type :Random, 'self.rand', '(?(Integer or Range) max) -> Numeric' RDL.type :Random, 'self.srand', '(?Integer number) -> Numeric old_seed' RDL.type :Random, :==, '(%any) -> %bool' diff --git a/lib/types/core/string.rb b/lib/types/core/string.rb index 70afc2dd..6b8b69a3 100644 --- a/lib/types/core/string.rb +++ b/lib/types/core/string.rb @@ -19,7 +19,8 @@ def String.output_type(trec, targs, meth, type) vals = targs.map { |t| t.val } to_type(trec.vals[0].send(meth, *vals)) else - raise "not yet implemented" + #raise "not yet implemented with method #{meth} and trec #{trec} and targs #{targs} and type #{type}" + RDL::Globals.parser.scan_str "#T #{type}" end to_type(res) else @@ -73,7 +74,7 @@ def String.string_promote!(trec) RDL.type :String, :initialize, '(?String str) -> self new_str' RDL.type :String, 'self.try_convert', '(Object obj) -> String or nil new_string' RDL.type :String, :%, '(Object) -> ``output_type(trec, targs, :%, "String")``' -RDL.type :String, :*, '(Integer) -> ``output_type(trec, targs, :*, "String")``' +RDL.type :String, :*, '(Numeric) -> ``output_type(trec, targs, :*, "String")``' def String.plus_output(trec, targs) if trec.is_a?(RDL::Type::PreciseStringType) && targs[0].is_a?(RDL::Type::PreciseStringType) diff --git a/lib/types/rails/action_controller/instrumentation.rb b/lib/types/rails/action_controller/instrumentation.rb index 95da5afc..d388cc24 100644 --- a/lib/types/rails/action_controller/instrumentation.rb +++ b/lib/types/rails/action_controller/instrumentation.rb @@ -2,4 +2,4 @@ RDL.type :'ActionController::Instrumentation', :render, '(String or Symbol) -> Array' RDL.type :'ActionController::Instrumentation', :render, '(?String or Symbol, {content_type: ?String, layout: ?%bool or String, action: ?String or Symbol, location: ?String, nothing: ?%bool, text: ?[to_s: () -> String], status: ?Symbol, content_type: ?String, formats: ?Symbol or Array, locals: ?Hash}) -> Array' RDL.type :'ActionController::Instrumentation', :redirect_to, '({controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' -RDL.type :'ActionController::Instrumentation', :redirect_to, '(String or Symbol or ActiveRecord::Base, ?{controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' +#RDL.type :'ActionController::Instrumentation', :redirect_to, '(String or Symbol or ActiveRecord::Base, ?{controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' diff --git a/lib/types/rails/action_controller/metal.rb b/lib/types/rails/action_controller/metal.rb index e0736f17..26ede3da 100644 --- a/lib/types/rails/action_controller/metal.rb +++ b/lib/types/rails/action_controller/metal.rb @@ -1,3 +1,3 @@ RDL.nowrap :'ActionController::Metal' -RDL.type :'ActionController::Metal', :params, '() -> ActiveSupport::HashWithIndifferentAccess' +#RDL.type :'ActionController::Metal', :params, '() -> ActiveSupport::HashWithIndifferentAccess' RDL.type :'ActionController::Metal', :request, '() -> ActionDispatch::Request' diff --git a/lib/types/rails/active_model/errors.rb b/lib/types/rails/active_model/errors.rb index 59c49bbb..581b5d08 100644 --- a/lib/types/rails/active_model/errors.rb +++ b/lib/types/rails/active_model/errors.rb @@ -10,5 +10,5 @@ RDL.type :'ActiveModel::Errors', :empty?, '() -> %bool' RDL.rdl_alias :'ActiveModel::Errors', :blank?, :empty? RDL.type :'ActiveModel::Errors', :hash, '(?%bool full_messages) -> Hash' -RDL.type :'ActiveModel::Errors', :add, '(%symstr, %symstr, Hash) -> %any' +RDL.type :'ActiveModel::Errors', :add, '(%symstr, %symstr, ?Hash) -> %any' RDL.type :'ActiveModel::Errors', :add, '(%symstr, { () -> String }, Hash) -> %any' # TODO: combine with prev with union once supported diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index ff6f4707..0ff464ca 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -3,7 +3,6 @@ class ActiveRecord::Base extend RDL::Annotate - type Object, :try, "(Symbol) -> Object", wrap: false type Object, :present?, "() -> %bool", wrap: false type :initialize, '(``DBType.rec_to_schema_type(trec, true)``) -> self', wrap: false type 'self.create', '(``DBType.rec_to_schema_type(trec, true, include_assocs: true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false @@ -24,6 +23,34 @@ class ActiveRecord::Base type String, :pluralize, "() -> String", wrap: false, effect: [:+, :+] type String, :underscore, "() -> String", wrap: false, effect: [:+, :+] + type Object, :try, "(Symbol) -> ``try_output(trec, targs)``", wrap: false + + def Object.try_output(trec, targs) + case trec + when RDL::Type::SingletonType + klass = trec.val.class + inst = {self: trec } + when RDL::Type::NominalType + klass = trec.klass + inst = {self: trec } + when RDL::Type::GenericType + klass = trec.base.klass + inst = trec.to_inst.merge(self: trec) + else + raise "Unexpected type #{trec}." + end + case targs[0] + when RDL::Type::SingletonType + meth_types = RDL::Typecheck.lookup({}, klass.to_s, targs[0].val, nil, make_unknown: false)#RDL::Globals.info.get(klass, targs[0].val, :type) + meth_types = meth_types[0] if meth_types + ret_type = meth_types ? RDL::Type::UnionType.new(*meth_types.map { |mt| mt.ret } ).canonical : RDL::Globals.types[:top] + else + return RDL::Globals.types[:top] + end + end + + + def self.access_output(trec, targs) case trec when RDL::Type::NominalType @@ -248,8 +275,8 @@ class ActiveRecord_Relation type_params [:t], :dummy - type :each, '() -> Enumerator', wrap: false - type :each, '() { (t) -> %any } -> Array', wrap: false + type :each, '() -> ``DBType.each_no_block_ret(trec)``', wrap: false + type :each, '() { (``DBType.each_block_arg(trec)``) -> %any } -> Array', wrap: false type :empty?, '() -> %bool', wrap: false type :present?, '() -> %bool', wrap: false type :create, '(``DBType.rec_to_schema_type(trec, true, include_assocs: true)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false @@ -269,7 +296,7 @@ class ActiveRecord_Relation type :to_a, "() -> ``DBType.rec_to_array(trec)``", wrap: false type :[], "(Integer) -> t", wrap: false type :size, "() -> Integer", wrap: false - type :update_all, '(``DBType.rec_to_schema_type(trec, true)``) -> Integer', wrap: false + type :update_all, '(``RDL::Type::UnionType.new(RDL::Globals.types[:string], DBType.rec_to_schema_type(trec, true))``) -> Integer', wrap: false type :valid, "() -> self", wrap: false end @@ -404,7 +431,7 @@ def self.table_name_to_schema_type(tname, check_col, takes_array=false, include_ RDL.type DBType, 'self.table_name_to_schema_type', "(Symbol, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code, effect: [:+, :+] def self.where_input_type(trec, targs) - handle_sql_strings(trec, targs) if targs[0].is_a? RDL::Type::PreciseStringType + handle_sql_strings(trec, targs) if targs[0].is_a?(RDL::Type::PreciseStringType) && !targs[1].nil? && !targs[1].kind_of_var_input? tschema = rec_to_schema_type(trec, true, true) return RDL::Type::UnionType.new(tschema, RDL::Globals.types[:string], RDL::Globals.types[:array]) ## no indepth checking for string or array cases end @@ -477,11 +504,14 @@ def self.find_output_type(trec, targs) ## TODO ## Actually, this is deprecated in later versions raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to ActiveRecord::Base#find." - else +p else raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to ActiveRecord::Base#find." end - when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::VarType + when RDL::Type::GenericType, RDL::Type::TupleType RDL::Type::GenericType.new(RDL::Globals.types[:array], DBType.rec_to_nominal(trec)) + when RDL::Type::VarType + nom = DBType.rec_to_nominal(trec) + RDL::Type::UnionType.new(RDL::Type::GenericType.new(RDL::Globals.types[:array], nom), nom) else raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to ActiveRecord::Base#find." end @@ -663,5 +693,20 @@ def self.count_input(trec, targs) end RDL.type DBType, 'self.count_input', "(RDL::Type::Type, Array) -> RDL::Type::OptionalType", wrap: false, typecheck: :type_code, effect: [:+, :+] + + def self.each_block_arg(trec) + case trec.params[0] + when RDL::Type::GenericType + raise "Expected JoinTable" unless trec.params[0].base.klass == JoinTable + return trec.params[0].params[0] + else + return trec.params[0] + end + end + + def self.each_no_block_ret(trec) + RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), each_block_arg(trec)) + end + end diff --git a/lib/types/rails/active_record/sql-strings.rb b/lib/types/rails/active_record/sql-strings.rb index 629175ad..f665136e 100644 --- a/lib/types/rails/active_record/sql-strings.rb +++ b/lib/types/rails/active_record/sql-strings.rb @@ -65,7 +65,7 @@ def visit_In(o) # base type is Array, maybe add check? raise RDL::Typecheck::StaticTypeError, "type error" unless promoted.params[0] <= schema_type else - raise RDL::Typecheck::StaticTypeError, "some other type after promotion" + raise RDL::Typecheck::StaticTypeError, "some other type #{promoted} after promotion" end end diff --git a/lib/types/sequel/comp_types.rb b/lib/types/sequel/comp_types.rb index 928c23c3..4cac9626 100644 --- a/lib/types/sequel/comp_types.rb +++ b/lib/types/sequel/comp_types.rb @@ -229,7 +229,8 @@ def self.join_ret_type(trec, targs) type :count, "() -> Integer", wrap: false type :map, "() { (``map_block_input(trec)``) -> x } -> Array", wrap: false type :each, "() { (``map_block_input(trec)``) -> x } -> self", wrap: false - type :import, "(``import_arg_type(trec, targs)``, Array) -> Array", wrap: false + type :import, "(``import_arg_type(trec, targs)``, Array) -> Array", wrap: false + type :limit, "(Integer) -> self", wrap: false def self.order_input(trec, targs) case trec @@ -409,7 +410,7 @@ def self.import_arg_type(trec, targs) arg1.params.each { |t| schema_arg_tuple_type(trec, [targs[0], t], :import) } ## check each individual tuple inside second arg tuple when RDL::Type::GenericType raise "expected Array, got #{arg1}" unless (arg1.base == RDL::Globals.types[:array]) - raise "`import` type not yet implemented for type #{arg1}" unless arg1.params[0].is_a?(RDL::Type::TupleType) + raise "`import` type not yet implemented for type #{arg1}" unless arg1.params[0].is_a?(RDL::Type::TupleType) schema_arg_tuple_type(trec, [targs[0], arg1.params[0]], :import) when RDL::Type::VarType schema_arg_tuple_type(trec, targs, :import) @@ -550,7 +551,7 @@ def self.schema_arg_tuple_type(trec, targs, meth) else upper_type = receiver_schema[cn.val] end - raise RDL::Typecheck::StaticTypeError, "Incompatible column types." unless RDL::Type::Type.leq(type, upper_type) + raise RDL::Typecheck::StaticTypeError, "Incompatible column types #{type} and #{upper_type}." unless RDL::Type::Type.leq(type, upper_type) end } return targs[0] @@ -570,7 +571,11 @@ def self.schema_arg_tuple_type(trec, targs, meth) new_tuple << upper_type end } - RDL::Type::Type.leq(targs[1], RDL::Type::TupleType.new(*new_tuple)) + if meth == :import + RDL::Type::Type.leq(targs[1], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::TupleType.new(*new_tuple).promote)) + else + RDL::Type::Type.leq(targs[1], RDL::Type::TupleType.new(*new_tuple)) + end return targs[0] else raise "not yet implemented for types #{targs[0]} and #{targs[1]}" From 626e16b2c75e42d20e8d7e3bc26ce726f0d6301a Mon Sep 17 00:00:00 2001 From: mckaz Date: Sat, 26 Oct 2019 09:12:35 -0400 Subject: [PATCH 007/124] get rid of deletion of struct types in struct_to_nominal --- lib/rdl/heuristics.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 5b43d34f..e9889f86 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -28,7 +28,7 @@ def self.struct_to_nominal(var_type) return if matching_classes.size > 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } union = RDL::Type::UnionType.new(*nom_sing_types).canonical - struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds + #struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds return union From 6ac3d3f9401bd4c6f35078d3ff6096741c46aad2 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Wed, 11 Dec 2019 11:12:05 -0500 Subject: [PATCH 008/124] many updates --- lib/rdl/boot.rb | 11 + lib/rdl/class_indexer.rb | 95 +++++ lib/rdl/config.rb | 4 +- lib/rdl/constraint.rb | 138 ++++++- lib/rdl/heuristics.rb | 34 +- lib/rdl/typecheck.rb | 361 ++++++++++++------ lib/rdl/types/finite_hash.rb | 6 +- lib/rdl/types/intersection.rb | 27 +- lib/rdl/types/method.rb | 4 +- lib/rdl/types/nominal.rb | 3 + lib/rdl/types/singleton.rb | 3 + lib/rdl/types/string.rb | 4 +- lib/rdl/types/type.rb | 98 +++-- lib/rdl/types/union.rb | 24 +- lib/rdl/types/var.rb | 16 +- lib/rdl/wrap.rb | 87 +++-- lib/types/core/_aliases.rb | 2 +- lib/types/core/array.rb | 58 ++- lib/types/core/class.rb | 2 +- lib/types/core/enumerable.rb | 3 +- lib/types/core/hash.rb | 70 +++- lib/types/core/integer.rb | 6 +- lib/types/core/io.rb | 2 + lib/types/core/kernel.rb | 4 +- lib/types/core/numeric.rb | 4 +- lib/types/core/object.rb | 3 +- lib/types/core/string.rb | 36 +- lib/types/core/symbol.rb | 4 +- lib/types/core/time.rb | 9 +- lib/types/rails/action_controller/base.rb | 2 +- .../action_controller/instrumentation.rb | 5 +- .../rails/action_controller/mime_responds.rb | 4 +- .../action_controller/strong_parameters.rb | 2 +- lib/types/rails/action_mailer/base.rb | 2 +- lib/types/rails/active_model/errors.rb | 4 +- lib/types/rails/active_record/comp_types.rb | 27 +- lib/types/rails/active_record/sql-strings.rb | 12 +- .../rails/active_support/tagged_logging.rb | 1 + lib/types/rails/time.rb | 2 + lib/types/sequel/comp_types.rb | 1 + rdl.gemspec | 1 + 41 files changed, 891 insertions(+), 290 deletions(-) create mode 100644 lib/rdl/class_indexer.rb diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 4434a8e9..c7a23458 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -3,6 +3,7 @@ require 'set' require 'parser/current' + module RDL end @@ -77,6 +78,13 @@ module RDL::Globals # Map from Sequel table names (symbols) to their schema types, which should be a Table type @seq_db_schema = {} + + # Array<[String, String]>, where each first string is a class name and each second one is a method name. + # klass/method pairs here should not be inferred. + @no_infer_meths = [] + + # Array of absolute file paths for files that should not be inferred. + @no_infer_files = [] end class << RDL::Globals # add accessors and readers for module variables @@ -94,6 +102,8 @@ class << RDL::Globals # add accessors and readers for module variables attr_accessor :comp_type_map attr_accessor :ar_db_schema attr_accessor :seq_db_schema + attr_accessor :no_infer_meths + attr_accessor :no_infer_files end # Create switches to control whether wrapping happens and whether @@ -146,6 +156,7 @@ class << RDL::Globals require 'rdl/contracts/proc.rb' require 'rdl/util.rb' +require 'rdl/class_indexer.rb' require 'rdl/wrap.rb' require 'rdl/query.rb' require 'rdl/typecheck.rb' diff --git a/lib/rdl/class_indexer.rb b/lib/rdl/class_indexer.rb new file mode 100644 index 00000000..2cc41785 --- /dev/null +++ b/lib/rdl/class_indexer.rb @@ -0,0 +1,95 @@ +## This is inspired by the class_indexer gem, found at: +## https://github.com/matugm/class_indexer + +module ClassIndexer + class Processor < AST::Processor + attr_reader :class_list + + def initialize + reset_class + + @class_list = Hash.new { |h,k| h[k] = [] } + end + + def reset_class + @current_class = "main" + end + + def add_method(method_name, line_num) + @class_list[@current_class] << { name: method_name.to_s, line: line_num } + end + + def on_class(node) + class_name = node.children[0].children[1].to_s + + if @current_class == "main" + @current_class = class_name + else + @current_class << "::" + class_name + end + + node.children.each { |c| process(c) } + + if @current_class.include?("::") + @current_class.sub!("::" + class_name, "") + else + reset_class + end + end + + def on_module(node) + module_name = node.children[0].children[1].to_s + + if @current_class == "main" + @current_class = module_name + else + #@current_class.prepend(module_name + "::") + @current_class << "::" + module_name + end + + node.children.each { |c| process(c) } + + @current_class.sub!("::"+module_name, "") + end + + def on_sclass(node) + raise "Not currently supported." unless node.children[0].type == :self + + @current_class.prepend("[s]") + + node.children.each { |c| process(c) } + + @current_class.sub!("[s]", "") + end + + # Instance methods + def on_def(node) + line_num = node.loc.line + method_name = node.children[0] + + add_method(method_name, line_num) + end + + # Class methods + def on_defs(node) + line_num = node.loc.line + method_name = "self.#{node.children[1]}" + + add_method(method_name, line_num) + end + + def on_begin(node) + node.children.each { |c| process(c) } + end + end + + def self.process_file(file) + exp = Parser::CurrentRuby.parse(File.read(file)) + ast = Processor.new + ast.process(exp) + ast.class_list + rescue Parser::SyntaxError + warn "Syntax Error found while parsing #{file}" + end + +end diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index 6f369dcf..0723ad66 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -8,7 +8,7 @@ class RDL::Config attr_reader :report # writer is custom defined attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types attr_accessor :type_defaults, :infer_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type - attr_accessor :practical_infer + attr_accessor :practical_infer, :use_precise_string, :number_mode def initialize @nowrap = Set.new # Set of symbols @@ -28,6 +28,8 @@ def initialize @check_comp_types = false ## this for dynamically checking that the result of a computed type still holds @rerun_comp_types = false ## this is for dynamically checking that a type computation still evaluates to the same thing as it did at type checking time @practical_infer = true + @use_precise_string = false + @number_mode = false end def report=(val) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index aeb24f3e..d6c560d6 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,3 +1,5 @@ +require 'csv' + module RDL::Typecheck def self.resolve_constraints @@ -15,19 +17,17 @@ def self.resolve_constraints end var_types.each { |var_type| - if var_type.var_type? || var_type.optional_var_type? - var_type = var_type.type if var_type.is_a?(RDL::Type::OptionalType) + if var_type.var_type? || var_type.optional_var_type? || var_type.vararg_var_type? + var_type = var_type.type if var_type.optional_var_type? || var_type.vararg_var_type? var_type.lbounds.each { |lower_t, ast| - #puts "1. Gonna apply #{lower_t} <= #{var_type}" var_type.add_and_propagate_lower_bound(lower_t, ast) } var_type.ubounds.each { |upper_t, ast| - #puts "2. Gonna apply #{var_type} <= #{upper_t}" var_type.add_and_propagate_upper_bound(upper_t, ast) } elsif var_type.fht_var_type? var_type.elts.values.each { |v| - vt = v.is_a?(RDL::Type::OptionalType) ? v.type : v + vt = v.optional_var_type? || v.vararg_var_type? ? v.type : v vt.lbounds.each { |lower_t, ast| vt.add_and_propagate_lower_bound(lower_t, ast) } @@ -48,11 +48,12 @@ def self.extract_var_sol(var, category) if category == :arg non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } sol = non_vartype_ubounds.size == 1 ? non_vartype_ubounds[0] : RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::IntersectionType) ## could be, e.g., nominal type if only one type used to create intersection. #return sol elsif category == :ret non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } sol = RDL::Type::UnionType.new(*non_vartype_lbounds) - sol = sol.drop_vars!.canonical if sol.is_a?(RDL::Type::UnionType) && !sol.types.all? { |t| t.is_a?(RDL::Type::VarType) } ## could be, e.g., nominal type if only one type used to create union. + sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. #return sol elsif category == :var if var.lbounds.empty? || (var.lbounds.size == 1 && var.lbounds[0][0] == RDL::Globals.types[:bot]) @@ -64,13 +65,14 @@ def self.extract_var_sol(var, category) ## use lower bounds non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } sol = RDL::Type::UnionType.new(*non_vartype_lbounds) - sol = sol.drop_vars!.canonical if sol.is_a?(RDL::Type::UnionType) && !sol.types.all? { |t| t.is_a?(RDL::Type::VarType) } ## could be, e.g., nominal type if only one type used to create union. + sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. #return sol#RDL::Type::UnionType.new(*non_vartype_lbounds).canonical end else raise "Unexpected VarType category #{category}." end - if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) + #sol.is_a?(RDL::Type::UnionType) + if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) ## Try each rule. Return first non-nil result. ## If no non-nil results, return original solution. ## TODO: check constraints. @@ -81,6 +83,7 @@ def self.extract_var_sol(var, category) new_cons = {} begin if typ + #puts "Attempting to apply heuristic solution #{typ} to #{var}" typ = typ.canonical var.add_and_propagate_upper_bound(typ, nil, new_cons) var.add_and_propagate_lower_bound(typ, nil, new_cons) @@ -102,18 +105,25 @@ def self.extract_var_sol(var, category) begin new_cons = {} sol = var if sol == RDL::Globals.types[:bot] # just use var itself when result of solution extraction was %bot. + return sol if sol.is_a?(RDL::Type::VarType) ## don't add var type as solution sol = sol.canonical var.add_and_propagate_upper_bound(sol, nil, new_cons) var.add_and_propagate_lower_bound(sol, nil, new_cons) @new_constraints = true if !new_cons.empty? + if sol.is_a?(RDL::Type::GenericType) + new_params = sol.params.map { |p| extract_var_sol(p, category) } + sol = RDL::Type::GenericType.new(sol.base, *new_params) + elsif sol.is_a?(RDL::Type::TupleType) + new_params = sol.params.map { |t| extract_var_sol(t, category) } + sol = RDL::Type::TupleType.new(*new_params) + end rescue RDL::Typecheck::StaticTypeError => e puts "Attempted to apply solution #{sol} for var #{var} but got the following error: " puts e undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is - ### MILOD TODO TOMORROW: set sol = the original var here. + sol = var end - return sol end @@ -152,20 +162,42 @@ def self.extract_meth_sol(tmeth) ## BLOCK SOLUTION if tmeth.block && !tmeth.block.ubounds.empty? - non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } - block_sol = non_vartype_ubounds.size > 1 ? RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical : non_vartype_bounds[0] ## doing this once and calling canonical to remove any supertypes that would be eliminated anyway + non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } + non_vartype_ubounds.reject! { |t| t.is_a?(RDL::Type::StructuralType) && (t.methods.size == 1) && t.methods.has_key?(:to_proc) } + + if non_vartype_ubounds.size == 0 + block_sol = tmeth.block + elsif non_vartype_ubounds.size > 1 + block_sols = [] + RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical.types.each { |m| + raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) + block_sols << RDL::Type::MethodType.new(*extract_meth_sol(m)) + } + block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical + else + block_sol = RDL::Type::MethodType.new(*extract_meth_sol(non_vartype_ubounds[0])) + end +=begin + block_sol = non_vartype_ubounds.size > 1 ? RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical : non_vartype_ubounds[0] ## doing this once and calling canonical to remove any supertypes that would be eliminated anyway block_sols = [] + puts "HERE 1 WITH #{block_sol.class}" block_sol.types.each { |m| + puts "here with #{m}" raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) block_sols << RDL::Type::MethodType.new(*extract_meth_sol(m)) } block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical + puts "HERE 2" +=end else block_sol = nil end - ## RET SOLUTION - ret_sol = tmeth.ret.is_a?(RDL::Type::VarType) ? extract_var_sol(tmeth.ret, :ret) : tmeth.ret + if tmeth.ret.to_s == "self" + ret_sol = tmeth.ret + else + ret_sol = tmeth.ret.is_a?(RDL::Type::VarType) ? extract_var_sol(tmeth.ret, :ret) : tmeth.ret + end return [arg_sols, block_sol, ret_sol] end @@ -173,8 +205,10 @@ def self.extract_meth_sol(tmeth) def self.extract_solutions ## Go through once to come up with solution for all var types. #until !@new_constraints + typ_sols = {} loop do @new_constraints = false + typ_sols = {} puts "\n\nRunning solution extraction..." RDL::Globals.constrained_types.each { |klass, name| typ = RDL::Globals.info.get(klass, name, :type) @@ -186,7 +220,7 @@ def self.extract_solutions block_string = block_sol ? " { #{block_sol} }" : nil puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" - + typ_sols[[klass.to_s, name.to_sym]] = "(#{arg_sols.join(', ')})#{block_string} -> #{ret_sol}" elsif name.to_s == "splat_param" else ## Instance/Class (also some times splat parameter) variables: @@ -199,9 +233,83 @@ def self.extract_solutions #typ.solution = var_sol puts "Extracted solution for #{typ} is #{var_sol}." + typ_sols[[klass.to_s, name.to_sym]] = var_sol.to_s end } break if !@new_constraints end + + #return unless $orig_types + + CSV.open("infer_data.csv", "wb") { |csv| + csv << ["Class", "Method", "Inferred Type", "Original Type", "Source Code", "Comments"] + } + + correct_types = 0 + total_potential = 0 + meth_types = 0 + var_types = 0 + typ_sols.each_pair { |km, typ| + klass, meth = km + orig_typ = RDL::Globals.info.get(klass, meth, :orig_type) + if orig_typ.is_a?(Array) + raise "expected just one original type for #{klass}##{meth}" unless orig_typ.size == 1 + orig_typ = orig_typ[0] + end + if orig_typ.nil? + #puts "Original type not found for #{klass}##{meth}." + #puts "Inferred type is: #{typ}" + elsif orig_typ.to_s == typ + #puts "Type for #{klass}##{meth} was correctly inferred, as: " + #puts typ + if orig_typ.is_a?(RDL::Type::MethodType) + correct_types += orig_typ.args.size + 1 ## 1 for ret + total_potential += orig_typ.args.size + 1 ## 1 for ret + meth_types += 1 + if !orig_typ.block.nil? + correct_types += orig_typ.block.args.size + 1 ## 1 for ret + total_potential += orig_typ.block.args.size + 1 ## 1 for ret + end + else + var_types += 1 + correct_types += 1 + total_potential += 1 + end + else + puts "Difference encountered for #{klass}##{meth}." + puts "Inferred: #{typ}" + puts "Original: #{orig_typ}" + if orig_typ.is_a?(RDL::Type::MethodType) + total_potential += orig_typ.args.size + 1 ## 1 for ret + total_potential += orig_typ.block.args.size + 1 if !orig_typ.block.nil? + meth_types += 1 + else + total_potential += 1 + var_types += 1 + end + + end + + if !meth.to_s.include?("@") && !meth.to_s.include?("$")#orig_typ.is_a?(RDL::Type::MethodType) + CSV.open("infer_data.csv", "a+") { |csv| + ast = RDL::Typecheck.get_ast(klass, meth) + code = ast.loc.expression.source + if RDL::Util.has_singleton_marker(klass) + comment = RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)).method(meth).comment + else + comment = RDL::Util.to_class(klass).instance_method(meth).comment + end + csv << [klass, meth, typ, orig_typ, code, comment] + } + end + } + + puts "Total correct (that could be automatically inferred): #{correct_types}" + puts "Total # method types: #{meth_types}" + puts "Total # variable types: #{var_types}" + puts "Total # individual types: #{total_potential}" end + + + end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 5b43d34f..efb027a3 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -20,7 +20,7 @@ def self.struct_to_nominal(var_type) matching_classes = ObjectSpace.each_object(Class).select { |c| class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods - matching_classes.reject! { |c| c.to_s.start_with?("# 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } union = RDL::Type::UnionType.new(*nom_sing_types).canonical - struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds + #struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds return union @@ -66,16 +66,17 @@ def model_set_type end +if defined? Rails + RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } + RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } +end -RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } -RDL::Heuristic.add(:struct_to_nominal) { |var| RDL::Heuristic.struct_to_nominal(var) } -RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } -RDL::Heuristic.add(:int_names) { |var| if var.base_name.end_with?("id") || (var.base_name == "num") || (var.base_name == "count") then RDL::Globals.types[:integer] end } -RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") then RDL::Globals.parser.scan_str "#T Array" end } +RDL::Heuristic.add(:struct_to_nominal) { |var| t1 = Time.now; g = RDL::Heuristic.struct_to_nominal(var); $stn = $stn + (Time.now - t1); g } +RDL::Heuristic.add(:int_names) { |var| if var.base_name.end_with?("id") || (var.base_name.end_with? "num") || (var.base_name.end_with? "count") then RDL::Globals.types[:integer] end } +RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") || (var.base_name.end_with? "nums") || (var.base_name.end_with? "counts") then RDL::Globals.parser.scan_str "#T Array" end } RDL::Heuristic.add(:predicate_method) { |var| if var.base_name.end_with?("?") then RDL::Globals.types[:bool] end } - +RDL::Heuristic.add(:string_name) { |var| if var.base_name.end_with?("name") then RDL::Globals.types[:string] end } RDL::Heuristic.add(:hash_access) { |var| - puts "trying hash_access!" old_var = var var = var.type if old_var.is_a?(RDL::Type::OptionalType) types = [] @@ -91,14 +92,21 @@ def model_set_type types.each { |struct| struct.methods.each { |meth, typ| if meth == :[] - value_type = typ.ret.is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.ret, :arg).canonical : typ.ret.canonical - hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: if ret is var type, should we extract solution first? + value_type = typ.ret#typ.ret.is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.ret, :arg).canonical : typ.ret.canonical elsif meth == :[]= - value_type = typ.args[1].is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.args[1], :arg).canonical : typ.args[1].canonical - hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(typ.args[1]) + value_type = typ.args[1]#typ.args[1].is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.args[1], :arg).canonical : typ.args[1].canonical else raise "Method should be one of :[] or :[]=, got #{meth}." end + if value_type.is_a?(RDL::Type::UnionType) + RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical + elsif value_type.is_a?(RDL::Type::IntersectionType) + RDL::Type::IntersectionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical + else + value_type = RDL::Typecheck.extract_var_sol(value_type, :arg) + end + #value_type = value_type.drop_vars!.canonical if (value_type.is_a?(RDL::Type::UnionType) || value_type.is_a?(RDL::Type::IntersectionType)) && (!value_type.types.all? { |t| t.is_a?(RDL::Type::VarType) }) + hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: } } #var.ubounds.delete_if { |t| t.is_a?(RDL::Type::StructuralType) } #= [] ## might have to change this later, in particular to take advantage of comp types when performing solution extraction diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 72314ba5..cbcb48d4 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -1,6 +1,7 @@ module RDL::Typecheck class StaticTypeError < StandardError; end + class BlockTypeError < StaticTypeError; end @@empty_hash_type = RDL::Type::FiniteHashType.new(Hash.new, nil) @@asgn_to_var = { lvasgn: :lvar, ivasgn: :ivar, cvasgn: :cvar, gvasgn: :gvar } @@ -161,8 +162,9 @@ def self.capture(scope, x, t, ast: nil) end # report msg at ast's loc - def self.error(reason, args, ast) - raise StaticTypeError, ("\n" + (Diagnostic.new :error, reason, args, ast.loc.expression).render.join("\n")) + def self.error(reason, args, ast, block: false) + errtype = block ? BlockTypeError : StaticTypeError + raise errtype, ("\n" + (Diagnostic.new :error, reason, args, ast.loc.expression).render.join("\n")) end def self.note(reason, args, ast) @@ -268,17 +270,20 @@ def self.infer(klass, meth) begin old_captured = scope[:captured].dup if body.nil? - body_type = RDL.types[:nil] + body_type = RDL::Globals.types[:nil] else targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this + @num_casts = 0 _, body_type = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) ## TODO: need separate argument indicating we're performing inference? or is this exactly the same as type checking... end old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured]) end until old_captured == scope[:captured] - body_type = self_type if meth == :initialize + #body_type = self_type if meth == :initialize + body_type = RDL::Globals.parser.scan_str "#T self" if meth == :initialize if body_type.is_a?(RDL::Type::UnionType) - body_type.types.each { |t| RDL::Type::Type.leq(t, ret_vartype, ast: ast) } else + body_type.types.each { |t| RDL::Type::Type.leq(t, ret_vartype, ast: ast) } + else RDL::Type::Type.leq(body_type, ret_vartype, ast: ast) end @@ -333,6 +338,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) body_type = RDL::Globals.types[:nil] else targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this + @num_casts = 0 _, body_type, body_effect = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) end old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured]) @@ -347,6 +353,10 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) RDL::Globals.info.set(klass, meth, :typechecked, true) end + def self.get_num_casts + return @num_casts + end + def self.effect_leq(e1, e2) raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) } p1, t1 = e1 @@ -388,9 +398,10 @@ def self.effect_union(e1, e2) ### TODO: clean up below. Should probably incorporate it into `targs.merge` call in `self.typecheck`. def self.widen_scopes(h1, h2) - h1new = {} + h1new = h1.nil? ? nil : {} h2new = {} [[h1, h1new], [h2, h2new]].each { |hash, newhash| + next if h1.nil? hash.each { |k, v| case v when RDL::Type::TupleType @@ -425,6 +436,14 @@ def self.widen_scopes(h1, h2) [h1new, h2new] end + def self.widen_envs(e1, e2) + e1_env = e1.nil? ? nil : e1.env + e1_hash, e2_hash = widen_scopes(e1_env, e2.env) + e2.env = e2_hash + e1.env = e1_hash if e1 + [e1, e2] + end + # [+ scope +] is used to typecheck default values for optional arguments # [+ env +] is used to typecheck default values for optional arguments # [+ type +] is a MethodType @@ -437,56 +456,62 @@ def self.args_hash(scope, env, type, args, ast, kind) tpos = 0 # position in type.args kw_args_matched = [] kw_rest_matched = false - args.children.each { |arg| - error :type_args_fewer, [kind, kind], arg if tpos >= type.args.length && arg.type != :blockarg # blocks could be called with yield + args.children.each_with_index { |arg, index| + error :type_args_fewer, [kind, kind], arg, block: (kind == 'block') if tpos >= type.args.length && arg.type != :blockarg # blocks could be called with yield targ = type.args[tpos] (if (targ.is_a?(RDL::Type::AnnotatedArgType) || targ.is_a?(RDL::Type::DependentArgType) || targ.is_a?(RDL::Type::BoundArgType)) then targ = targ.type end) if arg.type == :arg - error :type_arg_kind_mismatch, [kind, 'optional', 'required'], arg if (targ.optional? && !kind == 'block') ## block arg type can be optional while actual arg is required - error :type_arg_kind_mismatch, [kind, 'vararg', 'required'], arg if targ.vararg? - targs[arg.children[0]] = targ - env = env.merge(Env.new(arg.children[0] => targ)) - tpos += 1 + if kind == 'block' + targs[arg.children[0]] = (targ.optional? || targ.vararg?) ? targ.type : targ + env = env.merge(Env.new(arg.children[0] => targ)) + tpos += 1 unless (targ.optional? || targ.vararg?) && !(index == args.children.size - 1) + else + error :type_arg_kind_mismatch, [kind, 'optional', 'required'], arg, block: (kind == 'block') if (targ.optional? && !kind == 'block') ## block arg type can be optional while actual arg is required + error :type_arg_kind_mismatch, [kind, 'vararg', 'required'], arg, block: (kind == 'block') if targ.vararg? + targs[arg.children[0]] = targ + env = env.merge(Env.new(arg.children[0] => targ)) + tpos += 1 + end elsif arg.type == :optarg - error :type_arg_kind_mismatch, [kind, 'vararg', 'optional'], arg if targ.vararg? - error :type_arg_kind_mismatch, [kind, 'required', 'optional'], arg if !targ.optional? + error :type_arg_kind_mismatch, [kind, 'vararg', 'optional'], arg, block: (kind == 'block') if targ.vararg? + error :type_arg_kind_mismatch, [kind, 'required', 'optional'], arg, block: (kind == 'block') if !targ.optional? env, default_type = tc(scope, env, arg.children[1]) - error :optional_default_type, [default_type, targ.type], arg.children[1] unless RDL::Type::Type.leq(default_type, targ.type, ast: ast) + error :optional_default_type, [default_type, targ.type], arg.children[1], block: (kind == 'block') unless RDL::Type::Type.leq(default_type, targ.type, ast: ast) targs[arg.children[0]] = targ.type env = env.merge(Env.new(arg.children[0] => targ.type)) tpos += 1 elsif arg.type == :restarg - error :type_arg_kind_mismatch, [kind, 'optional', 'vararg'], arg if targ.optional? - error :type_arg_kind_mismatch, [kind, 'required', 'vararg'], arg if !targ.vararg? + error :type_arg_kind_mismatch, [kind, 'optional', 'vararg'], arg, block: (kind == 'block') if targ.optional? + error :type_arg_kind_mismatch, [kind, 'required', 'vararg'], arg, block: (kind == 'block') if !targ.vararg? targs[arg.children[0]] = RDL::Type::GenericType.new(RDL::Globals.types[:array], targ.type) tpos += 1 elsif arg.type == :kwarg - error :type_args_no_kws, [kind], arg unless targ.is_a?(RDL::Type::FiniteHashType) + error :type_args_no_kws, [kind], arg, block: (kind == 'block') unless targ.is_a?(RDL::Type::FiniteHashType) kw = arg.children[0] - error :type_args_no_kw, [kind, kw], arg unless targ.elts.has_key? kw + error :type_args_no_kw, [kind, kw], arg, block: (kind == 'block') unless targ.elts.has_key? kw tkw = targ.elts[kw] - error :type_args_kw_mismatch, [kind, 'optional', kw, 'required'], arg if tkw.is_a? RDL::Type::OptionalType + error :type_args_kw_mismatch, [kind, 'optional', kw, 'required'], arg, block: (kind == 'block') if tkw.is_a? RDL::Type::OptionalType kw_args_matched << kw targs[kw] = tkw env = env.merge(Env.new(kw => tkw)) elsif arg.type == :kwoptarg - error :type_args_no_kws, [kind], arg unless targ.is_a?(RDL::Type::FiniteHashType) + error :type_args_no_kws, [kind], arg, block: (kind == 'block') unless targ.is_a?(RDL::Type::FiniteHashType) kw = arg.children[0] - error :type_args_no_kw, [kind, kw], arg unless targ.elts.has_key? kw + error :type_args_no_kw, [kind, kw], arg, block: (kind == 'block') unless targ.elts.has_key? kw tkw = targ.elts[kw] - error :type_args_kw_mismatch, [kind, 'required', kw, 'optional'], arg if !tkw.is_a?(RDL::Type::OptionalType) + error :type_args_kw_mismatch, [kind, 'required', kw, 'optional'], arg, block: (kind == 'block') if !tkw.is_a?(RDL::Type::OptionalType) env, default_type = tc(scope, env, arg.children[1]) - error :optional_default_kw_type, [kw, default_type, tkw.type], arg.children[1] unless RDL::Type::Type.leq(default_type, tkw.type, ast: ast) + error :optional_default_kw_type, [kw, default_type, tkw.type], arg.children[1], block: (kind == 'block') unless RDL::Type::Type.leq(default_type, tkw.type, ast: ast) kw_args_matched << kw targs[kw] = tkw.type env = env.merge(Env.new(kw => tkw.type)) elsif arg.type == :kwrestarg - error :type_args_no_kws, [kind], e unless targ.is_a?(RDL::Type::FiniteHashType) - error :type_args_no_kw_rest, [kind], arg if targ.rest.nil? + error :type_args_no_kws, [kind], e, block: (kind == 'block') unless targ.is_a?(RDL::Type::FiniteHashType) + error :type_args_no_kw_rest, [kind], arg, block: (kind == 'block') if targ.rest.nil? targs[arg.children[0]] = RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:symbol], targ.rest) kw_rest_matched = true elsif arg.type == :blockarg - error :type_arg_block, [kind, kind], arg unless type.block + error :type_arg_block, [kind, kind], arg, block: (kind == 'block') unless type.block targs[arg.children[0]] = type.block # Note no check that if type.block then method expects block, because blocks can be called with yield else @@ -495,11 +520,11 @@ def self.args_hash(scope, env, type, args, ast, kind) } if (tpos == type.args.length - 1) && type.args[tpos].is_a?(RDL::Type::FiniteHashType) rest = type.args[tpos].elts.keys - kw_args_matched - error :type_args_kw_more, [kind, rest.map { |s| s.to_s }.join(", "), kind], ast unless rest.empty? - error :type_args_kw_rest, [kind], ast unless kw_rest_matched || type.args[tpos].rest.nil? + error :type_args_kw_more, [kind, rest.map { |s| s.to_s }.join(", "), kind], ast, block: (kind == 'block') unless rest.empty? + error :type_args_kw_rest, [kind], ast, block: (kind == 'block') unless kw_rest_matched || type.args[tpos].rest.nil? else unless (type.args.length == 1) && (type.args[0].is_a?(RDL::Type::OptionalType) || type.args[0].is_a?(RDL::Type::VarargType)) && args.children.empty? - error :type_args_more, [kind, kind], (if args.children.empty? then ast else args end) if (type.args.length != tpos) + error :type_args_more, [kind, kind], (if args.children.empty? then ast else args end), block: (kind == 'block') if (type.args.length != tpos) end end return [env, targs] @@ -512,7 +537,7 @@ def self.get_super_owner(slf, m) trecv_owner = get_super_owner_from_class(slf.val.singleton_class, m) RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(trecv_owner)) else - raise Exception, 'self is singleton class but nominal is not Class' + raise Exception, "self is singleton class but nominal is not Class, it is #{slf.nominal.name}" end when RDL::Type::NominalType RDL::Type::NominalType.new(get_super_owner_from_class(slf.klass, m)) @@ -541,10 +566,17 @@ def self.tc(scope, env, e) when :false [env, RDL::Globals.types[:false], [:+, :+]] when :str, :string - [env, RDL::Type::PreciseStringType.new(e.children[0]), [:+, :+]] + t = RDL::Config.instance.use_precise_string ? RDL::Type::PreciseStringType.new(e.children[0]) : RDL::Globals.types[:string] + [env, t, [:+, :+]] when :complex, :rational # constants [env, RDL::Type::NominalType.new(e.children[0].class), [:+, :+]] - when :int, :float, :sym # singletons + when :int, :float + if RDL::Config.instance.number_mode && (e.type == :float) + [env, RDL::Type::NominalType.new(Integer), [:+, :+]] + else + [env, RDL::Type::SingletonType.new(e.children[0]), [:+, :+]] + end + when :sym # singleton [env, RDL::Type::SingletonType.new(e.children[0]), [:+, :+]] when :dstr, :xstr # string (or execute-string) with interpolation effi = [:+, :+] @@ -553,15 +585,20 @@ def self.tc(scope, env, e) e.children.each { |ei| envi, ti, eff_new = tc(scope, envi, ei) effi = effect_union(effi, eff_new) - if ei.type == :str || ei.type == :string - ## for strings, just append the string itself - prec_str << ei.children[0] + if RDL::Config.instance.use_precise_string + if ei.type == :str || ei.type == :string + ## for strings, just append the string itself + prec_str << ei.children[0] + else + ## for interpolated part, append the interpolated part + prec_str << (if ti.is_a?(RDL::Type::SingletonType) then ti.val.to_s else ti end) + end + t = RDL::Type::PreciseStringType.new(*prec_str) else - ## for interpolated part, append the interpolated part - prec_str << (if ti.is_a?(RDL::Type::SingletonType) then ti.val.to_s else ti end) + t = RDL::Globals.types[:string] end } - [envi, RDL::Type::PreciseStringType.new(*prec_str), effi] + [envi, t, effi] when :dsym # symbol with interpolation envi = env e.children.each { |ei| envi, _ = tc(scope, envi, ei) } @@ -595,7 +632,14 @@ def self.tc(scope, env, e) tis << RDL::Type::TupleType.new(*ti.params) elsif ti.is_a?(RDL::Type::SingletonType) && ti.val.nil? # nil gets thrown out - elsif RDL::Type::Type.leq(RDL::Globals.types[:array], ti, ast: e) || RDL::Type::Type.leq(ti, RDL::Globals.types[:array], ast: ast) || + elsif ti.is_a?(RDL::Type::UnionType) && ti.types.all? { |t| t.is_a?(RDL::Type::GenericType) && t.base == RDL::Globals.types[:array] } + is_array = true + tis << RDL::Type::UnionType.new(*ti.types.map { |t| t.params[0] }).canonical + elsif ti.is_a?(RDL::Type::VarType) + new_arr_type = RDL::Type::GenericType.new(RDL::Globals.types[:array], v = make_unknown_var_type(ti, :splat_param, "splat param")) + RDL::Type::Type.leq(ti, new_arr_type, ast: e) + tis << v + elsif RDL::Type::Type.leq(RDL::Globals.types[:array], ti, ast: e) || RDL::Type::Type.leq(ti, RDL::Globals.types[:array], ast: e) || RDL::Type::Type.leq(RDL::Globals.types[:hash], ti, ast: e) || RDL::Type::Type.leq(ti, RDL::Globals.types[:hash], ast: e) # might or might not be array...can't splat... error :cant_splat, [ti], ei @@ -748,7 +792,7 @@ def self.tc(scope, env, e) envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left) } tuple_type = RDL::Type::TupleType.new(*new_tuple) - RDL::Type::Type.leq(tright, tuple_type, ast: e) + #RDL::Type::Type.leq(tright, tuple_type, ast: e) # don't think this is necessary [envi, tright, effi] else error :masgn_bad_rhs, [tright], e.children[1] @@ -845,34 +889,7 @@ def self.tc(scope, env, e) [env, RDL::Globals.types[:string], [:+, :+]] when :const c = find_constant(env, e) - case c - when TrueClass, FalseClass, Complex, Rational, Integer, Float, Symbol, Class, Module - [env, RDL::Type::SingletonType.new(c), [:+, :+]] - when Hash - fh = c.transform_keys { |k| - case k - when Symbol - k ## symbol keys in FHTs are used directly - when TrueClass, FalseClass, Complex, Rational, Integer, Float, Class, Module - RDL::Type::SingletonType.new(k) - else - RDL::Type::NominalType.new(k.class) - end - } - - fh = fh.transform_values { |v| - case v - when TrueClass, FalseClass, Complex, Rational, Integer, Float, Symbol, Class, Module - RDL::Type::SingletonType.new(v) - else - RDL::Type::NominalType.new(v.class) - end - } - - [env, RDL::Type::FiniteHashType.new(fh, nil), [:+, :+]] - else - [env, RDL::Type::NominalType.new(c.class), [:+, :+]] - end + [env, to_type(c), [:+, :+]] when :defined? # do not type check subexpression, since it may not be type correct, e.g., undefined variable [env, RDL::Globals.types[:string], [:+, :+]] @@ -903,6 +920,9 @@ def self.tc(scope, env, e) RDL::Type::Type.leq(ti, new_arr_type, ast: e) tactuals << RDL::Type::VarargType.new(v) #tactuals << RDL::Type::VarargType.new(RDL::Globals.types[:top]) + elsif ti.is_a?(RDL::Type::UnionType) && ti.types.all? { |t| t.is_a?(RDL::Type::GenericType) && t.base == RDL::Globals.types[:array] } + params_union = RDL::Type::UnionType.new(*ti.types.map { |t| t.params[0] }).canonical + tactuals << RDL::Type::VarargType.new(params_union) else error :cant_splat, [ti], ei.children[0] end @@ -990,9 +1010,9 @@ def self.tc(scope, env, e) elsif e.type == :and && !(tleft == RDL::Globals.types[:bool] || tleft == RDL::Globals.types[:false]) ## when :and and left is NOT false, then we always get back tright (or nil, which is a subtype of tright) ## no equivalent for :or, because if left is nil, could still get right - [Env.join(e, envleft, envright), tright, effect_union(effleft, effright)] + [envleft.merge(envright), tright, effect_union(effleft, effright)] else - [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical, effect_union(effleft, effright)] + [envleft.merge(envright), RDL::Type::UnionType.new(tleft, tright).canonical, effect_union(effleft, effright)] end # when :not # in latest Ruby, not is a method call that could be redefined, so can't count on its behavior # a1, t1 = tc(scope, a, e.children[0]) @@ -1047,7 +1067,7 @@ def self.tc(scope, env, e) error :generic_error, ["general refinement for generics not implemented yet"], wclause end else - next unless tcontrol <= new_typ || new_typ <= tcontrol # If control can't possibly match type, skip this branch + next unless tcontrol.is_a?(RDL::Type::VarType) || (tcontrol <= new_typ || new_typ <= tcontrol) # If control can't possibly match type, skip this branch initial_env = initial_env.bind(e.children[0].children[0], new_typ, force: true) # note force is safe above because the env from this arm will be joined with the other envs # (where the type was not refined like this), so after the case the variable will be back to its @@ -1289,10 +1309,11 @@ def self.tc(scope, env, e) tactuals = [] block = scope[:block] effi = [:+, :+] +=begin if block raise Exception, 'block in super method with block not supported' end - +=end scope_merge(scope, block: nil, break: env, next: env) { |sscope| e.children.each { |ei| if ei.type == :splat @@ -1327,16 +1348,16 @@ def self.tc(scope, env, e) when :zsuper envi = env block = scope[:block] - +=begin if block raise Exception, 'super method not supported' end - +=end klass = RDL::Util.to_class @cur_meth[0] mname = @cur_meth[1] sklass = get_super_owner_from_class klass, mname sklass_str = RDL::Util.to_class_str sklass - stype = RDL::Globals.info.get_with_aliases(sklass_str, mname, :type) + stype = lookup(scope, sklass_str, mname, e)[0]#RDL::Globals.info.get_with_aliases(sklass_str, mname, :type) error :no_instance_method_type, [sklass_str, mname], e unless stype raise Exception, "unsupported intersection type in super, e = #{e}" if stype.size > 1 tactuals = stype[0].args @@ -1351,6 +1372,30 @@ def self.tc(scope, env, e) end end + def self.to_type(val, as_key=false) + case val + when Symbol + if as_key then val else RDL::Type::SingletonType.new(val) end + when TrueClass, FalseClass, Class, Module + RDL::Type::SingletonType.new(val) + when Complex, Rational, Integer, Float + if RDL::Config.instance.number_mode && !(val.class == Integer) + RDL::Type::NominalType.new(Integer) + else + RDL::Type::SingletonType.new(val) + end + when Array + tarr = val.map { |v| to_type(v) } + RDL::Type::TupleType.new(*tarr) + when Hash + fh = val.transform_keys { |k| to_type(k, true) } + fh = fh.transform_values { |v| to_type(v) } + RDL::Type::FiniteHashType.new(fh, nil) + else + RDL::Type::NominalType.new(val.class) + end + end + # [+ kind +] is :lvar, :ivar, :cvar, or :gvar # [+ name +] is the variable name, which should be a symbol # [+ e +] is the expression for which errors should be reported @@ -1369,6 +1414,7 @@ def self.tc_var(scope, env, kind, name, e) end when :ivar, :cvar, :gvar klass = (if kind == :gvar then RDL::Util::GLOBAL_NAME else env[:self].to_s end) + klass = "Integer" if klass == "Number" klass = RDL::Util.remove_singleton_marker klass if RDL::Util.has_singleton_marker klass if RDL::Globals.info.has?(klass, name, :type) type = RDL::Globals.info.get(klass, name, :type) @@ -1377,7 +1423,8 @@ def self.tc_var(scope, env, kind, name, e) elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } type = make_unknown_var_type(klass, name, :var) else - error :untyped_var, [kind_text, name, klass], e + type = make_unknown_var_type(klass, name, :var) + #error :untyped_var, [kind_text, name, klass], e end [env, type.canonical] else @@ -1407,16 +1454,17 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) end when :ivasgn, :cvasgn, :gvasgn klass = (if kind == :gvasgn then RDL::Util::GLOBAL_NAME else env[:self].to_s end) + klass = "Integer" if klass == "Number" klass = RDL::Util.remove_singleton_marker klass if RDL::Util.has_singleton_marker klass if RDL::Globals.info.has?(klass, name, :type) tleft = RDL::Globals.info.get(klass, name, :type) elsif RDL::Config.instance.assume_dyn_type tleft = RDL::Globals.types[:dyn] elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } - type = make_unknown_var_type(klass, name, :var) - + tleft = make_unknown_var_type(klass, name, :var) else - error :untyped_var, [kind_text, name, klass], e + tleft = make_unknown_var_type(klass, name, :var) + #error :untyped_var, [kind_text, name, klass], e end error :vasgn_incompat, [tright.to_s, tleft.to_s], e unless RDL::Type::Type.leq(tright, tleft, inst={}, true, ast: e) tright.instantiate(inst) @@ -1474,6 +1522,7 @@ def self.tc_type_cast(scope, env, e) end sub_expr = e.children[2] env2, _ = tc(scope, env, sub_expr) + @num_casts += 1 [env2, typ] end @@ -1578,6 +1627,19 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) # Like tc_send but trecv should never be a union type # Returns array of possible return types, or throws exception if there are none def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) +=begin + puts "----------------------" + puts "Type checking method call to #{meth} for receiver #{trecv} and tactuals of size #{tactuals.size}:" + tactuals.each { |t| puts t } + puts "----------------------" +=end + if (trecv == RDL::Globals.types[:array]) + trecv = RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Globals.types[:bot]) + elsif (trecv == RDL::Globals.types[:hash]) + trecv = RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:bot], RDL::Globals.types[:bot]) + elsif trecv.is_a?(RDL::Type::AnnotatedArgType) || trecv.is_a?(RDL::Type::DependentArgType) + trecv = trecv.type + end return [tc_send_class(trecv, e), [[:+, :+]]] if (meth == :class) && (tactuals.empty?) ts = [] # Array, i.e., an intersection types case trecv @@ -1587,6 +1649,11 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, meth_lookup = :initialize trecv_lookup = trecv.val.to_s self_inst = RDL::Type::NominalType.new(trecv.val) + elsif (defined? ApplicationMailer) && trecv.val.ancestors.include?(ApplicationMailer) + ## Thanks to Rails magic, Mailer instance methods can be called as singletons. + meth_lookup = meth + trecv_lookup = trecv.val.to_s + self_inst = RDL::Type::NominalType.new(trecv.val) else meth_lookup = meth trecv_lookup = RDL::Util.add_singleton_marker(trecv.val.to_s) @@ -1631,6 +1698,13 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, self_klass = RDL::Util.to_class(trecv.name) when RDL::Type::GenericType ts, es = lookup(scope, trecv.base.name, meth, e) + if ts.nil? && (trecv.base.name == "ActiveRecord_Relation") && defined? DBType + ## ActiveRecord_Relation methods that don't exist are delegated to underlying scoped class. + ## Maybe there's a way not to bake this in to typechecker, but I can't think of one. + ts, es = lookup(scope, "[s]"+DBType.rec_to_nominal(trecv).name, meth, e) + error :no_instance_method_type, [trecv.base.name, meth], e unless ts + ts = ts.map { |t| RDL::Type::MethodType.new(t.args, t.block, trecv) } + end error :no_instance_method_type, [trecv.base.name, meth], e unless ts inst = trecv.to_inst.merge(self: trecv) self_klass = RDL::Util.to_class(trecv.base.name) @@ -1639,7 +1713,8 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ts, es = lookup(scope, "Array", meth, e) error :no_instance_method_type, ["Array", meth], e unless ts #inst = trecv.to_inst.merge(self: trecv) - inst = { self: trecv } + t_bind = trecv.promote.params[0].to_s == "t" ? RDL::Globals.types[:bot] : trecv.promote.params[0] + inst = { self: trecv, t: t_bind } self_klass = Array else ## need to promote in this case @@ -1655,7 +1730,9 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ts, es = lookup(scope, "Hash", meth, e) error :no_instance_method_type, ["Hash", meth], e unless ts #inst = trecv.to_inst.merge(self: trecv) - inst = { self: trecv } + k_bind = trecv.promote.params[0].to_s == "k" ? RDL::Globals.types[:bot] : trecv.promote.params[0] + v_bind = trecv.promote.params[1].to_s == "v" ? RDL::Globals.types[:bot] : trecv.promote.params[1] + inst = { self: trecv , k: k_bind, v: v_bind } self_klass = Hash else ## need to promote in this case @@ -1682,21 +1759,41 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, self_klass = RDL::Util.to_class(trecv.name) end when RDL::Type::VarType - ret_type = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :ret, name: "ret") + ## prevent overfitting + tactuals = tactuals.map { |t| + if t.is_a?(RDL::Type::PreciseStringType) + RDL::Globals.types[:string] + elsif t.is_a?(RDL::Type::SingletonType) && !(t.val.class == Symbol) ## Symbol singletons come in handy for reconstructing finite hash types + RDL::Type::NominalType.new(t.val.class) + else + t + end } + + if meth == :to_s + ret_type = RDL::Globals.types[:string] + elsif meth == :to_i + ret_type = RDL::Globals.types[:integer] + else + ret_type = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :ret, name: "ret") + end if block - blk_args = block[0].children.map {|a| a.children[0]} - blk_arg_vartypes = blk_args.map { |a| - RDL::Type::VarType.new(cls: trecv, meth: meth, category: :block_arg, name: a.to_s ) }#block[0].children[0].to_s) } - blk_ret_vartype = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :block_ret, name: "block_ret") - block_type = RDL::Type::MethodType.new(blk_arg_vartypes, nil, blk_ret_vartype) - - meth_type = RDL::Type::MethodType.new(tactuals, block_type, ret_type) - - tmeth_inst = tc_arg_types(meth_type, tactuals) - - raise "Expected method to be instantiated." unless tmeth_inst - tc_block(scope, env, block_type, block, tmeth_inst) + if block[0].is_a?(RDL::Type::MethodType) + meth_type = RDL::Type::MethodType.new(tactuals, block[0], ret_type) + else + blk_args = block[0].children.map {|a| a.children[0]} + blk_arg_vartypes = blk_args.map { |a| + RDL::Type::VarType.new(cls: trecv, meth: meth, category: :block_arg, name: a.to_s ) }#block[0].children[0].to_s) } + blk_ret_vartype = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :block_ret, name: "block_ret") + block_type = RDL::Type::MethodType.new(blk_arg_vartypes, nil, blk_ret_vartype) + + meth_type = RDL::Type::MethodType.new(tactuals, block_type, ret_type) + + tmeth_inst = tc_arg_types(meth_type, tactuals) + + raise "Expected method to be instantiated." unless tmeth_inst + tc_block(scope, env, block_type, block, tmeth_inst) + end else meth_type = RDL::Type::MethodType.new(tactuals, nil, ret_type) end @@ -1739,6 +1836,8 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, trets_tmp = [] deferred_constraints = [] ts.each_with_index { |tmeth, ind| # MethodType + got_match = false + current_dcs = [] comp_type = false if tmeth.is_a? RDL::Type::DynamicType trets_tmp << RDL::Type::DynamicType.new @@ -1760,10 +1859,14 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, tmeth = tmeth.instantiate(inst) if inst tmeth_names << tmeth #deferred_constraints = [] - tmeth_inst = tc_arg_types(tmeth, tactuals_expanded, deferred_constraints) + tmeth_inst = tc_arg_types(tmeth, tactuals_expanded, current_dcs) #apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? if tmeth_inst - effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block + begin + effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block + rescue BlockTypeError => err + block_mismatch = true + end if es es = es.map { |es_effect| if es_effect.nil? then es_effect else es_effect.clone end } es.each { |es_effect| ## expecting just one effect per method right now. can clean this up later. @@ -1795,30 +1898,35 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, elsif (tmeth.ret.instance_of?(RDL::Type::AnnotatedArgType) || tmeth.ret.instance_of?(RDL::Type::DependentArgType) || tmeth.ret.instance_of?(RDL::Type::BoundArgType)) error :bad_initialize_type, [], e unless (tmeth.ret.type == init_typ) else - error :bad_initialize_type, [], e unless (tmeth.ret == init_typ) + error :bad_initialize_type, [], e unless (tmeth.ret == init_typ) unless ((trecv.val == Hash) && tmeth.ret.is_a?(RDL::Type::FiniteHashType)) || ((trecv.val == Array) && tmeth.ret.is_a?(RDL::Type::TupleType)) end - trets_tmp << init_typ - else + #trets_tmp << init_typ unless block_mismatch + trets_tmp << tmeth.ret.instantiate(tmeth_inst) + got_match = true + elsif !block_mismatch trets_tmp << (tmeth.ret.instantiate(tmeth_inst)) # found a match for this subunion; add its return type to trets_tmp + got_match = true if comp_type && RDL::Config.instance.check_comp_types && !union if (e.type == :op_asgn) && op_asgn ## Hacky trick here. Because the ast `e` is used twice when type checking an op_asgn, ## in one of the cases we will use the object_id of its object_id to get two different mappings. RDL::Globals.comp_type_map[e.object_id.object_id] = [tmeth, tmeth_old, tmeth_res, self_klass, trecv_old, targs_old, (binds || {})] - else - RDL::Globals.comp_type_map[e.object_id] = [tmeth, tmeth_old, tmeth_res, self_klass, trecv_old, targs_old, (binds || {})] + else + RDL::Globals.comp_type_map[e.object_id] = [tmeth, tmeth_old, tmeth_res, self_klass, trecv_old, targs_old, (binds || {})] end end end + block_mismatch = false end end + deferred_constraints += current_dcs if got_match } if trets_tmp.empty? # no arm of the intersection matched this expanded actuals lists, so reset trets to signal error and break loop trets = [] break else - apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? + apply_deferred_constraints(deferred_constraints, e) if !deferred_constraints.empty? trets.concat(trets_tmp) end } @@ -1853,9 +1961,9 @@ def self.apply_deferred_constraints(deferred_constraints, e) ## If all the LHS types are the same single type, and all the RHS types ## are Numeric types (which is the case for almost all arithmetic methods), ## then only apply the single constraint that t1 <= Numeric. - RDL::Type::Type.leq(deferred_constraints[0][0], RDL::Globals.types[:numeric], ast: e) + RDL::Type::Type.leq(deferred_constraints[0][0], RDL::Globals.types[:numeric], nil, false, ast: e) else - deferred_constraints.each { |t1, t2| RDL::Type::Type.leq(t1, t2, ast: e) } + deferred_constraints.each { |t1, t2| RDL::Type::Type.leq(t1, t2, nil, false, ast: e) } end end @@ -1924,7 +2032,8 @@ def self.tc_send_class(trecv, e) when RDL::Type::PreciseStringType [RDL::Type::SingletonType.new(String)] when RDL::Type::VarType - error :recv_var_type, [trecv], e + [RDL::Type::NominalType.new(Class)] + #error :recv_var_type, [trecv], e when RDL::Type::MethodType [RDL::Type::SingletonType.new(Proc)] else @@ -1943,11 +2052,6 @@ def self.tc_arg_types(tmeth, tactuals, deferred_constraints=[]) formal, actual, inst, deferred_constraints = states.pop inst = inst.dup # avoid aliasing insts in different states since Type.leq mutates inst arg if formal == tformals.size && actual == tactuals.size # Matched everything -=begin - deferred_constraints.each { |t1, t2| - t1 <= t2 - } -=end return inst end next if formal >= tformals.size # Too many actuals to match @@ -1960,9 +2064,12 @@ def self.tc_arg_types(tmeth, tactuals, deferred_constraints=[]) t = t.type if actual == tactuals.size states << [formal+1, actual, inst, deferred_constraints] # skip over optinal formal - elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false, deferred_constraints) + elsif RDL::Type::Type.leq(tactuals[actual], t, inst, false, deferred_constraints) #&& (not (tactuals[actual].is_a?(RDL::Type::VarargType))) states << [formal+1, actual+1, inst, deferred_constraints] # match - states << [formal+1, actual, inst, deferred_constraints] # skip + #states << [formal+1, actual, inst, deferred_constraints] unless tactuals[actual].is_a?(RDL::Type::VarType) && tformals[formal+1].is_a?(RDL::Type::FiniteHashType)# skip + ## I added the `unless` guard above because of cases like `redirect_to @list, action: 'something'`. + ## The clear intention of the programmer is for @list to match a non-FHT formal arg, so don't + ## want to match it with the subsequent FHT. else states << [formal+1, actual, inst, deferred_constraints] # types don't match; must skip this formal end @@ -2082,10 +2189,10 @@ def self.tc_block(scope, env, tblock, block, inst) # TODO self is the same *except* instance_exec or instance_eval raise RuntimeError, "block with block arg?" unless tblock.is_a?(RDL::Type::VarType) || tblock.block.nil? tblock = tblock.instantiate(inst) - if block[0].is_a? RDL::Type::MethodType - error :bad_block_arg_type, [block[0], tblock], block[1] unless RDL::Type::Type.leq(block[0], tblock, inst, false, ast: block[1])#block[0] <= tblock + if block[0].is_a?(RDL::Type::MethodType) || block[0].is_a?(RDL::Type::VarType) + error :bad_block_arg_type, [block[0], tblock], block[1], block: true unless RDL::Type::Type.leq(block[0], tblock, inst, false, ast: block[1])#block[0] <= tblock elsif block[0].is_a?(RDL::Type::NominalType) && block[0].name == 'Proc' - error :proc_block_arg_type, [tblock], block[1] + error :proc_block_arg_type, [tblock], block[1], block: true elsif tblock.is_a?(RDL::Type::VarType) args, body = block arg_names = args.children.map { |a| a.children[0] } @@ -2101,13 +2208,13 @@ def self.tc_block(scope, env, tblock, block, inst) eff else # must be [block-args, block-body] args, body = block - env, targs = args_hash(scope, env, tblock, args, block, 'block') + env, targs = args_hash(scope, env, tblock, args, block[1], 'block') scope_merge(scope, outer_env: env) { |bscope| # note: okay if outer_env shadows, since nested scope will include outer scope by next line targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this env = env.merge(Env.new(targs_dup)) - _, body_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], [:+, :+]] else tc(bscope, env.merge(Env.new(targs)), body) end - error :bad_return_type, [body_type, tblock.ret], body unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false, ast: body) + _, body_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], [:+, :+]] else tc(bscope, env.merge(Env.new(targs)), body) end + error :bad_return_type, [body_type, tblock.ret], body, block: true unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false, ast: body) # eff } @@ -2140,15 +2247,19 @@ def self.lookup(scope, klass, name, e, make_unknown: true) e = RDL::Globals.info.get_with_aliases(klass, name, :effect) return [t, e] if t # simplest case, no need to walk inheritance hierarchy return [[make_unknown_method_type(klass, name)]] if RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } + klass_name = RDL::Util.has_singleton_marker(klass) ? RDL::Util.remove_singleton_marker(klass) : klass + return [[make_unknown_method_type(klass, name)]] if (name == :initialize) && (RDL::Util.to_class(klass_name).instance_method(:initialize).owner == RDL::Util.to_class(klass_name)) # don't want to walk up hierarchy in initialize case where it is specifically defined for this class the_klass = RDL::Util.to_class(klass) is_singleton = RDL::Util.has_singleton_marker(klass) included = RDL::Util.to_class(klass.gsub("[s]", "")).included_modules - the_klass.ancestors[1..-1].each { |ancestor| + ancestors = the_klass.ancestors[1..-1] + ancestors += [Kernel, BasicObject] if (!ancestors.include?(Kernel) && !ancestors.include?(BasicObject)) # Module#ancestors does not include these, even though all modules inherit their methods. + ancestors.each { |ancestor| # assumes ancestors is proper order to walk hierarchy # included modules' instance methods get added as instance methods, so can't be in singleton class next if (ancestor.instance_of? Module) && (included.member? ancestor) && is_singleton && !(ancestor == Kernel) # extended (i.e., not included) modules' instance methods get added as singleton methods, so can't be in class - next if (ancestor.instance_of? Module) && (not (included.member? ancestor)) && (not is_singleton) + next if (ancestor.instance_of? Module) && (not (included.member? ancestor)) && (not is_singleton) && (ancestor != Kernel) && (ancestor != BasicObject) if is_singleton #&& !ancestor.instance_of?(Module) anc_lookup = get_singleton_name(ancestor.to_s) else @@ -2200,7 +2311,7 @@ def self.lookup(scope, klass, name, e, make_unknown: true) #return nil ## Trying new approach: create unknown method type for any methods without types. if make_unknown - return [[make_unknown_method_type(klass, name)]] + return [[make_unknown_method_type(klass, name)]] if (RDL::Util.to_class(klass).instance_methods + RDL::Util.to_class(klass).private_instance_methods).include?(name) else return nil end @@ -2234,13 +2345,15 @@ def self.make_unknown_method_type(klass, meth) keyword_args = keyword_args.empty? ? [] : [RDL::Type::FiniteHashType.new(keyword_args, nil)] arg_types = arg_types + keyword_args if meth == :initialize +=begin if RDL::Util.has_singleton_marker(klass) # to_class gets the class object itself, so remove singleton marker to get class rather than singleton class ret_vartype = RDL::Type::SingletonType.new(RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass))) else ret_vartype = RDL::Type::NominalType.new(klass) end - +=end + ret_vartype = RDL::Globals.parser.scan_str "#T self" #ret_vartype = RDL::Type::VarType.new(:self) ## TODO: is this right? Or should it include klass/meth info? else ret_vartype = RDL::Type::VarType.new(cls: klass, meth: meth, category: :ret, name: "ret") diff --git a/lib/rdl/types/finite_hash.rb b/lib/rdl/types/finite_hash.rb index 7a5a0de0..40653063 100644 --- a/lib/rdl/types/finite_hash.rb +++ b/lib/rdl/types/finite_hash.rb @@ -11,9 +11,10 @@ class FiniteHashType < Type attr_reader :the_hash # either nil or hash type if self has been promoted to hash attr_accessor :ubounds # upper bounds this tuple has been compared with using <= attr_accessor :lbounds # lower bounds... - + attr_accessor :default # For hashes created with Hash.new, gives the default type to return for non-existent keys + # [+ elts +] is a map from keys to types - def initialize(elts, rest) + def initialize(elts, rest, default: nil) elts.each { |k, t| raise RuntimeError, "Got #{t.inspect} for key #{k} where Type expected" unless t.is_a? Type raise RuntimeError, "Type may not be annotated or vararg" if (t.instance_of? AnnotatedArgType) || (t.instance_of? VarargType) @@ -24,6 +25,7 @@ def initialize(elts, rest) @cant_promote = false @ubounds = [] @lbounds = [] + @default = default super() end diff --git a/lib/rdl/types/intersection.rb b/lib/rdl/types/intersection.rb index 3ab2f097..f92673d3 100644 --- a/lib/rdl/types/intersection.rb +++ b/lib/rdl/types/intersection.rb @@ -66,8 +66,33 @@ def canonicalize! @canonicalized = true end + def drop_vars! + @types.map! { |t| + if t.is_a?(UnionType) then + t.drop_vars! unless t.types.all? { |t| t.is_a?(VarType) } + if t.types.empty? then nil else t end + else t + end } + @types.delete(nil) + @types.reject! { |t| t.is_a? VarType } + self + end + + def drop_vars + return self if @types.all? { |t| t.is_a? VarType } ## when all are VarTypes, we have nothing concrete to reduce to, so don't want to drop vars + new_types = [] + for i in 0..(@types.length-1) + if @types[i].is_a?(UnionType) + new_types << @types[i].drop_vars.canonical + else + new_types << @types[i] unless @types[i].is_a? VarType + end + end + RDL::Type::IntersectionType.new(*new_types).canonical + end + def to_s # :nodoc: - return "(#{@types.map { |t| t.to_s }.join(' and ')})" + return "(#{@types.map { |t| t.to_s }.sort.join(' and ')})" end def ==(other) # :nodoc: diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index 4f88f1d9..afaa333a 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -31,7 +31,7 @@ def initialize(args, block, ret) raise "Varargs not allowed after named arguments" if state == :hash state = :vararg when FiniteHashType - raise "Only one set of named arguments allowed" if state == :hash + #raise "Only one set of named arguments allowed" if state == :hash state = :hash else raise "Attempt to create method type with non-type arg" unless arg.is_a? Type @@ -48,7 +48,7 @@ def initialize(args, block, ret) end @block = block - raise "Attempt to create method type with non-type ret" unless ret.is_a? Type + raise "Attempt to create method type with non-type ret #{ret} of class #{ret.class}" unless ret.is_a? Type @ret = ret super() diff --git a/lib/rdl/types/nominal.rb b/lib/rdl/types/nominal.rb index 7a93899b..94d00f2d 100644 --- a/lib/rdl/types/nominal.rb +++ b/lib/rdl/types/nominal.rb @@ -10,6 +10,7 @@ class << self def self.new(name) name = name.to_s + name = "Integer" if RDL::Config.instance.number_mode && ["Float", "Rational", "Complex", "BigDecimal"].include?(name) t = @@cache[name] return t if t t = self.__new__ name @@ -47,6 +48,8 @@ def to_s n = @name end return RDL::Util.add_singleton_marker(n[8..-2]) + elsif RDL::Config.instance.number_mode && (@name == "Integer") + return "Number" else return @name end diff --git a/lib/rdl/types/singleton.rb b/lib/rdl/types/singleton.rb index de27fcf7..146ec403 100644 --- a/lib/rdl/types/singleton.rb +++ b/lib/rdl/types/singleton.rb @@ -11,6 +11,9 @@ class << self end def self.new(val) + if RDL::Config.instance.number_mode && val.is_a?(Numeric) && !val.is_a?(Integer) + return RDL::Type::NominalType.new(Integer) + end t = @@cache[val] return t if t t = self.__new__ val diff --git a/lib/rdl/types/string.rb b/lib/rdl/types/string.rb index 44445b6e..0e750bcf 100644 --- a/lib/rdl/types/string.rb +++ b/lib/rdl/types/string.rb @@ -68,8 +68,8 @@ def promote! check_bounds end - def check_bounds - return (@lbounds.all? { |lbound| lbound <= self }) && (@ubounds.all? { |ubound| self <= ubound } ) + def check_bounds(no_promote=false) + return (@lbounds.all? { |lbound| lbound.<=(self, no_promote) }) && (@ubounds.all? { |ubound| self.<=(ubound, no_promote) } ) end def cant_promote! diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 61c102c4..1f559a71 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -31,13 +31,17 @@ def var_type? def optional_var_type? is_a?(OptionalType) && @type.var_type? end + + def vararg_var_type? + is_a?(VarargType) && @type.var_type? + end def fht_var_type? is_a?(FiniteHashType) && @elts.keys.all? { |k| k.is_a?(Symbol) } && @elts.values.all? { |v| v.optional_var_type? || v.var_type? } end def kind_of_var_input? - var_type? || optional_var_type? || fht_var_type? + var_type? || optional_var_type? || fht_var_type? || vararg_var_type? end # default behavior, override in appropriate subclasses @@ -58,8 +62,8 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con #propagate = false left = inst[left.name] if inst && ileft && left.is_a?(VarType) && !left.to_infer && inst[left.name] right = inst[right.name] if inst && !ileft && right.is_a?(VarType) && !right.to_infer && inst[right.name] - left = left.type if left.is_a? DependentArgType - right = right.type if right.is_a? DependentArgType + left = left.type if left.is_a?(DependentArgType) || left.is_a?(AnnotatedArgType) + right = right.type if right.is_a?(DependentArgType) || right.is_a?(AnnotatedArgType) left = left.type if left.is_a? NonNullType # ignore nullness! right = right.type if right.is_a? NonNullType left = left.canonical @@ -123,11 +127,14 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # nominal return left.klass.ancestors.member?(right.klass) if left.is_a?(NominalType) && right.is_a?(NominalType) - if (left.is_a?(NominalType) || left.is_a?(TupleType) || left.is_a?(FiniteHashType) || left.is_a?(PreciseStringType)) && right.is_a?(StructuralType) + if (left.is_a?(NominalType) || left.is_a?(TupleType) || left.is_a?(FiniteHashType) || left.is_a?(TopType) || left.is_a?(PreciseStringType) || (left.is_a?(SingletonType) && !left.val.nil?)) && right.is_a?(StructuralType) + case left when TupleType lklass = Array - base_inst = { self: left } + base_inst = { self: left}#, t: left.promote.params[0] } + t_bind = left.promote.params[0].to_s == "t" ? RDL::Globals.types[:bot] : left.promote.params[0] + base_inst[:t] = t_bind when FiniteHashType lklass = Hash base_inst = { self: left } @@ -139,9 +146,25 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con when PreciseStringType lklass = String base_inst = { self: left } - else - lklass = left.klass + when TopType + lklass = Object + base_inst = { self: RDL::Globals.types[:object] } + when SingletonType + lklass = left.val.class base_inst = { self: left } + else + if (left == RDL::Globals.types[:array]) + left = RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Globals.types[:bot]) + lklass = Array + base_inst = { self: left, t: RDL::Globals.types[:bot] } + elsif (left == RDL::Globals.types[:hash]) + left = RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:bot], RDL::Globals.types[:bot]) + lklass = Hash + base_inst = { self: left, k: RDL::Globals.types[:bot], v: RDL::Globals.types[:bot] } + else + lklass = left.klass + base_inst = { self: left } + end end right.methods.each_pair { |m, t| @@ -159,11 +182,32 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con computed_tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) leq(computed_tlm.instantiate(base_inst), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) else - leq(tlm.instantiate(base_inst), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) - # inst above is nil because the method types inside the class and - # inside the structural type have an implicit quantifier on them. So - # even if we're allowed to instantiate type variables we can't do that - # inside those types + leq(tlm.instantiate(base_inst), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + # inst above is nil because the method types inside the class and + # inside the structural type have an implicit quantifier on them. So + # even if we're allowed to instantiate type variables we can't do that + # inside those types + end + } + end + } + return true + end + + if (left.is_a?(SingletonType) && left.val.class == Class) && right.is_a?(StructuralType) + lklass = left.val + right.methods.each_pair { |m, t| + return false unless lklass.methods.include?(m) || RDL::Typecheck.lookup({}, "[s]"+lklass.to_s, m, nil, make_unknown: false) + types, _ = RDL::Typecheck.lookup({}, "[s]"+lklass.to_s, m, nil, make_unknown: false) + if types + return false unless types.any? { |tlm| + blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] + + if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } + computed_tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) + leq(computed_tlm, t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + else + leq(tlm, t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) end } end @@ -191,7 +235,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con when :- leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) when :~ - #puts "About to check with #{tl}, #{tr}, and #{ileft} AND #{inst}" leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) && leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) else raise RuntimeError, "Unexpected variance #{v}" # shouldn't happen @@ -205,8 +248,18 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con base_inst = Hash[*formals.zip(left.params).flatten] # instantiation for methods in base's class klass = left.base.klass right.methods.each_pair { |meth, t| - return false unless klass.method_defined?(meth) || RDL::Typecheck.lookup({}, klass.to_s, meth, nil, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) ## Added the second condition because Rails lazily defines some methods. - types, _ = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) + if (klass.to_s == "ActiveRecord_Relation") && !klass.method_defined?(meth) && defined? DBType + types, _ = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false) + if !types + base_types, _ = RDL::Typecheck.lookup({}, "[s]"+DBType.rec_to_nominal(left).name, meth, {}, make_unknown: false) + return false unless base_types + types = base_types.map { |t| RDL::Type::MethodType.new(t.args, t.block, left) } + end + return false unless types + else + return false unless klass.method_defined?(meth) || RDL::Typecheck.lookup({}, klass.to_s, meth, nil, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) ## Added the second condition because Rails lazily defines some methods. + types, _ = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) + end if types return false unless types.any? { |tlm| @@ -239,7 +292,8 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con left = RDL::Type::MethodType.new(left.args[0..(left.args.size-2)]+new_args, left.block, left.ret) end end - if left.args.last.is_a?(OptionalType) + + if left.args.last.is_a?(OptionalType) left = RDL::Type::MethodType.new(left.args.map { |t| if t.is_a?(RDL::Type::OptionalType) then t.type else t end }, left.block, left.ret) if left.args.size == right.args.size + 1 ## A method with an optional type in the last position can be used in place @@ -249,6 +303,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end return false unless left.args.size == right.args.size return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr.instantiate(inst), tl.instantiate(inst), inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } # contravariance + #return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr.instantiate(inst), tl.instantiate(inst), inst, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } # contravariance if left.block && right.block return false unless leq(right.block.instantiate(inst), left.block.instantiate(inst), inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # contravariance @@ -259,7 +314,8 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end return true if left.is_a?(MethodType) && right.is_a?(NominalType) && right.name == 'Proc' - + return true if left.is_a?(MethodType) && right.is_a?(StructuralType) && (right.methods.size == 1) && right.methods.has_key?(:to_proc) + # structural if left.is_a?(StructuralType) && right.is_a?(StructuralType) # allow width subtyping - methods of right have to be in left, but not vice-versa @@ -295,13 +351,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con left.elts.each_pair { |k, tl| if right_elts.has_key? k tr = right_elts[k] - if k == :action && tl.is_a?(RDL::Type::VarType) - if !$blah - $blah = true - else - #raise - end - end return false if tl.is_a?(OptionalType) && !tr.is_a?(OptionalType) # optional left, required right not allowed, since left may not have key tl = tl.type if tl.is_a? OptionalType tr = tr.type if tr.is_a? OptionalType @@ -329,6 +378,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end ## precise string + if left.is_a?(PreciseStringType) if right.is_a?(PreciseStringType) return false if left.vals.size != right.vals.size diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index 3e4e2dcf..8811876a 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -42,8 +42,8 @@ def canonical def canonicalize! return if @canonicalized - # for any type such that a supertype is already in ts, set its position to nil @types.map! { |t| t.canonical } + # for any type such that a supertype is already in ts, set its position to nil for i in 0..(@types.length-1) for j in (i+1)..(@types.length-1) next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType) && @types[i].to_infer) || (@types[j].is_a?(VarType) && @types[j].to_infer) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union @@ -61,13 +61,33 @@ def canonicalize! end def drop_vars! + @types.map! { |t| + if t.is_a?(IntersectionType) then + t.drop_vars! unless t.types.all? { |t| t.is_a?(VarType) } + if t.types.empty? then nil else t end + else t + end } + @types.delete(nil) @types.reject! { |t| t.is_a? VarType } self end + def drop_vars + return self if @types.all? { |t| t.is_a? VarType } ## when all are VarTypes, we have nothing concrete to reduce to, so don't want to drop vars + new_types = [] + for i in 0..(@types.length-1) + if @types[i].is_a?(IntersectionType) + new_types << @types[i].drop_vars.canonical + else + new_types << @types[i] unless @types[i].is_a? VarType + end + end + RDL::Type::UnionType.new(*new_types).canonical + end + def to_s # :nodoc: return @canonical.to_s if @canonical - return "(#{@types.map { |t| t.to_s }.join(' or ')})" + return "(#{@types.map { |t| t.to_s }.sort.join(' or ')})" end def ==(other) # :nodoc: diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index 830ff7c8..d958da2d 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -53,16 +53,16 @@ def initialize(name_or_hash) # [+ ast +] is the AST where the bound originates from, used for error messages. # [+ new_cons +] is a Hash. When provided, can be used to roll back constraints in case an error pops up. def add_and_propagate_upper_bound(typ, ast, new_cons = {}) + #puts "1a. Adding upper bound #{self} <= #{typ}" return if self.equal?(typ) - #puts "About to add upper bound #{self} <= #{typ}" if !@ubounds.any? { |t, a| t == typ } @ubounds << [typ, ast] new_cons[self] = new_cons[self] ? new_cons[self] | [[:upper, typ, ast]] : [[:upper, typ, ast]] end - #typ.ubounds.each { |t| add_and_propagate_upper_bound(typ) } @lbounds.each { |lower_t, a| + #puts "2a. Adding bound #{lower_t} <= #{typ}" if lower_t.is_a?(VarType) - lower_t.add_and_propagate_upper_bound(typ, ast, new_cons) + lower_t.add_and_propagate_upper_bound(typ, ast, new_cons) unless lower_t.ubounds.any? { |t, _| t == typ } else if typ.is_a?(VarType) new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:lower, lower_t, ast]] : [[:lower, lower_t, ast]] @@ -79,17 +79,19 @@ def add_and_propagate_upper_bound(typ, ast, new_cons = {}) ## Similar to above. def add_and_propagate_lower_bound(typ, ast, new_cons = {}) + #puts "1b. Adding lower bound #{typ} <= #{self}" raise if typ.to_s == "v" return if self.equal?(typ) - #puts "About to add lower bound #{typ} <= #{self}" if !@lbounds.any? { |t, a| t == typ } @lbounds << [typ, ast] new_cons[self] = new_cons[self] ? new_cons[self] | [[:lower, typ, ast]] : [[:lower, typ, ast]] end - #typ.lbounds.each { |t| add_and_propagate_lower_bound(typ) } if typ.is_a?(VarType) + #puts "The upper bounds are: "# + #@ubounds.each { |u, _| puts u } @ubounds.each { |upper_t, a| + #puts "2b. Adding bound #{typ} <= #{upper_t}." if upper_t.is_a?(VarType) - upper_t.add_and_propagate_lower_bound(typ, ast, new_cons) + upper_t.add_and_propagate_lower_bound(typ, ast, new_cons) unless upper_t.lbounds.any? { |t, _| t == typ } else if typ.is_a?(VarType) new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:upper, upper_t, ast]] : [[:upper, upper_t, ast]] @@ -118,7 +120,7 @@ def add_lbound(typ, ast, new_cons = {}, propagate: false) add_and_propagate_lower_bound(typ, ast, new_cons) else #puts "2. About to add lower bound #{typ} <= #{self}" - raise "blah" if typ.to_s == "Array" + #raise "blah" if typ.to_s == "Array" new_cons[self] = new_cons[self] ? new_cons[self] | [[:lower, typ, ast]] : [[:lower, typ, ast]] @lbounds << [typ, ast] unless @lbounds.any? { |t, a| t == typ } end diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index e49fa1ef..4aa61391 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -406,6 +406,21 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: nil end + def orig_type(klass=self, meth, type, wrap: nil, typecheck: nil) + $orig_types = true + klass, meth, type = RDL::Wrap.process_type_args(self, klass, meth, type) + RDL::Globals.info.add(klass, meth, :orig_type, type) + end + + def orig_var_type(klass=self, var, type) + raise RuntimeError, "Variable cannot begin with capital" if var.to_s =~ /^[A-Z]/ + return if var.to_s =~ /^[a-z]/ # local variables handled specially, inside type checker + klass = RDL::Util::GLOBAL_NAME if var.to_s =~ /^\$/ + unless RDL::Globals.info.set(klass, var, :orig_type, RDL::Globals.parser.scan_str("#T #{type}")) + raise RuntimeError, "Type already declared for #{var}" + end + end + def readd_comp_types RDL::Globals.dep_types.each { |klass, meth, t| RDL::Globals.info.add(klass, meth, :type, t) unless meth.nil? } end @@ -454,7 +469,43 @@ def infer(*args, time: RDL::Config.instance.infer_defaults[:time]) nil end + # [+ path +] is a String pathname, for which all direct ".rb" subfiles will be inferred. + def infer_all(path) + path = Pathname.new(path).expand_path + rb_files = Dir.entries(path).keep_if { |f| f.end_with?(".rb") } + rb_files.each { |f| infer_file(path+f, more: true) } + #RDL.do_infer :all + end + # [+ file +] is the String absolute path for a file, for which we want to infer all contained methods. + # [+ more +] is a %bool indicating whether there are other methods outside given file to infer. + # When false, we infer after processing all methods in file. When true, we do not infer in this method. + def infer_file(file, more: false) + file = Pathname.new(file).expand_path.to_s + return if RDL::Globals.no_infer_files.include? file + index = ClassIndexer.process_file(file) + index.each { |klass, meth_list| + if RDL::Util.has_singleton_marker(klass) + klass = RDL::Util.remove_singleton_marker(klass) + meth_list.each { |meth| + infer(klass, "self." + meth[:name], time: :files) unless RDL::Globals.no_infer_meths.include?([klass.to_s, "self."+meth[:name]]) + } + else + meth_list.each { |meth| + infer klass, meth[:name], time: :files unless RDL::Globals.no_infer_meths.include?([klass.to_s, meth[:name].to_s]) + } + end + } + #RDL.do_infer :all + end + + def no_infer_meth(klass, meth) + RDL::Globals.no_infer_meths << [klass.to_s, meth.to_s] + end + + def no_infer_file(path) + RDL::Globals.no_infer_files << Pathname.new(path).expand_path.to_s + end # [+ klass +] is the class containing the variable; self if omitted; ignored for local and global variables # [+ var +] is a symbol or string containing the name of the variable @@ -686,43 +737,25 @@ def self.do_typecheck(sym) def self.do_infer(sym) return unless RDL::Globals.to_infer[sym] + $stn = 0 + num_casts = 0 + time = Time.now RDL::Globals.to_infer[sym].each { |klass, meth| RDL::Typecheck.infer klass, meth + num_casts += RDL::Typecheck.get_num_casts } RDL::Globals.to_infer[sym] = Set.new nil RDL::Typecheck.resolve_constraints RDL::Typecheck.extract_solutions - #RDL::Globals.constrained_types = [] -=begin - puts "Here are the generated constraints: " - RDL::Globals.constrained_types.each { |klass, name| - typ = RDL::Globals.info.get(klass, name, :type) - if typ.is_a?(Array) - ## it's an array for method types (to handle intersections) - meth_type = typ[0] - raise "Expected MethodType, got #{meth_type}." unless meth_type.is_a?(RDL::Type::MethodType) - (meth_type.args + [meth_type.ret]).each { |var_type| - raise "Expected VarType, got #{var_type}." unless var_type.is_a?(RDL::Type::VarType) - puts "***** Lower bounds for #{var_type} *****" - var_type.lbounds.each { |t| puts t } - puts "***** Upper bounds for #{var_type} *****" - var_type.ubounds.each { |t| puts t } - } - else - ## variable type in this case - var_type = typ - raise "Expected VarType, got #{var_type}." unless var_type.is_a?(RDL::Type::VarType) - puts "***** Lower bounds for #{var_type} *****" - var_type.lbounds.each { |t| puts t } - puts "***** Upper bounds for #{var_type} *****" - var_type.ubounds.each { |t| puts t } - end - } -=end + time = Time.now - time + puts "Total time taken: #{time}." + puts "Total number of type casts used: #{num_casts}." + puts "Total amount of time spent on stn: #{$stn}." end def self.load_sequel_schema(db) + db.disconnect db.tables.each { |table| hash_str = "{ " kl_name = table.to_s.camelize.singularize diff --git a/lib/types/core/_aliases.rb b/lib/types/core/_aliases.rb index 4f2e44e3..5e2fa176 100644 --- a/lib/types/core/_aliases.rb +++ b/lib/types/core/_aliases.rb @@ -11,4 +11,4 @@ else RDL.type_alias '%path', '%string' end -RDL.type_alias '%open_args', '{external_encoding: ?String, internal_encoding: ?String, encoding: ?String, textmode: ?%any, binmode: ?%any, autoclose: ?%any, mode: ?String}' +RDL.type_alias '%open_args', '{external_encoding: ?(String or Encoding), internal_encoding: ?(String or Encoding), encoding: ?(String or Encoding), textmode: ?%any, binmode: ?%any, autoclose: ?%any, mode: ?String}' diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 2a090ee5..fc431043 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -112,12 +112,12 @@ def Array.append_push_output(trec, targs, meth) end RDL.type Array, 'self.append_push_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] -RDL.type :Array, :[], '(Range) -> ``output_type(trec, targs, :[], :promoted_array, "Array")``' +#RDL.type :Array, :[], '(Range) -> ``output_type(trec, targs, :[], :promoted_array, "Array")``' RDL.type :Array, :[], '(Integer or Float) -> ``output_type(trec, targs, :[], :promoted_param, "t")``' RDL.type :Array, :[], '(Integer, Integer) -> ``output_type(trec, targs, :[], :promoted_array, "Array")``' RDL.type :Array, :&, '(Array) -> ``output_type(trec, targs, :&, :promoted_array, "Array")``' RDL.type :Array, :*, '(Integer) -> ``output_type(trec, targs, :*, :promoted_array, "Array")``' -RDL.type :Array, :*, '(String) -> String' +RDL.type :Array, :*, '(String) -> ``output_type(trec, targs, :*, "String")``' RDL.type :Array, :+, '(``plus_input(targs)``) -> ``plus_output(trec, targs)``' @@ -125,8 +125,9 @@ def Array.plus_input(targs) case targs[0] when RDL::Type::TupleType return targs[0] - when RDL::Type::GenericType - return RDL::Globals.parser.scan_str "#T Array" + when RDL::Type::GenericType, RDL::Type::VarType + parse_string = defined? Rails ? "Array or ActiveRecord::Relation" : "Array" + return RDL::Globals.parser.scan_str "#T #{parse_string}" else RDL::Globals.types[:array] end @@ -157,7 +158,7 @@ def Array.plus_output(trec, targs) when RDL::Type::GenericType promoted = trec.promote param_union = RDL::Type::UnionType.new(promoted.params[0], RDL.type_cast(targs[0], "RDL::Type::GenericType", force: true).params[0] ) - return RDL::Type::GenericType.new(RDL.type_cast(targs[0], "RDL::Type::GenericType", force: true).base, param_union) + return RDL::Type::GenericType.new(RDL::Globals.types[:array], param_union) else ## targs[0] should just be Array here return RDL::Globals.types[:array] @@ -227,7 +228,6 @@ def Array.multi_assign_output(trec, targs) RDL.type :Array, :map, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :map!, '() {(``promoted_or_t(trec)``) -> u} -> ``map_output(trec)``' - def Array.map_output(trec) case trec when RDL::Type::TupleType @@ -264,6 +264,48 @@ def Array.map_output(trec) RDL.type :Array, :drop_while, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :each, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :each, '() { (``promoted_or_t(trec)``) -> %any } -> self' +RDL.type :Array, :each, '() { (``each_arg(trec, 0)``, ``each_arg(trec, 1)``) -> %any } -> self' ## hack: if receiver is an array of arrays, then each block can take multiple args. not sure how to generalize this to arbitrary args, so for now I'm just getting it to work with two. +RDL.type :Array, :each, '() { (``each_arg(trec, 0)``, ``each_arg(trec, 1)``, ``each_arg(trec, 2)``, ``each_arg(trec, 3)``, ``each_arg(trec, 4)``, ``each_arg(trec, 5)``) -> %any } -> self' ## hack: if receiver is an array of arrays, then each block can take multiple args. not sure how to generalize this to arbitrary args, so for now I'm just getting it to work with two. + +def Array.each_arg(trec, num) + case trec + when RDL::Type::TupleType + if trec.params.all? { |t| t.is_a?(RDL::Type::TupleType) } + first_params = RDL::Type::UnionType.new(*trec.params.map { |t| t.params[num] }) + first_params = first_params.canonical if first_params.is_a?(RDL::Type::UnionType) + return first_params + else + return promoted_or_t(trec) + end + else + if trec.params[0].is_a?(RDL::Type::TupleType) + return trec.params[0].params[num] + else + return promoted_or_t(trec) + end + end +end + +def Array.each_arg2(trec) + case trec + when RDL::Type::TupleType + if trec.params.all? { |t| t.is_a?(RDL::Type::TupleType) } + second_params = RDL::Type::UnionType.new(*trec.params.map { |t| t.params[1] }) + second_params = second_params.canonical if second_params.is_a?(RDL::Type::UnionType) + return second_params + else + return RDL::Type::OptionalType.new(promoted_or_t(trec)) + end + else + if trec.params[0].is_a?(RDL::Type::TupleType) + return trec.params[0].params[1] + else + return RDL::Type::OptionalType.new(promoted_or_t(trec)) + end + end +end + + RDL.type :Array, :each_index, '() { (Integer) -> %any } -> self' RDL.type :Array, :each_index, '() -> Enumerator' RDL.type :Array, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``', effect: [:+, :+] @@ -332,7 +374,7 @@ def Array.include_output(trec, targs) RDL.type :Array, :keep_if, '() { (``promoted_or_t(trec)``) -> %bool } -> ``promote_tuple!(trec)``' RDL.type :Array, :last, '() -> ``output_type(trec, targs, :last, :promoted_param, "t")``' RDL.type :Array, :last, '(Integer) -> ``output_type(trec, targs, :last, :promoted_array, "Array")``' -RDL.type :Array, :member?, '(u) -> ``output_type(trec, targs, :member?, "%bool", use_sing_val: false, nil_false_default: true)``' +RDL.type :Array, :member?, '(%any) -> ``output_type(trec, targs, :member?, "%bool", use_sing_val: false, nil_false_default: true)``' RDL.type :Array, :length, '() -> ``output_type(trec, targs, :length, "Integer")``' RDL.type :Array, :permutation, '(?Integer) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :permuation, '(?Integer) { (``promote_tuple(trec)``) -> %any } -> ``promote_tuple(trec)``' @@ -410,7 +452,9 @@ def Array.reverse_output(trec) RDL.rdl_alias :Array, :to_s, :inspect RDL.type :Array, :transpose, '() -> ``promote_tuple(trec)``' RDL.type :Array, :uniq, '() -> ``promote_tuple(trec)``' +RDL.type :Array, :uniq, '() { (self) -> %any } -> ``promote_tuple(trec)``' RDL.type :Array, :uniq!, '() -> ``promote_tuple!(trec)``' +RDL.type :Array, :uniq!, '() { (self) -> %any } -> ``promote_tuple!(trec)``' RDL.type :Array, :unshift, '(``any_or_t(trec, true)``) -> ``promote_tuple!(trec)``' RDL.type :Array, :values_at, '(*Integer) -> ``output_type(trec, targs, :values_at, :promoted_array, "Array")``' RDL.type :Array, :values_at, '(Range) -> ``promote_tuple(trec)``' diff --git a/lib/types/core/class.rb b/lib/types/core/class.rb index 05819731..b3eab955 100644 --- a/lib/types/core/class.rb +++ b/lib/types/core/class.rb @@ -1,6 +1,6 @@ RDL.nowrap :Class -RDL.type :Class, :allocate, '() -> %any' # Instance of class self +RDL.type :Class, :allocate, '() -> ``RDL::Type::NominalType.new(trec.val)``' # Instance of class self RDL.type :Class, :inherited, '(Class) -> %any' #RDL.type :Class, 'initialize', '() -> ' #RDL.type :Class, 'new', '(*%any) -> %any' #Causes two other test cases to fail diff --git a/lib/types/core/enumerable.rb b/lib/types/core/enumerable.rb index 192192ea..cf5fba7c 100644 --- a/lib/types/core/enumerable.rb +++ b/lib/types/core/enumerable.rb @@ -7,7 +7,7 @@ RDL.type :Enumerable, :any?, '() -> %bool' RDL.type :Enumerable, :any?, '() { (t) -> %bool } -> %bool' # RDL.type :Enumerable, :chunk, '(XXXX : *XXXX)' # TODO -RDL.type :Enumerable, :collect, '() { (t) -> u } -> Array' +RDL.type :Enumerable, :collect, '() { (?t) -> u } -> Array' RDL.type :Enumerable, :collect, '() -> Enumerator' # RDL.type :Enumerable, :collect_concat # TODO RDL.type :Enumerable, :count, '() -> Integer' @@ -16,6 +16,7 @@ RDL.type :Enumerable, :cycle, '(?Integer n) { (t) -> %any } -> nil' RDL.type :Enumerable, :cycle, '(?Integer n) -> Enumerator' RDL.type :Enumerable, :detect, '(?Proc ifnone) { (t) -> %bool } -> t or nil' # TODO ifnone +RDL.type :Enumerable, :detect, '() { (t) -> %bool } -> t or nil' # TODO ifnone RDL.type :Enumerable, :detect, '(?Proc ifnone) -> Enumerator' RDL.type :Enumerable, :drop, '(Integer n) -> Array' RDL.type :Enumerable, :drop_while, '() { (t) -> %bool } -> Array' diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index d8b65ed8..366ab757 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -14,6 +14,12 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_de return trec.promote.params[1] elsif default1 == :promoted_key return trec.promote.params[0] + elsif default1 == :default_or_promoted_val + if trec.default then + return assign_output(trec, targs + [trec.default]) + else + return trec.promote.params[1] + end else return RDL::Globals.parser.scan_str "#T #{default1}" end @@ -21,10 +27,15 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_de to_type(res) else if default1 == :promoted_val - x = trec.promote.params[1] - return x + return trec.promote.params[1] elsif default1 == :promoted_key return trec.promote.params[0] + elsif default1 == :default_or_promoted_val + if trec.default then + return assign_output(trec, targs + [trec.default]) + else + return trec.promote.params[1] + end else RDL::Globals.parser.scan_str "#T #{default1}" end @@ -33,7 +44,11 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_de if default2 == "k" trec.params[0] ## equivalent of k in Hash elsif default2 == "v" - trec.params[1] ## equivalent of v in Hash + if trec.to_s == 'ActionController::Parameters' + return RDL::Globals.parser.scan_str "#T (Symbol or String)" + else + trec.params[1] ## equivalent of v in Hash + end else RDL::Globals.parser.scan_str "#T #{default2}" end @@ -62,6 +77,12 @@ def Hash.any_or_k(trec) when RDL::Type::GenericType #RDL::Globals.parser.scan_str "#T k" trec.params[0] ## equivalent of k in Hash + when RDL::Type::NominalType + if trec.to_s == 'ActionController::Parameters' + return RDL::Globals.parser.scan_str "#T (Symbol or String)" + else + return RDL::Globals.parser.scan_str "#T k" + end else raise "unexpected, got #{trec}" end @@ -94,6 +115,18 @@ def Hash.promoted_or_v(trec) end RDL.type Hash, 'self.promoted_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +def Hash.promoted_or_k(trec) + case trec + when RDL::Type::FiniteHashType + trec.promote.params[0] + when RDL::Type::GenericType + #RDL::Globals.parser.scan_str "#T v" + trec.params[0] + else + raise "unexpected" + end +end + def Hash.weak_promote(val) case val @@ -119,13 +152,18 @@ def Hash.weak_promote(val) def Hash.hash_create_output_from_list(targs) raise RDL::Typecheck::StaticTypeError, "Hash[...] expect only 1 argument. Have #{targs}." if targs.size > 1 - raise RDL::Typecheck::StaticTypeError, "The argument has to be an array or tuple" unless (targs[0].is_a?(RDL::Type::GenericType) && targs[0].base.klass == Array) + raise RDL::Typecheck::StaticTypeError, "The argument has to be an array or tuple, got #{targs[0]}" unless ((targs[0].is_a?(RDL::Type::GenericType) && targs[0].base.klass == Array) || targs[0].is_a?(RDL::Type::VarType)) - case targs[0].params[0] - when RDL::Type::GenericType - return RDL::Globals.parser.scan_str "#T Hash<#{targs[0].params[0].params[0]}, #{targs[0].params[0].params[0]}>" - when RDL::Type::TupleType - return RDL::Type::GenericType.new(RDL::Type::NominalType.new(Hash), targs[0].params[0].params[0], targs[0].params[0].params[1]) + case targs[0] + when RDL::Type::VarType + return RDL::Globals.types[:hash] + else + case targs[0].params[0] + when RDL::Type::GenericType + return RDL::Globals.parser.scan_str "#T Hash<#{targs[0].params[0].params[0]}, #{targs[0].params[0].params[0]}>" + when RDL::Type::TupleType + return RDL::Type::GenericType.new(RDL::Type::NominalType.new(Hash), targs[0].params[0].params[0], targs[0].params[0].params[1]) + end end end @@ -140,7 +178,7 @@ def Hash.hash_create_output(targs) end RDL.type Hash, 'self.hash_create_output', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] -RDL.type :Hash, :[], '(``any_or_k(trec)``) -> ``output_type(trec, targs, :[], :promoted_val, "v", nil_default: true)``', effect: [:+, :+] +RDL.type :Hash, :[], '(``any_or_k(trec)``) -> ``output_type(trec, targs, :[], :default_or_promoted_val, "v", nil_default: true)``', effect: [:+, :+] RDL.type :Hash, :[]=, '(``any_or_k(trec)``, ``any_or_v(trec)``) -> ``assign_output(trec, targs)``' @@ -166,6 +204,9 @@ def Hash.assign_output(trec, targs) end RDL.type Hash, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, :initialize, "(*%any) -> ``RDL::Type::FiniteHashType.new({}, nil, default: targs[0])``" +RDL.type Hash, :initialize, "() { (Hash, x) -> y } -> ``RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:top], RDL::Globals.types[:top])``" + RDL.type :Hash, :store, '(``any_or_k(trec)``, ``any_or_v(trec)``) -> ``assign_output(trec, targs)``' RDL.type :Hash, :assoc, '(``any_or_k(trec)``) -> ``RDL::Type::TupleType.new(targs[0], output_type(trec, targs, :[], :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :clear, '() -> self' @@ -197,6 +238,7 @@ def Hash.delete_output(trec, targs, block) end end else + return RDL::Globals.types[:nil] if trec.to_s == "ActionController::Parameters" t = (if block then "u or v" else "v" end) RDL::Globals.parser.scan_str "#T #{t}" end @@ -205,13 +247,13 @@ def Hash.delete_output(trec, targs, block) RDL.type :Hash, :delete_if, '() { (``any_or_k(trec)``, ``any_or_v(trec)``) -> %any } -> self' RDL.type :Hash, :delete_if, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. -RDL.type :Hash, :each, '() { (``any_or_k(trec)``, ``any_or_v(trec)``) -> %any } -> self' +RDL.type :Hash, :each, '() { (``promoted_or_k(trec)``, ``promoted_or_v(trec)``) -> %any } -> self' RDL.type :Hash, :each, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. -RDL.type :Hash, :each_pair, '() { (``any_or_k(trec)``, ``any_or_v(trec)``) -> %any } -> self' +RDL.type :Hash, :each_pair, '() { (``promoted_or_k(trec)``, ``promoted_or_v(trec)``) -> %any } -> self' RDL.type :Hash, :each_pair, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. -RDL.type :Hash, :each_key, '() { (``any_or_k(trec)``) -> %any } -> self' +RDL.type :Hash, :each_key, '() { (``promoted_or_k(trec)``) -> %any } -> self' RDL.type :Hash, :each_key, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), any_or_k(trec))``' -RDL.type :Hash, :each_value, '() { (``any_or_v(trec)``) -> %any } -> self' +RDL.type :Hash, :each_value, '() { (``promoted_or_v(trec)``) -> %any } -> self' RDL.type :Hash, :each_value, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), any_or_v(trec))``' RDL.type :Hash, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) -> ``output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true)``' diff --git a/lib/types/core/integer.rb b/lib/types/core/integer.rb index 73924680..cf1e537a 100644 --- a/lib/types/core/integer.rb +++ b/lib/types/core/integer.rb @@ -199,7 +199,8 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.pre(:Integer, :round) { |x| x!=0 && if x.is_a?(Complex) then x.imaginary==0 && (if x.real.is_a?(Float)||x.real.is_a?(BigDecimal) then !x.real.infinite? && !x.real.nan? else true end) elsif x.is_a?(Float) then x!=Float::INFINITY && !x.nan? elsif x.is_a?(BigDecimal) then x!=BigDecimal::INFINITY && !x.nan? else true end} #Also, x must be in range [-2**31, 2**31]. RDL.type :Integer, :size, '() -> ``sing_or_type(trec, targs, :size, "Integer")``' RDL.type :Integer, :succ, '() -> ``sing_or_type(trec, targs, :succ, "Integer")``' -RDL.type :Integer, :times, '() { (?Integer) -> %any } -> Integer' +RDL.type :Integer, :times, '() { (Integer) -> %any } -> Integer' +RDL.type :Integer, :times, '() { () -> %any } -> Integer' RDL.type :Integer, :times, '() -> Enumerator' RDL.type :Integer, :to_c, '() -> Complex r {{ r.imaginary==0 }}' RDL.type :Integer, :to_f, '() -> ``sing_or_type(trec, targs, :to_f, "Float")``' @@ -396,7 +397,8 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.pre(:Integer, :round) { |x| x!=0 && if x.is_a?(Complex) then x.imaginary==0 && (if x.real.is_a?(Float)||x.real.is_a?(BigDecimal) then !x.real.infinite? && !x.real.nan? else true end) elsif x.is_a?(Float) then x!=Float::INFINITY && !x.nan? elsif x.is_a?(BigDecimal) then x!=BigDecimal::INFINITY && !x.nan? else true end} #Also, x must be in range [-2**31, 2**31]. RDL.type :Integer, :size, '() -> Integer' RDL.type :Integer, :succ, '() -> Integer' -RDL.type :Integer, :times, '() { (?Integer) -> %any } -> Integer' +RDL.type :Integer, :times, '() { (Integer) -> %any } -> Integer' +RDL.type :Integer, :times, '() { () -> %any } -> Integer' RDL.type :Integer, :times, '() -> Enumerator' RDL.type :Integer, :to_c, '() -> Complex r {{ r.imaginary==0 }}' RDL.type :Integer, :to_f, '() -> Float' diff --git a/lib/types/core/io.rb b/lib/types/core/io.rb index d04fab9d..1f66d8dd 100644 --- a/lib/types/core/io.rb +++ b/lib/types/core/io.rb @@ -71,6 +71,7 @@ RDL.type :IO, :putc, '(Numeric or String) -> %any' RDL.type :IO, :puts, '(*[to_s: () -> String]) -> nil' RDL.type :IO, :read, '(?Integer length, ?String outbuf) -> String or nil' +RDL.type :IO, :read, "(Integer) -> String or nil" RDL.type :IO, :read_nonblock, '(Integer maxlen) -> String' RDL.type :IO, :read_nonblock, '(Integer maxlen, String outbuf) -> String outbuf' RDL.type :IO, :readbyte, '() -> Integer' @@ -83,6 +84,7 @@ RDL.type :IO, :reopen, '(String path, String mode_str) -> IO' RDL.type :IO, :rewind, '() -> 0' RDL.type :IO, :seek, '(Integer amount, ?Integer whence) -> 0' +RDL.type :IO, :seek, '(Integer amount, Integer whence) -> 0' RDL.type :IO, :set_encoding, '(?String or Encoding ext_or_ext_int_enc) -> self' RDL.type :IO, :set_encoding, '(?String or Encoding ext_enc, ?String or Encoding int_enc) -> self' RDL.type :IO, :stat, '() -> File::Stat' diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index c271e501..27eb7b67 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -10,7 +10,7 @@ # RDL.type :Kernel, 'self.Hash', '(x : [to_hash : () -> Hash]) -> Hash' RDL.type :Kernel, 'self.Hash', '(nil x) -> Hash' # RDL.type :Kernel, 'self.Hash, '(x : []) -> Hash' -RDL.type :Kernel, 'self.Integer', '(Numeric or String arg, ?Integer base) -> Integer' +RDL.type :Kernel, 'self.Integer', '(Numeric or String, ?Integer) -> Integer' # RDL.type :Kernel, 'self.Integer', '(arg : [to_int : () -> Integer], base : ?Integer) -> Integer' # RDL.type :Kernel, 'self.Integer', '(arg : [to_i : () -> Integer], base : ?Integer) -> Integer' RDL.type :Kernel, 'self.Rational', '(Numeric x, Numeric y) -> Rational' @@ -45,6 +45,8 @@ RDL.type :Kernel, 'self.format', '(String format, *%any args) -> String' RDL.type :Kernel, 'self.gets', '(?String, ?Integer) -> String' RDL.type :Kernel, 'self.global_variables', '() -> Array' +RDL.type :Kernel, 'self.instance_variable_get', '(Symbol or String) -> Object', wrap: false +RDL.type :Kernel, 'self.instance_variable_set', '(Symbol or String, %any) -> Object', wrap: false # returns 2nd argument RDL.type :Kernel, 'self.iterator?', '() -> %bool' # RDL.type :Kernel, 'self.lambda' # TODO RDL.type :Kernel, 'self.load', '(String filename, ?%bool) -> %bool' diff --git a/lib/types/core/numeric.rb b/lib/types/core/numeric.rb index 4d6f871e..d0df46f8 100644 --- a/lib/types/core/numeric.rb +++ b/lib/types/core/numeric.rb @@ -44,9 +44,9 @@ RDL.type :Numeric, :remainder, '(%numeric) -> %real' RDL.type :Numeric, :round, '(%numeric) -> %numeric' RDL.type :Numeric, :singleton_method_added, '(Symbol) -> TypeError' -RDL.type :Numeric, :step, '(%numeric) { (%numeric) -> %any } -> %numeric' +RDL.type :Numeric, :step, '(%numeric) { (?%numeric) -> %any } -> %numeric' RDL.type :Numeric, :step, '(%numeric) -> Enumerator<%numeric>' -RDL.type :Numeric, :step, '(%numeric, %numeric) { (%numeric) -> %any } -> %numeric' +RDL.type :Numeric, :step, '(%numeric, %numeric) { (?%numeric) -> %any } -> %numeric' RDL.type :Numeric, :step, '(%numeric, %numeric) -> Enumerator<%numeric>' RDL.type :Numeric, :to_c, '() -> Complex' RDL.type :Numeric, :to_int, '() -> Integer' diff --git a/lib/types/core/object.rb b/lib/types/core/object.rb index c40c6429..0085374b 100644 --- a/lib/types/core/object.rb +++ b/lib/types/core/object.rb @@ -25,6 +25,7 @@ RDL.type :Object, :!~, '(%any other) -> %bool', wrap: false RDL.type :Object, :<=>, '(%any other) -> Integer or nil', wrap: false RDL.type :Object, :===, '(%any other) -> %bool', wrap: false, effect: [:+, :+] +RDL.type :Object, :==, '(%any other) -> %bool', wrap: false, effect: [:+, :+] RDL.type :Object, :=~, '(%any other) -> nil', wrap: false RDL.type :Object, :class, '() -> Class', wrap: false RDL.type :Object, :clone, '() -> self', wrap: false, effect: [:+, :+] @@ -56,7 +57,7 @@ RDL.type :Object, :public_send, '(Symbol or String, *%any args) -> %any', wrap: false RDL.type :Object, :remove_instance_variable, '(Symbol) -> %any', wrap: false # RDL.type :Object, :respond_to?, '(Symbol or String, ?%bool include_all) -> %bool' -RDL.type :Object, :send, '(Symbol or String, *%any) -> %any', wrap: false, effect: [:-, :+] # Can't wrap this, used outside wrap switch +RDL.type :Object, :send, '(Symbol or String, *%any) -> Object', wrap: false, effect: [:-, :+] # Can't wrap this, used outside wrap switch RDL.type :Object, :singleton_class, '() -> Class', wrap: false RDL.type :Object, :singleton_method, '(Symbol) -> Method', wrap: false RDL.type :Object, :singleton_methods, '(?%bool all) -> Array', wrap: false diff --git a/lib/types/core/string.rb b/lib/types/core/string.rb index 6b8b69a3..d9c7226f 100644 --- a/lib/types/core/string.rb +++ b/lib/types/core/string.rb @@ -13,7 +13,7 @@ def String.output_type(trec, targs, meth, type) when RDL::Type::PreciseStringType res = trec.vals[0].send(meth, targs.vals[0]) else - RDL::Globals.parser.scan_str "#T #{type}" + return RDL::Globals.parser.scan_str "#T #{type}" end elsif targs.size > 1 && targs.all? { |a| a.is_a?(RDL::Type::SingletonType) } vals = targs.map { |t| t.val } @@ -73,7 +73,7 @@ def String.string_promote!(trec) RDL.type :String, :initialize, '(?String str) -> self new_str' RDL.type :String, 'self.try_convert', '(Object obj) -> String or nil new_string' -RDL.type :String, :%, '(Object) -> ``output_type(trec, targs, :%, "String")``' +RDL.type :String, :%, '(x) -> ``output_type(trec, targs, :%, "String")``' RDL.type :String, :*, '(Numeric) -> ``output_type(trec, targs, :*, "String")``' def String.plus_output(trec, targs) @@ -98,7 +98,7 @@ def String.append_output(trec, targs) trec.vals << v end } - raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds + raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds(true) trec elsif trec.is_a?(RDL::Type::PreciseStringType) trec.promote! @@ -110,7 +110,7 @@ def String.append_output(trec, targs) RDL.type String, 'self.append_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] -RDL.type :String, :<=>, '(String other) -> ``output_type(trec, targs, :<=>, "Integer")``' +RDL.type :String, :<=>, '(String) -> ``output_type(trec, targs, :<=>, "Integer")``' RDL.type :String, :==, '(%any) -> ``output_type(trec, targs, :==, "%bool")``', effect: [:+, :+] RDL.type :String, :===, '(%any) -> ``output_type(trec, targs, :===, "%bool")``' RDL.type :String, :=~, '(Object) -> ``output_type(trec, targs, :=~, "Integer")``', wrap: false # Wrapping this messes up $1 etc @@ -121,7 +121,7 @@ def String.append_output(trec, targs) RDL.type :String, :[], '(String) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] RDL.type :String, :ascii_only?, '() -> ``output_type(trec, targs, :ascii_only?, "%bool")``' RDL.type :String, :b, '() -> ``output_type(trec, targs, :b, "String")``' -RDL.type :String, :bytes, '() -> ``output_type(trec, targs, :bytes, "Array")``' +RDL.type :String, :bytes, '() -> ``output_type(trec, targs, :bytes, "Array")``' RDL.type :String, :bytesize, '() -> ``output_type(trec, targs, :bytesize, "Integer")``' RDL.type :String, :byteslice, '(Integer, ?Integer) -> ``output_type(trec, targs, :byteslice, "String")``' RDL.type :String, :byteslice, '(Range) -> ``output_type(trec, targs, :byteslice, "String")``' @@ -131,7 +131,7 @@ def String.cap_down_output(trec, meth) case trec when RDL::Type::PreciseStringType trec.vals.each { |v| v.send(meth) if v.is_a?(String) } - raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds + raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds(true) trec else RDL::Globals.types[:string] @@ -153,7 +153,7 @@ def String.chop_output(trec) when RDL::Type::PreciseStringType if trec.vals.last.is_a?(String) trec.vals.last.chop! - raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds + raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds(true) trec else trec.promote! @@ -173,7 +173,7 @@ def String.clear_output(trec) case trec when RDL::Type::PreciseStringType trec.vals = [""] - raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds + raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds(true) trec else RDL::Type::PreciseStringType.new("") @@ -197,7 +197,7 @@ def String.clear_output(trec) RDL.type :String, :each_char, '() -> Enumerator' RDL.type :String, :each_codepoint, '() {(Integer) -> %any} -> String' RDL.type :String, :each_codepoint, '() -> Enumerator' -RDL.type :String, :each_line, '(?String) {(Integer) -> %any} -> String' +RDL.type :String, :each_line, '(?String) {(String) -> %any} -> String' RDL.type :String, :each_line, '(?String) -> Enumerator' RDL.type :String, :empty?, '() ->``output_type(trec, targs, :empty?, "%bool")``' # RDL.type :String, :encode, '(?Encoding, ?Encoding, *Symbol) -> String' # TODO: fix Hash arg:String, @@ -211,10 +211,12 @@ def String.clear_output(trec) RDL.type :String, :gsub, '(Regexp or String, Hash) -> ``output_type(trec, targs, :gsub, "String")``' RDL.type :String, :gsub, '(Regexp or String, String) -> ``output_type(trec, targs, :gsub, "String")``', wrap: false RDL.type :String, :gsub, '(Regexp or String) {(String) -> %any } -> ``output_type(trec, targs, :gsub, "String")``' +RDL.type :String, :gsub, '(Regexp or String) {() -> %any } -> ``output_type(trec, targs, :gsub, "String")``' + RDL.type :String, :gsub, '(Regexp or String, String) -> ``output_type(trec, targs, :gsub, "String")``', wrap: false RDL.type :String, :gsub, '(Regexp or String) -> ``output_type(trec, targs, :gsub, "String")``' RDL.type :String, :gsub!, '(Regexp or String, String) -> ``string_promote!(trec)``', wrap: false -RDL.type :String, :gsub!, '(Regexp or String) {(String) -> %any } -> ``string_promote!(trec)``', wrap: false +RDL.type :String, :gsub!, '(Regexp or String) {(?String) -> %any } -> ``string_promote!(trec)``', wrap: false RDL.type :String, :gsub!, '(Regexp or String) -> ``string_promote!(trec); RDL::Type::NominalType.new(Enumerator)``', wrap: false RDL.type :String, :hash, '() -> Integer' RDL.type :String, :hex, '() -> ``output_type(trec, targs, :getbyte, "Integer")``' @@ -228,7 +230,7 @@ def String.replace_output(trec, targs) case targs[0] when RDL::Type::PreciseStringType trec.vals = targs[0].vals - raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds + raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds(true) trec else raise RDL::Typecheck::StaticTypeError, "Failed to promote string #{trec}." unless trec.promote! @@ -251,7 +253,7 @@ def String.insert_output(trec, targs) arg_int = targs[0].val arg_str = targs[1].vals.join trec.vals = [rec_str.insert(arg_int, arg_str)] - raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds + raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds(true) trec else raise RDL::Typecheck::StaticTypeError, "Failed to promote string #{trec}." unless trec.promote! @@ -279,7 +281,7 @@ def String.lrstrip_output(trec, meth) if trec.vals[0].is_a?(String) if trec.vals[0].send(check, " ") trec.vals[0].send(meth) - raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds + raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds(true) trec else trec @@ -305,7 +307,7 @@ def String.mutate_output(trec, meth) when RDL::Type::PreciseStringType if trec.vals.all? { |v| v.is_a?(String) } trec.vals = [trec.vals.join.send(meth)] - raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds + raise RDL::Typecheck::StaticTypeError, "Failed to mutate string: new string #{trec} does not match prior constraints." unless trec.check_bounds(true) trec else raise RDL::Typecheck::StaticTypeError, "Failed to promote string #{trec}." unless trec.promote! @@ -330,7 +332,7 @@ def String.mutate_output(trec, meth) RDL.type :String, :rstrip, '() -> ``output_type(trec, targs, :rstrip, "String")``' RDL.type :String, :rstrip!, '() -> ``lrstrip_output(trec, :rstrip!)``' RDL.type :String, :scan, '(Regexp or String) -> ``output_type(trec, targs, :scan, "Array>")``', wrap: false # :String, Can't wrap or screws up last_match -RDL.type :String, :scan, '(Regexp or String) {(*%any) -> %any} -> ``output_type(trec, targs, :scan, "Array>")``', wrap: false +RDL.type :String, :scan, '(Regexp or String) {(*String) -> %any} -> ``output_type(trec, targs, :scan, "Array>")``', wrap: false RDL.type :String, :scrub, '(?String) -> ``output_type(trec, targs, :scrub, "String")``' RDL.type :String, :scrub, '(?String) {(%any) -> %any} -> String' RDL.type :String, :scrub!, '(?String) -> ``string_promote!(trec)``' @@ -368,7 +370,7 @@ def String.mutate_output(trec, meth) RDL.type :String, :tr!, '(String, String) -> ``string_promote!(trec)``' RDL.type :String, :tr_s, '(String, String) -> ``output_type(trec, targs, :tr_s, "String")``' RDL.type :String, :tr_s!, '(String, String) -> ``string_promote!(trec)``' -RDL.type :String, :unpack, '(String) -> ``output_type(trec, targs, :unpack, "Array")``' +RDL.type :String, :unpack, '(String) -> ``output_type(trec, targs, :unpack, "Array")``' RDL.type :String, :upcase, '() -> ``output_type(trec, targs, :upcase, "String")``' RDL.type :String, :upcase!, '() -> ``mutate_output(trec, :upcase!)``' RDL.type :String, :upto, '(String, ?bool) -> Enumerator' @@ -440,7 +442,7 @@ def String.mutate_output(trec, meth) RDL.type :String, :each_char, '() -> Enumerator' RDL.type :String, :each_codepoint, '() {(Integer) -> %any} -> String' RDL.type :String, :each_codepoint, '() -> Enumerator' -RDL.type :String, :each_line, '(?String) {(Integer) -> %any} -> String' +RDL.type :String, :each_line, '(?String) {(String) -> %any} -> String' RDL.type :String, :each_line, '(?String) -> Enumerator' RDL.type :String, :empty?, '() -> %bool' # RDL.type :String, :encode, '(?Encoding, ?Encoding, *Symbol) -> String' # TODO: fix Hash arg:String, diff --git a/lib/types/core/symbol.rb b/lib/types/core/symbol.rb index 95d0da13..6cbf3b09 100644 --- a/lib/types/core/symbol.rb +++ b/lib/types/core/symbol.rb @@ -2,8 +2,8 @@ RDL.type :Symbol, 'self.all_symbols', '() -> Array' RDL.type :Symbol, :<=>, '(Symbol other) -> Integer or nil' -RDL.type :Symbol, :==, '(%any obj) -> %bool', effect: [:+, :+] -RDL.type :Symbol, :=~, '(%any obj) -> Integer or nil' +RDL.type :Symbol, :==, '(Object) -> %bool', effect: [:+, :+] +RDL.type :Symbol, :=~, '(Object) -> Integer or nil' RDL.type :Symbol, :[], '(Integer idx) -> String' RDL.type :Symbol, :[], '(Integer b, Integer n) -> String' RDL.type :Symbol, :[], '(Range) -> String' diff --git a/lib/types/core/time.rb b/lib/types/core/time.rb index 9d52affc..3632cf0e 100644 --- a/lib/types/core/time.rb +++ b/lib/types/core/time.rb @@ -1,5 +1,6 @@ RDL.nowrap :Time +RDL.type :Time, 'self.at', '(Numeric seconds, Numeric microseconds_with_frac) -> ``RDL::Type::NominalType.new(trec.val)``' RDL.type :Time, 'self.at', '(Time) -> Time' RDL.type :Time, 'self.at', '(Numeric seconds_with_frac) -> Time' RDL.type :Time, 'self.at', '(Numeric seconds, Numeric microseconds_with_frac) -> Time' @@ -13,7 +14,7 @@ RDL.type :Time, :+, '(Numeric) -> Time' RDL.type :Time, :-, '(Time) -> Float' RDL.type :Time, :-, '(Numeric) -> Time' -RDL.type :Time, :<=>, '(Time other) -> -1 or 0 or 1 or nil' +RDL.type :Time, :<=>, '(Time or DateTime) -> -1 or 0 or 1 or nil' RDL.type :Time, :asctime, '() -> String' RDL.type :Time, :ctime, '() -> String' RDL.type :Time, :day, '() -> Integer' @@ -31,7 +32,7 @@ RDL.type :Time, :hour, '() -> Integer' RDL.type :Time, :inspect, '() -> String' RDL.type :Time, :isdst, '() -> %bool' -RDL.type :Time, :localtime, '(?String utc_offset) -> self' +RDL.type :Time, :localtime, '(?(String or Integer)) -> self' RDL.type :Time, :mday, '() -> Integer' RDL.type :Time, :min, '() -> Integer' RDL.type :Time, :mon, '() -> Integer' @@ -42,13 +43,13 @@ RDL.type :Time, :saturday?, '() -> %bool' RDL.type :Time, :sec, '() -> Integer' RDL.type :Time, :strftime, '(String) -> String' -RDL.type :Time, :subsec, '() -> Numeric' +RDL.type :Time, :subsec, '() -> Integer or Rational' RDL.type :Time, :succ, '() -> Time' RDL.type :Time, :sunday?, '() -> %bool' RDL.type :Time, :thursday?, '() -> %bool' RDL.type :Time, :to_a, '() -> [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, %bool, String]' RDL.type :Time, :to_f, '() -> Float' -RDL.type :Time, :to_i, '() -> Numeric' +RDL.type :Time, :to_i, '() -> Integer' RDL.type :Time, :to_r, '() -> Rational' RDL.type :Time, :to_s, '() -> String' RDL.type :Time, :tuesday?, '() -> %bool' diff --git a/lib/types/rails/action_controller/base.rb b/lib/types/rails/action_controller/base.rb index 1236398b..f74a040e 100644 --- a/lib/types/rails/action_controller/base.rb +++ b/lib/types/rails/action_controller/base.rb @@ -1,3 +1,3 @@ RDL.nowrap :'ActionController::Base' RDL.type :'ActionController::Base', 'self.helpers', '() -> ActionView::Base' -RDL.type :'ActionController::Base', :logger, '() -> ActiveSupport::TaggedLogging' +RDL.type :'ActionController::Base', 'self.logger', '() -> ActiveSupport::TaggedLogging' diff --git a/lib/types/rails/action_controller/instrumentation.rb b/lib/types/rails/action_controller/instrumentation.rb index d388cc24..e18c17c3 100644 --- a/lib/types/rails/action_controller/instrumentation.rb +++ b/lib/types/rails/action_controller/instrumentation.rb @@ -1,5 +1,6 @@ RDL.nowrap :'ActionController::Instrumentation' RDL.type :'ActionController::Instrumentation', :render, '(String or Symbol) -> Array' -RDL.type :'ActionController::Instrumentation', :render, '(?String or Symbol, {content_type: ?String, layout: ?%bool or String, action: ?String or Symbol, location: ?String, nothing: ?%bool, text: ?[to_s: () -> String], status: ?Symbol, content_type: ?String, formats: ?Symbol or Array, locals: ?Hash}) -> Array' -RDL.type :'ActionController::Instrumentation', :redirect_to, '({controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' +RDL.type :'ActionController::Instrumentation', :render, '(?String or Symbol, {content_type: ?String, layout: ?%bool or String, action: ?String or Symbol, location: ?String, nothing: ?%bool, file: ?String, text: ?[to_s: () -> String], status: ?Symbol, content_type: ?String, formats: ?Symbol or Array, locals: ?Hash}) -> Array' +RDL.type :'ActionController::Instrumentation', :redirect_to, '(?(String or Symbol or ActiveRecord::Base), {controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' +#RDL.type :'ActionController::Instrumentation', :redirect_to, '({controller: ?String, action: String, notice: ?String, alert: ?String}) -> String' ## When no first argument is provided, `action` must be present in options. #RDL.type :'ActionController::Instrumentation', :redirect_to, '(String or Symbol or ActiveRecord::Base, ?{controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' diff --git a/lib/types/rails/action_controller/mime_responds.rb b/lib/types/rails/action_controller/mime_responds.rb index 7ab0553a..08232fe6 100644 --- a/lib/types/rails/action_controller/mime_responds.rb +++ b/lib/types/rails/action_controller/mime_responds.rb @@ -1,14 +1,14 @@ RDL.nowrap :'ActionController::MimeResponds' RDL.type :'ActionController::MimeResponds', :respond_to, - '(*(String or Symbol)) { (ActionController::MimeResponds::Collector) -> %any } -> Array or String' +'(?(String or Symbol)) { (ActionController::MimeResponds::Collector) -> %any } -> Array or String' module ActionController module MimeResponds class Collector Mime::EXTENSION_LOOKUP.each { |mime| RDL.type :'ActionController::MimeResponds::Collector', :method_missing, - "(#{mime[1].symbol.inspect}) { (ActionController::MimeResponds::Collector::VariantCollector) -> %any } -> %any" + "(#{mime[1].symbol.inspect}) { (?ActionController::MimeResponds::Collector::VariantCollector) -> %any } -> %any" } end end diff --git a/lib/types/rails/action_controller/strong_parameters.rb b/lib/types/rails/action_controller/strong_parameters.rb index 8d3213b9..e251abda 100644 --- a/lib/types/rails/action_controller/strong_parameters.rb +++ b/lib/types/rails/action_controller/strong_parameters.rb @@ -15,4 +15,4 @@ def self.params_type(typs) end end -RDL.type :'ActionController::StrongParameters', :params, '() -> ActionController::Parameters' +RDL.type :'ActionController::StrongParameters', :params, "() -> Hash", wrap: false#'() -> ActionController::Parameters' diff --git a/lib/types/rails/action_mailer/base.rb b/lib/types/rails/action_mailer/base.rb index 28b8172a..87bed13e 100644 --- a/lib/types/rails/action_mailer/base.rb +++ b/lib/types/rails/action_mailer/base.rb @@ -1,2 +1,2 @@ RDL.nowrap :'ActionMailer::Base' -RDL.type :'ActionMailer::Base', :mail, '({subject: ?String, to: ?String, from: ?String, cc: ?String or Array, bcc: ?String or Array, reply_to: ?String, date: ?String}) -> Mail::Message' +RDL.type :'ActionMailer::Base', :mail, '({subject: ?String, to: ?String, from: ?String, cc: ?String or Array, bcc: ?String or Array, reply_to: ?String, date: ?String}) -> ActionMailer::MessageDelivery' diff --git a/lib/types/rails/active_model/errors.rb b/lib/types/rails/active_model/errors.rb index 581b5d08..d6f3bb84 100644 --- a/lib/types/rails/active_model/errors.rb +++ b/lib/types/rails/active_model/errors.rb @@ -10,5 +10,5 @@ RDL.type :'ActiveModel::Errors', :empty?, '() -> %bool' RDL.rdl_alias :'ActiveModel::Errors', :blank?, :empty? RDL.type :'ActiveModel::Errors', :hash, '(?%bool full_messages) -> Hash' -RDL.type :'ActiveModel::Errors', :add, '(%symstr, %symstr, ?Hash) -> %any' -RDL.type :'ActiveModel::Errors', :add, '(%symstr, { () -> String }, Hash) -> %any' # TODO: combine with prev with union once supported +RDL.type :'ActiveModel::Errors', :add, '(%symstr, %symstr, ?Hash) -> Array' +RDL.type :'ActiveModel::Errors', :add, '(%symstr, { () -> String }, Hash) -> Array' # TODO: combine with prev with union once supported diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index 0ff464ca..67143ea1 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -125,8 +125,8 @@ module ActiveRecord::Core::ClassMethods extend RDL::Annotate ## Types from this module are used when receiver is ActiveRecord::Base - type :find, '(Integer or String) -> ``DBType.find_output_type(trec, targs)``', wrap: false - type :find, '(Array) -> ``DBType.find_output_type(trec, targs)``', wrap: false + type :find, '(Integer or String or Symbol or Array) -> ``DBType.find_output_type(trec, targs)``', wrap: false + #type :find, '(Array) -> ``DBType.find_output_type(trec, targs)``', wrap: false type :find, '(Integer, Integer, *Integer) -> ``DBType.find_output_type(trec, targs)``', wrap: false type :find_by, '(``DBType.find_input_type(trec, targs)``) -> ``DBType.rec_to_nominal(trec)``', wrap: false ## TODO: find_by's with conditions given as string @@ -217,6 +217,20 @@ module ActiveRecord::Relation::QueryMethods end +module ActionController::Instrumentation + extend RDL::Annotate +=begin + type :redirect_to, "(``redirect_input(targs)``) -> String", wrap: false + + ## When first input is a VarType, we want to associate that VarType with the first (optional) arg of redirect_to. + ## Same goes if first input is anything *other than* a FHT. + ## Otherwise, we drop first optional arg. + def self.redirect_input(targs) + + end +=end +end + class ActiveRecord::QueryMethods::WhereChain extend RDL::Annotate type_params [:t], :dummy @@ -298,9 +312,12 @@ class ActiveRecord_Relation type :size, "() -> Integer", wrap: false type :update_all, '(``RDL::Type::UnionType.new(RDL::Globals.types[:string], DBType.rec_to_schema_type(trec, true))``) -> Integer', wrap: false type :valid, "() -> self", wrap: false + type :sort, "() { (t, t) -> Integer } -> Array", wrap: false end + + class DBType ## given a type (usually representing a receiver type in a method call), this method returns the nominal type version of that type. ## if the given type represents a joined table, then we return the nominal type version of the *base* of the joined table. @@ -493,7 +510,7 @@ def self.find_output_type(trec, targs) when 1 arg0 = targs[0] case arg0 - when RDL::Globals.types[:integer], RDL::Globals.types[:string] + when RDL::Globals.types[:integer], RDL::Globals.types[:string], RDL::Globals.types[:symbol] DBType.rec_to_nominal(trec) when RDL::Type::SingletonType # expecting symbol or integer here @@ -656,7 +673,7 @@ def self.plus_output_type(trec, targs) [trec, targs[0]].each { |t| case t when RDL::Type::GenericType - raise "Expected ActiveRecord_Relation." unless t.base.name == "ActiveRecord_Relation" + raise "Expected ActiveRecord_Relation, got #{t} for #{trec} and #{targs}." unless (t.base.name == "ActiveRecord_Relation") param0 = t.params[0] case param0 when RDL::Type::GenericType @@ -668,6 +685,8 @@ def self.plus_output_type(trec, targs) else raise "unexpected paramater type in #{t}" end + when RDL::Type::VarType + return RDL::Globals.types[:array] else raise "unexpected type #{t}" end diff --git a/lib/types/rails/active_record/sql-strings.rb b/lib/types/rails/active_record/sql-strings.rb index f665136e..8c9d0908 100644 --- a/lib/types/rails/active_record/sql-strings.rb +++ b/lib/types/rails/active_record/sql-strings.rb @@ -18,7 +18,7 @@ def binary_op(o) schema_type = RDL::Globals.ar_db_schema[table.classify.to_sym].params[0].elts[column.to_sym] query_type = query_type.elts[column.to_sym] if query_type.is_a? RDL::Type::FiniteHashType # puts query_type, schema_type - raise RDL::Typecheck::StaticTypeError, "type error" unless query_type <= schema_type + raise RDL::Typecheck::StaticTypeError, "type error #{query_type} <= #{schema_type}" unless query_type <= schema_type end alias_method :visit_Greater, :binary_op @@ -59,7 +59,7 @@ def visit_In(o) end schema_type = RDL::Globals.ar_db_schema[table.classify.to_sym].params[0].elts[column.to_sym] - # IN works with arrays +uts # IN works with arrays promoted = if query_type.is_a?(RDL::Type::TupleType) then query_type.promote else query_type end if promoted.is_a? RDL::Type::GenericType # base type is Array, maybe add check? @@ -135,8 +135,12 @@ def handle_sql_strings(trec, targs) when RDL::Type::SingletonType base_klass = trec sql_query = "SELECT * FROM `#{base_klass.val.to_s.tableize}` WHERE #{build_string_from_precise_string(targs)}" - # puts sql_query - ast = parser.scan_str(sql_query) + #puts sql_query + begin + ast = parser.scan_str(sql_query) + rescue => e + return ## the ruby SQL parser is very picky, so if we get a parse error, we just won't precisely type check that case + end search_cond = ast.query_expression.table_expression.where_clause.search_condition visitor = ASTVisitor.new base_klass.val.to_s.tableize, targs visitor.visit(search_cond) diff --git a/lib/types/rails/active_support/tagged_logging.rb b/lib/types/rails/active_support/tagged_logging.rb index 5088fd1b..69be8a3e 100644 --- a/lib/types/rails/active_support/tagged_logging.rb +++ b/lib/types/rails/active_support/tagged_logging.rb @@ -1,2 +1,3 @@ RDL.nowrap :'ActiveSupport::TaggedLogging' RDL.type :'ActiveSupport::TaggedLogging', :info, '(String) -> %bool' +RDL.type :'ActiveSupport::TaggedLogging', :debug, '(String) -> %bool' diff --git a/lib/types/rails/time.rb b/lib/types/rails/time.rb index f224748a..27f77642 100644 --- a/lib/types/rails/time.rb +++ b/lib/types/rails/time.rb @@ -1 +1,3 @@ RDL.type :Time, 'self.zone', '() -> ActiveSupport::TimeZone' +RDL.type :Time, :+, '(ActiveSupport::Duration) -> Time' +RDL.type :Time, :-, '(ActiveSupport::Duration) -> Time' diff --git a/lib/types/sequel/comp_types.rb b/lib/types/sequel/comp_types.rb index 4cac9626..fe4db6b9 100644 --- a/lib/types/sequel/comp_types.rb +++ b/lib/types/sequel/comp_types.rb @@ -587,6 +587,7 @@ def self.schema_arg_tuple_type(trec, targs, meth) RDL.type Table, 'self.schema_arg_tuple_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] def self.where_arg_type(trec, targs, tuple=false) + return targs[0] if targs[0] == RDL::Globals.types[:string] trp0 = RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true) if trp0.elts[:__all_joined].is_a?(RDL::Type::UnionType) arg0 = targs[0] diff --git a/rdl.gemspec b/rdl.gemspec index a2ab9c55..7e847591 100644 --- a/rdl.gemspec +++ b/rdl.gemspec @@ -20,4 +20,5 @@ EOF s.license = 'BSD-3-Clause' s.add_runtime_dependency 'parser', '~>2.3', '>= 2.3.1.4' s.add_runtime_dependency 'sql-parser', '~>0.0.2' + s.add_runtime_dependency 'method_source' end From fea7876935842a076dc2ea7a7ef3513a4053721d Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 12 Dec 2019 08:43:19 -0500 Subject: [PATCH 009/124] beginning of unionvar/possible types --- lib/rdl/typecheck.rb | 18 +++++++++++++++--- lib/rdl/types/possible.rb | 20 ++++++++++++++++++++ lib/rdl/types/tuple.rb | 2 +- lib/rdl/types/unionvar.rb | 21 +++++++++++++++++++++ lib/rdl/wrap.rb | 8 +++++++- lib/types/core/array.rb | 6 ++++-- lib/types/core/enumerable.rb | 2 +- lib/types/core/hash.rb | 4 ++-- lib/types/core/module.rb | 9 +++++---- 9 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 lib/rdl/types/possible.rb create mode 100644 lib/rdl/types/unionvar.rb diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index cbcb48d4..3a867a2c 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -1368,7 +1368,7 @@ def self.tc(scope, env, e) [envi, tres.canonical, effres] } else - raise RuntimeError, "Expression kind #{e.type} unsupported" + raise RuntimeError, "Expression kind #{e.type} unsupported, for expression #{e}" end end @@ -1508,7 +1508,11 @@ def self.tc_type_cast(scope, env, e) typ_str = e.children[3].children[0] if (e.children[3].type == :str) || (e.children[3].type == :string) error :type_cast_format, [], e.children[3] if typ_str.nil? begin - typ = RDL::Globals.parser.scan_str("#T " + typ_str) + if RDL::Util.has_singleton_marker(typ_str) + typ = RDL::Type::SingletonType.new(RDL::Globals.parser.scan_str("#T #{RDL::Util.remove_singleton_marker(typ_str)}").klass) + else + typ = RDL::Globals.parser.scan_str("#T " + typ_str) + end rescue Racc::ParseError => err error :generic_error, [err.to_s[1..-1]], e.children[3] # remove initial newline end @@ -1627,6 +1631,7 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) # Like tc_send but trecv should never be a union type # Returns array of possible return types, or throws exception if there are none def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) + raise "Type checking not currently supported for method #{meth}." if [:define_method, :module_exec].include?(meth) =begin puts "----------------------" puts "Type checking method call to #{meth} for receiver #{trecv} and tactuals of size #{tactuals.size}:" @@ -1778,7 +1783,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, end if block - if block[0].is_a?(RDL::Type::MethodType) + if block[0].is_a?(RDL::Type::MethodType) || block[0].is_a?(RDL::Type::VarType) meth_type = RDL::Type::MethodType.new(tactuals, block[0], ret_type) else blk_args = block[0].children.map {|a| a.children[0]} @@ -1863,6 +1868,13 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, #apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? if tmeth_inst begin +=begin + if (meth == :define_method) && trecv.is_a?(RDL::Type::SingletonType) + new_env = env.bind(:self, RDL::Type::NominalType.new(trecv.val), force: true) ## define_method defines a new *instance* method... self in block is nominal, not singleton + else + new_env = env + end +=end effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block rescue BlockTypeError => err block_mismatch = true diff --git a/lib/rdl/types/possible.rb b/lib/rdl/types/possible.rb new file mode 100644 index 00000000..b9b109b3 --- /dev/null +++ b/lib/rdl/types/possible.rb @@ -0,0 +1,20 @@ +module RDL::Type + class PossibleType < VarType + + # [+ types +] is an array of the possible upper bounds. + # [+ unionvar +] is the union var that is decided if this type is ever decided. + def initialize(types, unionvar) + raise "Expected at least one type." if types.size == 0 + raise "Expected UnionVar type." unless unionvar.is_a?(UnionVarType) + @possible_ubounds = types + @unionvar = unionvar + end + + ## When PossibleType has been reduced to just one type, that must be the true type. + ## Otherwise, continue to treat it as a PossibleType. + def canonical + if types.size == 1 then types[0] else self end + end + + end +end diff --git a/lib/rdl/types/tuple.rb b/lib/rdl/types/tuple.rb index 68453e1b..ffa4e20c 100644 --- a/lib/rdl/types/tuple.rb +++ b/lib/rdl/types/tuple.rb @@ -46,7 +46,7 @@ def match(other) def promote(t=nil) return false if @cant_promote - param = (@params.empty? && !t) ? RDL::Type::VarType.new(:t) : UnionType.new(*@params, t) + param = (@params.empty? && !t) ? RDL::Type::VarType.new(:t) : UnionType.new(*@params.map { |p| if p.is_a?(RDL::Type::SingletonType) then p.nominal else p end }, t) param = param.widen if RDL::Config.instance.promote_widen GenericType.new(RDL::Globals.types[:array], param) end diff --git a/lib/rdl/types/unionvar.rb b/lib/rdl/types/unionvar.rb new file mode 100644 index 00000000..34686362 --- /dev/null +++ b/lib/rdl/types/unionvar.rb @@ -0,0 +1,21 @@ +module RDL::Type + class UnionVarType < VarType + + # [+ type_map +] is a Hash. Keys represent types in the corresponding + # PossibleType, and values represent the resulting true types of self. + # [+ possible +] is the corresponding PossibleType. + def initialize(type_map, possible) + raise "Expected at least one type." if type_map.size == 0 + raise "Expected PossibleType, given #{possible}" unless possible.is_a?(PossibleType) + @type_map = type_map + @possible = possible + end + + ## When UnionVarType has been reduced to just one type, that is the true type. + ## Otherwise, continue to treat it as UnionVarType. + def canonical + if (type_map.size == 1) return type_map.values[0] else return self end + end + + end +end diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 4aa61391..ba4ec0be 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -913,7 +913,13 @@ def self.remove_type(klass, meth) # Returns a new object that wraps self in a type cast. If force is true this cast is *unchecked*, so use with caution def self.type_cast(obj, typ, force: false) - new_typ = if typ.is_a? RDL::Type::Type then typ else RDL::Globals.parser.scan_str "#T #{typ}" end + new_typ = if typ.is_a? RDL::Type::Type + typ + elsif RDL::Util.has_singleton_marker(typ) + RDL::Type::SingletonType.new(RDL::Globals.parser.scan_str("#T #{RDL::Util.remove_singleton_marker(typ)}").klass) + else + RDL::Globals.parser.scan_str "#T #{typ}" + end raise RuntimeError, "type cast error: self not a member of #{new_typ}" unless force || new_typ.member?(obj) new_obj = SimpleDelegator.new(obj) new_obj.instance_variable_set('@__rdl_type', new_typ) diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index fc431043..ee18495e 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -201,6 +201,8 @@ def Array.assign_output(trec, targs) RDL.type Array, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] RDL.type :Array, :[]=, '(Integer, Integer, ``any_or_t(trec)``) -> t' +RDL.type :Array, :[]=, '(Integer, Integer, ``any_or_t(trec)``) -> t' +RDL.type :Array, :[]=, '(Integer, Integer, ``RDL::Type::VarType.new(:self)``) -> self' def Array.multi_assign_output(trec, targs) @@ -310,8 +312,8 @@ def Array.each_arg2(trec) RDL.type :Array, :each_index, '() -> Enumerator' RDL.type :Array, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``', effect: [:+, :+] RDL.type :Array, :fetch, '(Integer) -> ``output_type(trec, targs, :[], :promoted_param, "t")``' -RDL.type :Array, :fetch, '(Integer, u) -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("u"), output_type(trec, targs, :[], :promoted_param, "t"))``' -RDL.type :Array, :fetch, '(Integer) { (Integer) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("u"), output_type(trec, targs, :[], :promoted_param, "t"))``' +RDL.type :Array, :fetch, '(Integer, u) -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :[], :promoted_param, "t"))``' +RDL.type :Array, :fetch, '(Integer) { (Integer) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :[], :promoted_param, "t"))``' RDL.type :Array, :fill, '(``any_or_t(trec)``) -> ``fill_output(trec, targs)``' diff --git a/lib/types/core/enumerable.rb b/lib/types/core/enumerable.rb index cf5fba7c..74a6d4ae 100644 --- a/lib/types/core/enumerable.rb +++ b/lib/types/core/enumerable.rb @@ -5,7 +5,7 @@ RDL.type :Enumerable, :all?, '() -> %bool', effect: [:blockdep, :blockdep] RDL.type :Enumerable, :all?, '() { (t) -> %bool } -> %bool', effect: [:blockdep, :blockdep] RDL.type :Enumerable, :any?, '() -> %bool' -RDL.type :Enumerable, :any?, '() { (t) -> %bool } -> %bool' +RDL.type :Enumerable, :any?, '() { (t) -> %any } -> %bool' # RDL.type :Enumerable, :chunk, '(XXXX : *XXXX)' # TODO RDL.type :Enumerable, :collect, '() { (?t) -> u } -> Array' RDL.type :Enumerable, :collect, '() -> Enumerator' diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 366ab757..3e6ca654 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -257,8 +257,8 @@ def Hash.delete_output(trec, targs, block) RDL.type :Hash, :each_value, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), any_or_v(trec))``' RDL.type :Hash, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) -> ``output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true)``' -RDL.type :Hash, :fetch, '(``any_or_k(trec)``, u) -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' -RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``any_or_k(trec)``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' +RDL.type :Hash, :fetch, '(``any_or_k(trec)``, u) -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' +RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``RDL::Type::OptionalType.new(any_or_k(trec))``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :member?, '(%any) -> ``output_type(trec, targs, :member?, "%bool")``' RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``', effect: [:+, :+] RDL.type :Hash, :key?, '(%any) -> ``output_type(trec, targs, :key?, "%bool")``' diff --git a/lib/types/core/module.rb b/lib/types/core/module.rb index 19d53828..87d0ce5f 100644 --- a/lib/types/core/module.rb +++ b/lib/types/core/module.rb @@ -17,14 +17,15 @@ RDL.type :Module, :ancestors, '() -> Array' RDL.type :Module, :autoload, '(Symbol module, String filename) -> nil' RDL.type :Module, :autoload?, '(Symbol name) -> String or nil' -RDL.type :Module, :class_eval, '(String, ?String filename, ?Integer lineno) -> %any' +RDL.type :Module, :class_eval, '(?String, ?String filename, ?Integer lineno) -> %any' +RDL.type :Module, :class_eval, '() { () -> x } -> x' RDL.type :Module, :class_exec, '(*%any args) { (*%any args) -> %any } -> %any' RDL.type :Module, :class_variable_defined?, '(Symbol or String) -> %bool' RDL.type :Module, :class_variable_get, '(Symbol or String) -> %any' RDL.type :Module, :class_variable_set, '(Symbol or String, %any) -> %any' RDL.type :Module, :class_variables, '(?%bool inherit) -> Array' RDL.type :Module, :const_defined?, '(Symbol or String, ?%bool inherit) -> %bool' -RDL.type :Module, :const_get, '(Symbol or String, ?%bool inherit) -> %any' +RDL.type :Module, :const_get, '(Symbol or String, ?%bool inherit) -> Object' RDL.type :Module, :const_missing, '(Symbol) -> %any' RDL.type :Module, :const_set, '(Symbol or String, %any) -> %any' RDL.type :Module, :constants, '(?%bool inherit) -> Array' @@ -37,7 +38,7 @@ RDL.type :Module, :instance_methods, '(?%bool include_super) -> Array' RDL.type :Module, :method_defined?, '(Symbol or String) -> %bool' RDL.type :Module, :module_eval, '(String, ?String filename, ?Integer lineno) -> %any' # matches rdoc example but not RDL.type -RDL.type :Module, :module_exec, '(*%any args) { (*%any args) -> %any } -> %any' +RDL.type :Module, :module_exec, '(*%any args) { (*%any) -> %any } -> %any' RDL.type :Module, :name, '() -> String' RDL.type :Module, :prepend, '(*Module) -> self' RDL.type :Module, :private_class_method, '(*(Symbol or String)) -> self' @@ -55,7 +56,7 @@ RDL.type :Module, :singleton_class?, '() -> %bool' RDL.type :Module, :to_s, '() -> String', effect: [:+, :+] # private methods below here -RDL.type :Module, :alias_method, '(Symbol new_name, Symbol old_name) -> self' +RDL.type :Module, :alias_method, '(Symbol or String new_name, Symbol or String old_name) -> self' RDL.type :Module, :append_features, '(Module) -> self' RDL.rdl_alias :Module, :attr, :attr_reader RDL.type :Module, :attr_accessor, '(*(Symbol or String)) -> nil' From 91ea75012f070e62392c101035a52a125579a8a0 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 12 Dec 2019 13:12:55 -0500 Subject: [PATCH 010/124] updates to choice types --- lib/rdl/boot.rb | 1 + lib/rdl/typecheck.rb | 72 +++++++++++++++++++++++++++++++++++---- lib/rdl/types/choice.rb | 30 ++++++++++++++++ lib/rdl/types/possible.rb | 20 ----------- lib/rdl/types/unionvar.rb | 21 ------------ lib/types/core/array.rb | 2 +- 6 files changed, 97 insertions(+), 49 deletions(-) create mode 100644 lib/rdl/types/choice.rb delete mode 100644 lib/rdl/types/possible.rb delete mode 100644 lib/rdl/types/unionvar.rb diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index c7a23458..7009ca65 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -124,6 +124,7 @@ class << RDL::Globals require 'rdl/types/annotated_arg.rb' require 'rdl/types/bound_arg.rb' require 'rdl/types/bot.rb' +require 'rdl/types/choice.rb' require 'rdl/types/computed.rb' require 'rdl/types/dependent_arg.rb' require 'rdl/types/dots_query.rb' diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 3a867a2c..698d5c5b 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -220,6 +220,7 @@ def self.get_ast(klass, meth) def self.infer(klass, meth) puts "*************** Infering method #{meth} from class #{klass} ***************" RDL::Config.instance.use_comp_types = true + RDL::Config.instance.number_mode = true @cur_meth = [klass, meth] ast = get_ast(klass, meth) types = RDL::Globals.info.get(klass, meth, :type) @@ -1829,23 +1830,30 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, trets = [] # all possible return types # there might be more than one return type because multiple cases of an intersection type might match tmeth_names = [] ## necessary for more precise error messages with ComputedTypes + arg_choices = Hash.new { |h, k| h[k] = {} } # Hash>. The keys are tactual arguments that are VarTypes. The values are choice hashes, to be turned into ChoiceTypes that will be upper bounds on the keys. + ret_choice = {} # Hash for return ChoiceType + deferred_constraints = [] ## Array> of deferred constraints to apply. + # for ALL of the expanded lists of actuals... if RDL::Config.instance.use_comp_types ts = filter_comp_types(ts, true) else ts = filter_comp_types(ts, false) error :no_non_dep_types, [trecv, meth], e unless !ts.empty? - end + end RDL::Type.expand_product(tactuals).each { |tactuals_expanded| # AT LEAST ONE of the possible intesection arms must match trets_tmp = [] - deferred_constraints = [] + #deferred_constraints = [] + choice_num = 0 ts.each_with_index { |tmeth, ind| # MethodType got_match = false current_dcs = [] comp_type = false + choice_num += 1 + current_ret = nil if tmeth.is_a? RDL::Type::DynamicType - trets_tmp << RDL::Type::DynamicType.new + trets_tmp << (current_ret = RDL::Type::DynamicType.new) elsif ((tmeth.block && block) || (tmeth.block.nil? && block.nil?) || tmeth.block.is_a?(RDL::Type::VarType)) if trecv.is_a?(RDL::Type::FiniteHashType) && trecv.the_hash trecv = trecv.canonical @@ -1913,10 +1921,10 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, error :bad_initialize_type, [], e unless (tmeth.ret == init_typ) unless ((trecv.val == Hash) && tmeth.ret.is_a?(RDL::Type::FiniteHashType)) || ((trecv.val == Array) && tmeth.ret.is_a?(RDL::Type::TupleType)) end #trets_tmp << init_typ unless block_mismatch - trets_tmp << tmeth.ret.instantiate(tmeth_inst) + trets_tmp << (current_ret = tmeth.ret.instantiate(tmeth_inst)) got_match = true elsif !block_mismatch - trets_tmp << (tmeth.ret.instantiate(tmeth_inst)) # found a match for this subunion; add its return type to trets_tmp + trets_tmp << (current_ret = (tmeth.ret.instantiate(tmeth_inst))) # found a match for this subunion; add its return type to trets_tmp got_match = true if comp_type && RDL::Config.instance.check_comp_types && !union if (e.type == :op_asgn) && op_asgn @@ -1931,17 +1939,67 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, block_mismatch = false end end - deferred_constraints += current_dcs if got_match + + if got_match + deferred_constraints += current_dcs + if ts.size > 1 && tactuals_expanded.any? { |t| t.is_a?(RDL::Type::VarType) } + ## need ChoiceTypes in this case + puts "DID WE MAKE IT HERE? WITH #{current_dcs}" + ## first convert constraints in current_dcs (which is of form [[t1, t2], [t3,t4]...]) + ## into hash of form { t1 => t2, t3 => t4 }, combining multiple upper bounds into Union. + constraints_hash = current_dcs.inject({}) do |hash, constraint| + hash[constraint[0]] = RDL::Type::UnionType.new(hash[constraint[0]], constraint[1]).canonical + hash + end + puts "AHORA CONSTRAINTS_HASH IS #{constraints_hash}" + ## next, make the choice hashes and add them to arg_choices + constraints_hash.each { |vartype, ubound| + puts "GOING ROUND WITH #{vartype} AND #{ubound}" + arg_choices[vartype][choice_num] = ubound + puts "DOES IT HAVE SOMETHING NOW? #{arg_choices[vartype]}" + } + + raise "Expected return type." unless current_ret + ret_choice[choice_num] = RDL::Type::UnionType.new(current_ret, ret_choice[choice_num]).canonical + puts "HERE WITH #{arg_choices}" + end + end + } if trets_tmp.empty? # no arm of the intersection matched this expanded actuals lists, so reset trets to signal error and break loop trets = [] break else - apply_deferred_constraints(deferred_constraints, e) if !deferred_constraints.empty? + #apply_deferred_constraints(deferred_constraints, e) if !deferred_constraints.empty? trets.concat(trets_tmp) end } + ## Down here EITHER: apply all deferred constraints, continue with trets OR turn choices into ChoiceTypes, apply those deferred constraints, turn trets into [ret_choice_type] + + if arg_choices.empty? + apply_deferred_constraints(deferred_constraints, e) if !deferred_constraints.empty? + else + new_dcs = [] + all_args = [] + arg_choices.each { |vartype, choice_hash| + ct = RDL::Type::ChoiceType.new(choice_hash) + new_dcs << [vartype, ct] + all_args << ct + } + + raise "Expected non-empty return choice hash." if ret_choice.empty? + if ret_choice.values.uniq == 1 + new_ret = ret_choice.values[0] + all_cts.each { |ct| ct.add_connecteds(*all_args) } + else + new_ret = RDL::Type::ChoiceType.new(ret_choice) + (all_cts+[new_ret]).each { |ct| ct.add_connecteds(new_ret, *all_args) } + end + trets = [new_ret] + end + + if trets.empty? # no possible matching call msg = <, where the keys are the distinct numbers of every chocie, + # and the values are the diferent Type choices. + # [+ connecteds +] is an Array of connected ChoiceTypes which are jointly chosen over. + # All connecteds should have the same choice numbers + def initialize(choices, connecteds=[]) + raise "Expected at least one choice." if choices.size == 0 + raise "Expected Hash, got #{choices}." unless choices.is_a?(Hash) && choices.keys.all? { |k| k.is_a?(Integer) } && choices.values.all { |v| v.is_a?(Type) } + raise "Expected Array of ChoiceTypes type." unless connecteds.is_a?(Array)&& connecteds.all? { |t| t.is_a?(ChoiceType) } + @choices = choices + @connecteds = connecteds + end + + def add_connecteds(*connecteds) + connecteds.each { |c| + raise "Expected ChoiceType, got #{c}." unless c.is_a?(ChoiceType) + @connecteds << c unless self.equal? c + } + end + + def to_s + "~~ #{connecteds} ~~" + end + + end +end diff --git a/lib/rdl/types/possible.rb b/lib/rdl/types/possible.rb deleted file mode 100644 index b9b109b3..00000000 --- a/lib/rdl/types/possible.rb +++ /dev/null @@ -1,20 +0,0 @@ -module RDL::Type - class PossibleType < VarType - - # [+ types +] is an array of the possible upper bounds. - # [+ unionvar +] is the union var that is decided if this type is ever decided. - def initialize(types, unionvar) - raise "Expected at least one type." if types.size == 0 - raise "Expected UnionVar type." unless unionvar.is_a?(UnionVarType) - @possible_ubounds = types - @unionvar = unionvar - end - - ## When PossibleType has been reduced to just one type, that must be the true type. - ## Otherwise, continue to treat it as a PossibleType. - def canonical - if types.size == 1 then types[0] else self end - end - - end -end diff --git a/lib/rdl/types/unionvar.rb b/lib/rdl/types/unionvar.rb deleted file mode 100644 index 34686362..00000000 --- a/lib/rdl/types/unionvar.rb +++ /dev/null @@ -1,21 +0,0 @@ -module RDL::Type - class UnionVarType < VarType - - # [+ type_map +] is a Hash. Keys represent types in the corresponding - # PossibleType, and values represent the resulting true types of self. - # [+ possible +] is the corresponding PossibleType. - def initialize(type_map, possible) - raise "Expected at least one type." if type_map.size == 0 - raise "Expected PossibleType, given #{possible}" unless possible.is_a?(PossibleType) - @type_map = type_map - @possible = possible - end - - ## When UnionVarType has been reduced to just one type, that is the true type. - ## Otherwise, continue to treat it as UnionVarType. - def canonical - if (type_map.size == 1) return type_map.values[0] else return self end - end - - end -end diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index ee18495e..ec31a6b9 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -112,7 +112,7 @@ def Array.append_push_output(trec, targs, meth) end RDL.type Array, 'self.append_push_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] -#RDL.type :Array, :[], '(Range) -> ``output_type(trec, targs, :[], :promoted_array, "Array")``' +RDL.type :Array, :[], '(Range) -> ``output_type(trec, targs, :[], :promoted_array, "Array")``' RDL.type :Array, :[], '(Integer or Float) -> ``output_type(trec, targs, :[], :promoted_param, "t")``' RDL.type :Array, :[], '(Integer, Integer) -> ``output_type(trec, targs, :[], :promoted_array, "Array")``' RDL.type :Array, :&, '(Array) -> ``output_type(trec, targs, :&, :promoted_array, "Array")``' From b943a5e9da6868a8526d8c14f9347410f1f3e6d9 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 2 Jan 2020 10:31:24 -0500 Subject: [PATCH 011/124] updates, mostly to choice types --- lib/rdl/boot.rb | 2 +- lib/rdl/constraint.rb | 55 ++++- lib/rdl/heuristics.rb | 6 +- lib/rdl/typecheck.rb | 80 +++++-- lib/rdl/types/choice.rb | 105 ++++++++- lib/rdl/types/intersection.rb | 2 +- lib/rdl/types/singleton.rb | 2 +- lib/rdl/types/tuple.rb | 2 +- lib/rdl/types/type.rb | 412 +++++++++++++++++++++++++++++----- lib/rdl/types/union.rb | 2 +- lib/rdl/types/var.rb | 29 ++- lib/types/core/array.rb | 8 +- lib/types/core/enumerable.rb | 4 +- lib/types/core/hash.rb | 3 +- lib/types/core/string.rb | 8 +- 15 files changed, 611 insertions(+), 109 deletions(-) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 7009ca65..88dcd1c3 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -124,7 +124,6 @@ class << RDL::Globals require 'rdl/types/annotated_arg.rb' require 'rdl/types/bound_arg.rb' require 'rdl/types/bot.rb' -require 'rdl/types/choice.rb' require 'rdl/types/computed.rb' require 'rdl/types/dependent_arg.rb' require 'rdl/types/dots_query.rb' @@ -146,6 +145,7 @@ class << RDL::Globals require 'rdl/types/type_query.rb' require 'rdl/types/union.rb' require 'rdl/types/var.rb' +require 'rdl/types/choice.rb' ## depends on var.rb require 'rdl/types/vararg.rb' require 'rdl/types/wild_query.rb' require 'rdl/types/string.rb' diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index d6c560d6..52aecf86 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -46,24 +46,24 @@ def self.extract_var_sol(var, category) #raise "Expected VarType, got #{var}." unless var.is_a?(RDL::Type::VarType) return var.canonical unless var.is_a?(RDL::Type::VarType) if category == :arg - non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } sol = non_vartype_ubounds.size == 1 ? non_vartype_ubounds[0] : RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::IntersectionType) ## could be, e.g., nominal type if only one type used to create intersection. #return sol elsif category == :ret - non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } sol = RDL::Type::UnionType.new(*non_vartype_lbounds) sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. - #return sol + #return sol elsif category == :var if var.lbounds.empty? || (var.lbounds.size == 1 && var.lbounds[0][0] == RDL::Globals.types[:bot]) ## use upper bounds in this case. - non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } sol = RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical #return sol else ## use lower bounds - non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.is_a?(RDL::Type::VarType) } + non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } sol = RDL::Type::UnionType.new(*non_vartype_lbounds) sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. #return sol#RDL::Type::UnionType.new(*non_vartype_lbounds).canonical @@ -71,7 +71,6 @@ def self.extract_var_sol(var, category) else raise "Unexpected VarType category #{category}." end - #sol.is_a?(RDL::Type::UnionType) if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) ## Try each rule. Return first non-nil result. ## If no non-nil results, return original solution. @@ -87,6 +86,13 @@ def self.extract_var_sol(var, category) typ = typ.canonical var.add_and_propagate_upper_bound(typ, nil, new_cons) var.add_and_propagate_lower_bound(typ, nil, new_cons) + + new_cons.each_key { |var_type| + new_cons[var_type].each { |u_or_l, bound, _| + puts "Here 1 with #{u_or_l} bound #{bound} of kind #{bound.class} on #{var_type}" + } + } + @new_constraints = true if !new_cons.empty? return typ #sol = typ @@ -98,7 +104,6 @@ def self.extract_var_sol(var, category) ## no new constraints in this case so we'll leave it as is end } - end ## out here, none of the heuristics applied. ## Try to use `sol` as solution -- there is a chance it will @@ -110,8 +115,18 @@ def self.extract_var_sol(var, category) var.add_and_propagate_upper_bound(sol, nil, new_cons) var.add_and_propagate_lower_bound(sol, nil, new_cons) @new_constraints = true if !new_cons.empty? + + new_cons.each_key { |var_type| + new_cons[var_type].each { |u_or_l, bound, _| + puts "Here 2 with #{u_or_l} bound #{bound} of kind #{bound.class} on #{var_type}" + puts "It has name #{bound.name}" if bound.is_a?(RDL::Type::NominalType) + puts "#{var_type} already has bounds:" + var_type.ubounds.each { |t, _| puts "#{t} of kind #{t.class} and name #{t.name if t.is_a?(RDL::Type::NominalType)}" } + } + } + if sol.is_a?(RDL::Type::GenericType) - new_params = sol.params.map { |p| extract_var_sol(p, category) } + new_params = sol.params.map { |p| if p.is_a?(RDL::Type::VarType) && !p.to_infer then p else extract_var_sol(p, category) end } sol = RDL::Type::GenericType.new(sol.base, *new_params) elsif sol.is_a?(RDL::Type::TupleType) new_params = sol.params.map { |t| extract_var_sol(t, category) } @@ -211,15 +226,19 @@ def self.extract_solutions typ_sols = {} puts "\n\nRunning solution extraction..." RDL::Globals.constrained_types.each { |klass, name| + RDL::Type::VarType.no_print_XXX! typ = RDL::Globals.info.get(klass, name, :type) if typ.is_a?(Array) raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 tmeth = typ[0] + arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) - block_string = block_sol ? " { #{block_sol} }" : nil puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" + + RDL::Type::VarType.print_XXX! + block_string = block_sol ? " { #{block_sol} }" : nil typ_sols[[klass.to_s, name.to_sym]] = "(#{arg_sols.join(', ')})#{block_string} -> #{ret_sol}" elsif name.to_s == "splat_param" else @@ -231,8 +250,9 @@ def self.extract_solutions ## Can improve later if desired. var_sol = extract_var_sol(typ, :var) #typ.solution = var_sol - - puts "Extracted solution for #{typ} is #{var_sol}." + puts "Extracted solution for #{klass} variable #{name} is #{var_sol}." + + RDL::Type::VarType.print_XXX! typ_sols[[klass.to_s, name.to_sym]] = var_sol.to_s end } @@ -241,6 +261,9 @@ def self.extract_solutions #return unless $orig_types + complete_types = [] + incomplete_types = [] + CSV.open("infer_data.csv", "wb") { |csv| csv << ["Class", "Method", "Inferred Type", "Original Type", "Source Code", "Comments"] } @@ -300,9 +323,19 @@ def self.extract_solutions comment = RDL::Util.to_class(klass).instance_method(meth).comment end csv << [klass, meth, typ, orig_typ, code, comment] + #if typ.include?("XXX") + # incomplete_types << [klass, meth, typ, orig_typ, code, comment] + #else + # complete_types << [klass, meth, typ, orig_typ, code, comment] + #end } end } + #CSV.open("infer_data.csv", "a+") { |csv| + #complete_types.each { |row| csv << row } + #csv << ["X", "X", "X", "X", "X", "X"] + #incomplete_types.each { |row| csv << row } + #} puts "Total correct (that could be automatically inferred): #{correct_types}" puts "Total # method types: #{meth_types}" diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index efb027a3..f6fa841c 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -16,15 +16,15 @@ def self.struct_to_nominal(var_type) struct_types = var_type.ubounds.select { |t, loc| t.is_a?(RDL::Type::StructuralType) } struct_types.map! { |t, loc| t } return if struct_types.empty? - meth_names = struct_types.map { |st| st.methods.keys }.flatten + meth_names = struct_types.map { |st| st.methods.keys }.flatten.uniq matching_classes = ObjectSpace.each_object(Class).select { |c| class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods matching_classes.reject! { |c| c.to_s.start_with?("# 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } union = RDL::Type::UnionType.new(*nom_sing_types).canonical diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 698d5c5b..cd8546ed 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -222,6 +222,7 @@ def self.infer(klass, meth) RDL::Config.instance.use_comp_types = true RDL::Config.instance.number_mode = true @cur_meth = [klass, meth] + @var_cache = {} ast = get_ast(klass, meth) types = RDL::Globals.info.get(klass, meth, :type) if types == [] or types.nil? @@ -572,7 +573,7 @@ def self.tc(scope, env, e) when :complex, :rational # constants [env, RDL::Type::NominalType.new(e.children[0].class), [:+, :+]] when :int, :float - if RDL::Config.instance.number_mode && (e.type == :float) + if RDL::Config.instance.number_mode #&& (e.type == :float) [env, RDL::Type::NominalType.new(Integer), [:+, :+]] else [env, RDL::Type::SingletonType.new(e.children[0]), [:+, :+]] @@ -963,7 +964,6 @@ def self.tc(scope, env, e) eff = effect_union(eff, effi) block = [map_block_type, e_map_case] end - tres, effres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e) [envi, tres.canonical, effect_union(effres, eff) ] } @@ -1008,8 +1008,8 @@ def self.tc(scope, env, e) else # e.type == :or if tleft.val then [envleft, tleft, effleft] else [envright, tright, effright] end end - elsif e.type == :and && !(tleft == RDL::Globals.types[:bool] || tleft == RDL::Globals.types[:false]) - ## when :and and left is NOT false, then we always get back tright (or nil, which is a subtype of tright) + elsif e.type == :and && !(tleft == RDL::Globals.types[:bool] || tleft == RDL::Globals.types[:false] || tleft.is_a?(RDL::Type::VarType)) + ## when :and and left is NOT false or a variable, then we always get back tright (or nil, which is a subtype of tright) ## no equivalent for :or, because if left is nil, could still get right [envleft.merge(envright), tright, effect_union(effleft, effright)] else @@ -1380,7 +1380,7 @@ def self.to_type(val, as_key=false) when TrueClass, FalseClass, Class, Module RDL::Type::SingletonType.new(val) when Complex, Rational, Integer, Float - if RDL::Config.instance.number_mode && !(val.class == Integer) + if RDL::Config.instance.number_mode# && !(val.class == Integer) RDL::Type::NominalType.new(Integer) else RDL::Type::SingletonType.new(val) @@ -1614,7 +1614,36 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) trets = [] eff = [:+, :+] trecvs.each { |trecv| - ts, es = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) + if trecv.is_a?(RDL::Type::ChoiceType) + choice_hash = { } + errs = [] + trecv.choices.each { |num, t| + trecv.activate_all_connected(num) ## type check method call for this particular choice + begin + t = t.canonical + tarms = t.is_a?(RDL::Type::UnionType) ? t.types : [t] + tarms.each { |t| + ts, es = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) + choice_hash[num] = RDL::Type::UnionType.new(*ts).canonical + } + rescue StaticTypeError => err + errs << err + trecv.remove!(num) ## choice num failed to type check, remove it from choice type + end + } + trecv.deactivate_all_connected + if choice_hash.empty? + raise StaticTypeError, "Failed to type check call to method #{meth} for ChoiceType receiver #{trecv}. Errors received were: #{errs.each { |e| puts e }}" + elsif choice_hash.values.uniq.size == 1 + ts = [choice_hash.values[0]] ## only one type resulted, no need for ChoiceType + else + ts = [RDL::Type::ChoiceType.new(choice_hash, [trecv] + trecv.connecteds)] + end + else + ts, es = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) + end + + if es.nil? || (es.all? { |effect| effect.nil? }) ## could be multiple, because every time e is called, nil is added to effects ## should probably change default effect to be [:-, :-], but for now I want it like this, ## so I can easily see when a method has been used and its effect set to the default. @@ -1677,6 +1706,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, else # SingletonType(class) klass = env[:self].val end + puts "TRYING FOR KLASS #{klass}" ts, es = lookup(scope, klass.to_s, trecv.val, e) error :no_type_for_symbol, [trecv.val.inspect], e if ts.nil? return [ts, nil] ## TODO: not sure what to do hear about effect @@ -1780,7 +1810,12 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, elsif meth == :to_i ret_type = RDL::Globals.types[:integer] else - ret_type = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :ret, name: "ret") + if @var_cache.has_key?(e.object_id) ## cache is based on syntactic location of method call + ret_type = @var_cache[e.object_id] + else + ret_type = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :ret, name: "ret") + @var_cache[e.object_id] = ret_type + end end if block @@ -1824,7 +1859,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, when RDL::Type::DynamicType return [[trecv]] else - raise RuntimeError, "receiver type #{trecv} not supported yet, meth=#{meth}" + raise RuntimeError, "receiver type #{trecv} of kind #{trecv.class} not supported yet, meth=#{meth}" end trets = [] # all possible return types @@ -1840,7 +1875,8 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, else ts = filter_comp_types(ts, false) error :no_non_dep_types, [trecv, meth], e unless !ts.empty? - end + end + RDL::Type.expand_product(tactuals).each { |tactuals_expanded| # AT LEAST ONE of the possible intesection arms must match trets_tmp = [] @@ -1944,24 +1980,21 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, deferred_constraints += current_dcs if ts.size > 1 && tactuals_expanded.any? { |t| t.is_a?(RDL::Type::VarType) } ## need ChoiceTypes in this case - puts "DID WE MAKE IT HERE? WITH #{current_dcs}" + ## first convert constraints in current_dcs (which is of form [[t1, t2], [t3,t4]...]) ## into hash of form { t1 => t2, t3 => t4 }, combining multiple upper bounds into Union. constraints_hash = current_dcs.inject({}) do |hash, constraint| hash[constraint[0]] = RDL::Type::UnionType.new(hash[constraint[0]], constraint[1]).canonical hash end - puts "AHORA CONSTRAINTS_HASH IS #{constraints_hash}" + ## next, make the choice hashes and add them to arg_choices constraints_hash.each { |vartype, ubound| - puts "GOING ROUND WITH #{vartype} AND #{ubound}" arg_choices[vartype][choice_num] = ubound - puts "DOES IT HAVE SOMETHING NOW? #{arg_choices[vartype]}" } raise "Expected return type." unless current_ret ret_choice[choice_num] = RDL::Type::UnionType.new(current_ret, ret_choice[choice_num]).canonical - puts "HERE WITH #{arg_choices}" end end @@ -1981,22 +2014,27 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, apply_deferred_constraints(deferred_constraints, e) if !deferred_constraints.empty? else new_dcs = [] - all_args = [] + ct_args = [] arg_choices.each { |vartype, choice_hash| - ct = RDL::Type::ChoiceType.new(choice_hash) - new_dcs << [vartype, ct] - all_args << ct + if choice_hash.values.uniq.size == 1 + new_dcs << [vartype, choice_hash.values[0]] + else + t = RDL::Type::ChoiceType.new(choice_hash) + new_dcs << [vartype, t] + ct_args << t + end } raise "Expected non-empty return choice hash." if ret_choice.empty? - if ret_choice.values.uniq == 1 + if ret_choice.values.uniq.size == 1 new_ret = ret_choice.values[0] - all_cts.each { |ct| ct.add_connecteds(*all_args) } + ct_args.each { |ct| ct.add_connecteds(*ct_args) } else new_ret = RDL::Type::ChoiceType.new(ret_choice) - (all_cts+[new_ret]).each { |ct| ct.add_connecteds(new_ret, *all_args) } + (ct_args+[new_ret]).each { |ct| ct.add_connecteds(new_ret, *ct_args) } end trets = [new_ret] + apply_deferred_constraints(new_dcs, e) if !new_dcs.empty? end diff --git a/lib/rdl/types/choice.rb b/lib/rdl/types/choice.rb index 157f7351..291e8d89 100644 --- a/lib/rdl/types/choice.rb +++ b/lib/rdl/types/choice.rb @@ -1,7 +1,15 @@ module RDL::Type - class ChoiceType < VarType + class ChoiceType < Type + + class << self + alias :__new__ :new + end + ## variational typing! attr_accessor :choices, :connecteds + attr_accessor :ubounds # upper bounds this ChoiceType has been compared with using <= + attr_accessor :lbounds # lower bounds... + attr_reader :activated # a Type when one arm of this ChoiceTypes is activated, nil otherwise # [+ choices +] is a Hash, where the keys are the distinct numbers of every chocie, # and the values are the diferent Type choices. @@ -9,10 +17,14 @@ class ChoiceType < VarType # All connecteds should have the same choice numbers def initialize(choices, connecteds=[]) raise "Expected at least one choice." if choices.size == 0 - raise "Expected Hash, got #{choices}." unless choices.is_a?(Hash) && choices.keys.all? { |k| k.is_a?(Integer) } && choices.values.all { |v| v.is_a?(Type) } - raise "Expected Array of ChoiceTypes type." unless connecteds.is_a?(Array)&& connecteds.all? { |t| t.is_a?(ChoiceType) } + raise "Expected Hash, got #{choices}." unless choices.is_a?(Hash) && choices.keys.all? { |k| k.is_a?(Integer) } && choices.values.all? { |v| v.is_a?(Type) } + raise "Expected Array of ChoiceTypes, got #{connecteds}." unless connecteds.is_a?(Array)&& connecteds.all? { |t| t.is_a?(ChoiceType) } @choices = choices @connecteds = connecteds + @ubounds = [] + @lbounds = [] + @activated = nil + @hash = 101 + @choices.hash end def add_connecteds(*connecteds) @@ -22,9 +34,92 @@ def add_connecteds(*connecteds) } end + def <=(other) + return Type.leq(self, other) + end + def to_s - "~~ #{connecteds} ~~" + typs = [] + @choices.each { |choice, typ| + typs << (choice.to_s + " => " + typ.to_s) + } + "<< " + typs.join(", ") + " >>" + end + + def ==(other) # :nodoc: + return false if other.nil? + return (other.instance_of? ChoiceType) && (other.choices == @choices) ## include connecteds here? + end + + alias eql? == + + def remove!(choice, removed_hash: {}) + raise "Expected integer, got #{choice}." unless choice.is_a? Integer + removed = @choices.delete(choice) + removed_hash[self] ||= {} + removed_hash[self][choice] = removed + raise "Bounds violation after removing choice #{choice} type #{removed} from ChoiceType #{self}" unless check_bounds + @connecteds.each { |t| t.remove!(choice, removed_hash: removed_hash) if t.choices.has_key?(choice) } + end + + def check_bounds + return (@lbounds.all? { |lbound| lbound <= self }) && (@ubounds.all? { |ubound| self <= ubound }) + end + + def canonical + return @activated if @activated + @choices = @choices.transform_values { |v| v.canonical } + return @choices.values[0] if (@choices.values.uniq.size == 1) + self + end + + def activate(choice) + raise "No choice #{choice} for this ChoiceType." unless @choices.has_key?(choice) + @activated = @choices[choice] + end + + def deactivate + @activated = nil + end + + def activate_all_connected(choice) + activate(choice) + @connecteds.each { |c| c.activate(choice) } + end + + def deactivate_all_connected + @activated = nil + @connecteds.each { |c| c.deactivate } + end + + def instantiate(inst) + # Following the lead of Tuples and FHTs, this instantiate method will actually mutate self. + @choices.each { |choice, t| + @choices[choice] = t.instantiate(inst) + } + self + end + + def add_choices(choices) + raise "Expected Hash, got #{choices}" unless choices.is_a?(Hash) && choices.keys.all? { |k| k.is_a?(Integer) } && choices.values.all? { |t| t.is_a?(Type) } + choices.each { |num, t| + raise "Tried to add choice { #{num} => #{t} } to #{self}, but choice ##{num} already exists." if @choices.has_key?(num) + @choices[num] = t + } + end + + def copy + new_choices = {} + @choices.each { |num, t| new_choices[num] = t.copy } + new_connecteds = @connecteds.map { |c| RDL::Type::ChoiceType.new(c.choices.transform_values { |t| t.copy }) } + new_self = ChoiceType.new(new_choices, new_connecteds) + new_connecteds.each { |c| c.add_connecteds(*([new_self] + new_connecteds)) } + new_self + end + + def hash + @hash end - end + end end diff --git a/lib/rdl/types/intersection.rb b/lib/rdl/types/intersection.rb index f92673d3..29b6e8ca 100644 --- a/lib/rdl/types/intersection.rb +++ b/lib/rdl/types/intersection.rb @@ -54,7 +54,7 @@ def canonicalize! @types.map! { |t| t.canonical } for i in 0..(@types.length-1) for j in i+1..(@types.length-1) - next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType)) || (@types[j].is_a?(VarType)) + next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType)) || (@types[j].is_a?(VarType))|| @types[i].is_a?(ChoiceType) || @types[j].is_a?(ChoiceType) (@types[j] = nil; break) if Type.leq(@types[i], @types[j], nil, true, []) (@types[i] = nil) if Type.leq(@types[j], @types[i], nil, true, []) end diff --git a/lib/rdl/types/singleton.rb b/lib/rdl/types/singleton.rb index 146ec403..836f4021 100644 --- a/lib/rdl/types/singleton.rb +++ b/lib/rdl/types/singleton.rb @@ -11,7 +11,7 @@ class << self end def self.new(val) - if RDL::Config.instance.number_mode && val.is_a?(Numeric) && !val.is_a?(Integer) + if RDL::Config.instance.number_mode && val.is_a?(Numeric) #&& !val.is_a?(Integer) return RDL::Type::NominalType.new(Integer) end t = @@cache[val] diff --git a/lib/rdl/types/tuple.rb b/lib/rdl/types/tuple.rb index ffa4e20c..d76fb680 100644 --- a/lib/rdl/types/tuple.rb +++ b/lib/rdl/types/tuple.rb @@ -46,7 +46,7 @@ def match(other) def promote(t=nil) return false if @cant_promote - param = (@params.empty? && !t) ? RDL::Type::VarType.new(:t) : UnionType.new(*@params.map { |p| if p.is_a?(RDL::Type::SingletonType) then p.nominal else p end }, t) + param = (@params.empty? && !t) ? RDL::Type::VarType.new(:t) : UnionType.new(*@params.map { |p| if p.is_a?(RDL::Type::SingletonType) && !p.val.nil? then p.nominal else p end }, t) param = param.widen if RDL::Config.instance.promote_widen GenericType.new(RDL::Globals.types[:array], param) end diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 1f559a71..b55828c5 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -55,10 +55,16 @@ def vararg?; return false; end # [+ deferred_constraints +] is an Array<[Type, Type]>. When provided, instead of applying # constraints to VarTypes, we simply defer them by putting them in this array. # [+ no_constraint +] is a %bool indicating whether or not we should add to tuple/FHT constraints + # [+ ast +] is a parser expression, used for printing error messages when VarType constraints are violated. + # [+ propagate +] is a %bool indicating whether or not VarType constraints should be propagated. + # [+ new_cons +] is a set of all new contraints generated on VarTypes, which may be rolled back if they are + # from heuristic guesses. + # [+ removed_choices +] is a Hash> mapping ChoiceTypes to choices removed + # from that ChoiceType. These removals may be rolled back in certain cases. # if inst is nil, returns self <= other # if inst is non-nil and ileft, returns inst(self) <= other, possibly mutating inst to make this true # if inst is non-nil and !ileft, returns self <= inst(other), again possibly mutating inst - def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_constraint: false, ast: nil, propagate: false, new_cons: {}) + def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_constraint: false, ast: nil, propagate: false, new_cons: {}, removed_choices: {}) #propagate = false left = inst[left.name] if inst && ileft && left.is_a?(VarType) && !left.to_infer && inst[left.name] right = inst[right.name] if inst && !ileft && right.is_a?(VarType) && !right.to_infer && inst[right.name] @@ -68,6 +74,12 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con right = right.type if right.is_a? NonNullType left = left.canonical right = right.canonical + #puts "ABOUT TO TRY #{left} <= #{right}" + + return true if left.equal?(right) + + #left = left.nominal if left.is_a?(RDL::Type::SingletonType) && left.val.is_a?(Integer) && RDL::Config.instance.number_mode + #right = right.nominal if right.is_a?(RDL::Type::SingletonType) && right.val.is_a?(Integer) && RDL::Config.instance.number_mode # top and bottom return true if left.is_a? BotType @@ -84,45 +96,181 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con return left.name == right.name elsif left.is_a?(VarType) && left.to_infer && right.is_a?(VarType) && right.to_infer if deferred_constraints.nil? - left.add_ubound(right, ast, new_cons, propagate: propagate) unless (left.ubounds.any? { |t, loc| t == right } || left.equal?(right)) - right.add_lbound(left, ast, new_cons, propagate: propagate) unless (right.lbounds.any? { |t, loc| t == left } || right.equal?(left)) + left.add_ubound(right, ast, new_cons, propagate: propagate) unless (left.ubounds.any? { |t, loc| t == right || t.hash == right.hash } || left.equal?(right)) ## Added this last one for ChoiceTypes, because the ChoiceType can change but the hash does not. + right.add_lbound(left, ast, new_cons, propagate: propagate) unless (right.lbounds.any? { |t, loc| t == left || t.hash == left.hash } || right.equal?(left)) else deferred_constraints << [left, right] end return true elsif left.is_a?(VarType) && left.to_infer if deferred_constraints.nil? - left.add_ubound(right, ast, new_cons, propagate: propagate) unless (left.ubounds.any? { |t, loc| t == right } || left.equal?(right)) + left.add_ubound(right, ast, new_cons, propagate: propagate) unless (left.ubounds.any? { |t, loc| t == right || t.hash == right.hash } || left.equal?(right)) else deferred_constraints << [left, right] end return true elsif right.is_a?(VarType) && right.to_infer if deferred_constraints.nil? - right.add_lbound(left, ast, new_cons, propagate: propagate) unless (right.lbounds.any? { |t, loc| t == left } || right.equal?(left)) + right.add_lbound(left, ast, new_cons, propagate: propagate) unless (right.lbounds.any? { |t, loc| t == left || t.hash == left.hash } || right.equal?(left)) else deferred_constraints << [left, right] end return true end + ## choice types + if left.is_a?(ChoiceType) && right.is_a?(ChoiceType) + #puts "HERE 1 TRYING #{left} <= #{right}" + ## ChoiceTypes can't contain VarTypes to be inferred within them, so no need to worry about constraints here. + if left.connecteds.include?(right) + ## if left and right are connected, left <= right whenever each individual left choice is <= the corresponding right choice + left.choices.each { |choice, t| + return false unless right.choices.has_key? choice + return false unless t <= right.choices[choice] + } + return true + else + ## if left and right are not connected, each left choice must be <= all right choices + raise "Not currently supported." if (left.choices.values + right.choices.values).any? { |t| t.is_a?(VarType) } + lsafe_choices = [] + rsafe_choices = [] + left.choices.each { |lchoice, lt| + right.choices.each { |rchoice, rt| + if lt <= rt + lsafe_choices << lchoice unless lsafe_choices.include? lchoice + rsafe_choices << rchoice unless rsafe_choices.include? rchoice + end + } + } + if lsafe_choices.empty? + return false + else + ## There are some safe choices. Remove the unsafe ones and return true + left.choices.each { |num, _| left.remove!(num) unless lsafe_choices.include?(num) } + right.choices.each { |num, _| right.remove!(num) unless rsafe_choices.include?(num) } + return true + end + end + elsif left.is_a?(ChoiceType) || right.is_a?(ChoiceType) + #puts "HERE 2 TRYING #{left} <= #{right}" + if left.is_a?(ChoiceType) + main_ct = left + lct = true + else + main_ct = right + lct = false + end + to_remove = [] + ub_var_choices = Hash.new { |h, k| h[k] = {} } # Hash>. The keys are tactual arguments that are VarTypes. The values are choice hashes, to be turned into ChoiceTypes that will be upper bounds on the keys. + lb_var_choices = Hash.new { |h, k| h[k] = {} } # same as above, but values are lower bounds on keys. + main_ct.choices.each { |num, t| + new_dcs = [] + check = lct ? Type.leq(t, right, inst, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) : Type.leq(left, t, inst, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + if check + new_dcs.each { |t1, t2| + ub_var_choices[t1][num] = RDL::Type::UnionType.new(ub_var_choices[t1][num], t2).canonical if t1.is_a?(VarType) + lb_var_choices[t2][num] = RDL::Type::UnionType.new(lb_var_choices[t2][num], t1).canonical if t2.is_a?(VarType) +=begin + if t1.is_a?(VarType) + raise "Not currently supported, for var types: #{t1} and #{t2}." if t2.is_a?(VarType) + ub_var_choices[t1][num] = RDL::Type::UnionType.new(ub_var_choices[t1][num], t2).canonical + else + raise "Expected VarType, got #{t2}" unless t2.is_a?(VarType) + lb_var_choices[t2][num] = RDL::Type::UnionType.new(lb_var_choices[t2][num], t1).canonical + end +=end + } + else + to_remove << num + end + } + + if to_remove.size == main_ct.choices.size + return false + else + to_remove.each { |num| main_ct.remove!(num) } + if !((lb_var_choices.empty?) && (ub_var_choices.empty?)) + all_cts = [] + ub_var_choices.each { |vartype, choice_hash| + if choice_hash.values.uniq.size == 1 + RDL::Type::Type.leq(vartype, choice_hash.values[0], nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + else + t = RDL::Type::ChoiceType.new(choice_hash) + RDL::Type::Type.leq(vartype, t, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + all_cts << t + end + } + + lb_var_choices.each { |vartype, choice_hash| + if choice_hash.values.uniq.size == 1 + RDL::Type::Type.leq(choice_hash.values[0], vartype, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + else + t = RDL::Type::ChoiceType.new(choice_hash) + RDL::Type::Type.leq(t, vartype, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + all_cts << t + end + } + (all_cts + [main_ct]).each { |ct| ct.add_connecteds(*(all_cts+ [main_ct])) } + end + end + return true + end + + +=begin + to_remove << num unless Type.leq(t, right, inst, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices)#t <= right + ## TODO: actually use deferred_constraints or new_cons here + } + if to_remove.size == left.choices.size + ## none of the choices match, so left is not a subtype of right + return false + else + to_remove.each { |num| left.remove!(num) } + return true + end + #to_remove.each { |num| left.remove!(num) } + #return !left.choices.empty? +=end + +=begin + elsif right.is_a?(ChoiceType) + puts "HERE 3 TRYING #{left} <= #{right}" + # VarTypes would have been handled by now + to_remove = [] + right.choices.each { |num, t| + to_remove << num unless Type.leq(left, t, inst, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices)#left <= t + } + if to_remove.size == right.choices.size + return false + else + to_remove.each { |num| right.remove!(num) } + return true + end + #to_remove.each { |num| right.remove!(num) } + #return !right.choices.empty? + end +=end # union - return left.types.all? { |t| leq(t, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } if left.is_a?(UnionType) + return left.types.all? { |t| leq(t, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } if left.is_a?(UnionType) if right.instance_of?(UnionType) right.types.each { |t| # return true at first match, updating inst accordingly to first succeessful match new_inst = inst.dup unless inst.nil? - if leq(left, t, new_inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + #new_rc = {} + if leq(left, t, new_inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices)#new_rc) inst.update(new_inst) unless inst.nil? return true + else + ## if a particular arm doesn't apply, undo the + #removed_choices.each { | end } return false end # intersection - return right.types.all? { |t| leq(left, t, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } if right.instance_of?(IntersectionType) - return left.types.any? { |t| leq(t, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } if left.is_a?(IntersectionType) + return right.types.all? { |t| leq(left, t, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } if right.instance_of?(IntersectionType) + return left.types.any? { |t| leq(t, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } if left.is_a?(IntersectionType) # nominal @@ -150,8 +298,13 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con lklass = Object base_inst = { self: RDL::Globals.types[:object] } when SingletonType - lklass = left.val.class base_inst = { self: left } + if left.val.class == Class + lklass = left.val + klass_lookup = "[s]"+lklass.to_s + else + lklass = left.val.class + end else if (left == RDL::Globals.types[:array]) left = RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Globals.types[:bot]) @@ -166,59 +319,146 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con base_inst = { self: left } end end - + klass_lookup = lklass.to_s unless klass_lookup + right.methods.each_pair { |m, t| - return false unless lklass.method_defined?(m) || RDL::Typecheck.lookup({}, lklass.to_s, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) ## Added the second condition because Rails lazily defines some methods. + return false unless lklass.method_defined?(m) || RDL::Typecheck.lookup({}, klass_lookup, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) ## Added the second condition because Rails lazily defines some methods. types, _ = RDL::Typecheck.lookup({}, lklass.to_s, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) + + if RDL::Config.instance.use_comp_types + types = RDL::Typecheck.filter_comp_types(types, true) + else + types = RDL::Typecheck.filter_comp_types(types, false) + end + + ret = types.nil? #false if types - #non_dep_types = RDL::Typecheck.filter_comp_types(types, false) - #raise "Need non-dependent types for method #{m} of class #{lklass} in order to use a structural type." if non_dep_types.empty? - return false unless types.any? { |tlm| + choice_num = 0 + ub_var_choices = Hash.new { |h, k| h[k] = {} } # Hash>. The keys are tactual arguments that are VarTypes. The values are choice hashes, to be turned into ChoiceTypes that will be upper bounds on the keys. + lb_var_choices = Hash.new { |h, k| h[k] = {} } # Hash>. The keys are tactual arguments that are VarTypes. The values are choice hashes, to be turned into ChoiceTypes that will be lower bounds on the keys. + types.each { |tlm| + choice_num += 1 blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } ## In this case, need to actually evaluate the ComputedType. ## Going to do this using the receiver `left` and the args from `t` ## If subtyping holds for this, then we know `left` does indeed have a method of the relevant type. - computed_tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) - leq(computed_tlm.instantiate(base_inst), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) - else - leq(tlm.instantiate(base_inst), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) + end + new_dcs = [] + if leq(tlm.instantiate(base_inst), t, nil, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + ret = true + if types.size > 1 && !new_dcs.empty? ## method has intersection type, and vartype constraints were created + new_dcs.each { |t1, t2| + ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical if t1.is_a?(VarType) + lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical if t2.is_a?(VarType) +=begin + if t1.is_a?(VarType) + raise "Not currently supported, for var types: #{t1} and #{t2}." if t2.is_a?(VarType) ## This shouldn't happen, because there shouldn't be intersection types with VarTypes in them. + ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical + else + raise "Expected VarType, got #{t2}" unless t2.is_a?(VarType) + lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical + end +=end + } + else + new_dcs.each { |t1, t2| RDL::Type::Type.leq(t1, t2, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } + end + end + } + + if !((lb_var_choices.empty?) && (ub_var_choices.empty?)) + all_cts = [] + ub_var_choices.each { |vartype, choice_hash| + if choice_hash.values.uniq.size == 1 + RDL::Type::Type.leq(vartype, choice_hash.values[0], nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + else + t = RDL::Type::ChoiceType.new(choice_hash) + RDL::Type::Type.leq(vartype, t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + all_cts << t + end + } + + lb_var_choices.each { |vartype, choice_hash| + if choice_hash.values.uniq.size == 1 + RDL::Type::Type.leq(choice_hash.values[0], vartype, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + else + t = RDL::Type::ChoiceType.new(choice_hash) + RDL::Type::Type.leq(t, vartype, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + all_cts << t + end + } + all_cts.each { |ct| ct.add_connecteds(*all_cts) } + end + end + return ret if !ret ## false if at least one type didn't match for this method + } + return true + end + + +=begin + (raise errs[0] if !errs.empty? ; return false ) unless types.any? { |tlm| ## raising err[0] if it exist, but really could be any error we raise + blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] + if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } + ## In this case, need to actually evaluate the ComputedType. + ## Going to do this using the receiver `left` and the args from `t` + ## If subtyping holds for this, then we know `left` does indeed have a method of the relevant type. + tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) + end + begin + attempted_cons = {} + ret = leq(tlm.instantiate(base_inst), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: attempted_cons, removed_choices: removed_choices) p # inst above is nil because the method types inside the class and # inside the structural type have an implicit quantifier on them. So # even if we're allowed to instantiate type variables we can't do that # inside those types + new_cons.merge(attempted_cons) { |key, orig_val, new_val| orig_val | new_val } + ret + rescue RDL::Typecheck::StaticTypeError => err ## could be raised when propagating constraints + RDL::Typecheck.undo_constraints(attempted_cons) + errs << err + false end } - end + end } return true end + if (left.is_a?(SingletonType) && left.val.class == Class) && right.is_a?(StructuralType) lklass = left.val right.methods.each_pair { |m, t| return false unless lklass.methods.include?(m) || RDL::Typecheck.lookup({}, "[s]"+lklass.to_s, m, nil, make_unknown: false) types, _ = RDL::Typecheck.lookup({}, "[s]"+lklass.to_s, m, nil, make_unknown: false) if types + errs = [] return false unless types.any? { |tlm| blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] - if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } - computed_tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) - leq(computed_tlm, t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) - else - leq(tlm, t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) - end + tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } + begin + attempted_cons = {} + ret = leq(tlm, t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: attempted_cons, removed_choices: removed_choices) + new_cons.merge(attempted_cons) { |key, orig_val, new_val| orig_val | new_val } + ret + rescue RDL::Typecheck::StaticTypeError => err ## could be raised when propagating constraints + RDL::Typecheck.undo_constraints(attempted_cons) + errs << err + false + end } end } return true end - +=end # singleton return left.val == right.val if left.is_a?(SingletonType) && right.is_a?(SingletonType) return true if left.is_a?(SingletonType) && left.val.nil? # right cannot be a SingletonType due to above conditional - return leq(left.nominal, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) if left.is_a?(SingletonType) # fall through case---use nominal type for reasoning + return leq(left.nominal, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) if left.is_a?(SingletonType) # fall through case---use nominal type for reasoning # generic if left.is_a?(GenericType) && right.is_a?(GenericType) @@ -231,11 +471,11 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con return variance.zip(left.params, right.params).all? { |v, tl, tr| case v when :+ - leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) when :- - leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) when :~ - leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) && leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) && leq(tr, tl, inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) else raise RuntimeError, "Unexpected variance #{v}" # shouldn't happen end @@ -260,24 +500,96 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con return false unless klass.method_defined?(meth) || RDL::Typecheck.lookup({}, klass.to_s, meth, nil, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) ## Added the second condition because Rails lazily defines some methods. types, _ = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) end + + if RDL::Config.instance.use_comp_types + types = RDL::Typecheck.filter_comp_types(types, true) + else + types = RDL::Typecheck.filter_comp_types(types, false) + end + + ret = types.nil? if types - + choice_num = 0 + ub_var_choices = Hash.new { |h, k| h[k] = {} } # Hash>. The keys are tactual arguments that are VarTypes. The values are choice hashes, to be turned into ChoiceTypes that will be upper bounds on the keys. + lb_var_choices = Hash.new { |h, k| h[k] = {} } # Hash>. The keys are tactual arguments that are VarTypes. The values are choice hashes, to be turned into ChoiceTypes that will be lower bounds on the keys. + types.each { |tlm| + choice_num += 1 + blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] + tlm = RDL::Typecheck.compute_types(tlm, klass, left, t.args) if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } + new_dcs = [] + if leq(tlm.instantiate(base_inst.merge({ self: left})), t, nil, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + ret = true + if types.size > 1 && !new_dcs.empty? ## method has intersection type, and vartype constraints were + new_dcs.each { |t1, t2| + ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical if t1.is_a?(VarType) + lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical if t2.is_a?(VarType) +=begin + if t1.is_a?(VarType) + raise "Not currently supported, for var types: #{t1} and #{t2}." if t2.is_a?(VarType) ## This shouldn't happen, because there shouldn't be intersection types with VarTypes in them. + ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical + else + raise "Expected VarType, got #{t2}" unless t2.is_a?(VarType) + lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical + end +=end + } + else + new_dcs.each { |t1, t2| RDL::Type::Type.leq(t1, t2, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } + end + end + } + if !((lb_var_choices.empty?) && (ub_var_choices.empty?)) + all_cts = [] + ub_var_choices.each { |vartype, choice_hash| + if choice_hash.values.uniq.size == 1 + RDL::Type::Type.leq(vartype, choice_hash.values[0], nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + else + t = RDL::Type::ChoiceType.new(choice_hash) + RDL::Type::Type.leq(vartype, t, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + all_cts << t + end + } + + lb_var_choices.each { |vartype, choice_hash| + if choice_hash.values.uniq.size == 1 + RDL::Type::Type.leq(choice_hash.values[0], vartype, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + else + t = RDL::Type::ChoiceType.new(choice_hash) + RDL::Type::Type.leq(t, vartype, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + all_cts << t + end + } + all_cts.each { |ct| ct.add_connecteds(*all_cts) } + end + end + return ret if !ret ## false if at least one type didn't match for this method + } + return true + end + + +=begin + errs = [] return false unless types.any? { |tlm| blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] - if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } - ## In this case, need to actually evaluate the ComputedType. - ## Going to do this using the receiver `left` and the args from `t` - ## If subtyping holds for this, then we know `left` does indeed have a method of the relevant type. - computed_tlm = RDL::Typecheck.compute_types(tlm, klass, left, t.args) - leq(computed_tlm.instantiate(base_inst.merge({ self: left })), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) - else - leq(tlm.instantiate(base_inst.merge({ self: left})), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + tlm = RDL::Typecheck.compute_types(tlm, klass, left, t.args) if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } + begin + attempted_cons = {} + ret = leq(tlm.instantiate(base_inst.merge({ self: left})), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: attempted_cons, removed_choices: removed_choices) + new_cons.merge(attempted_cons) { |key, orig_val, new_val| orig_val | new_val } + ret + rescue RDL::Typecheck::StaticTypeError => err + RDL::Typecheck.undo_constraints(attempted_cons) + errs << err + false end } end } return true end +=end + # Note we do not allow raw subtyping leq(GenericType, NominalType, ...) # method @@ -302,15 +614,15 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end end return false unless left.args.size == right.args.size - return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr.instantiate(inst), tl.instantiate(inst), inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } # contravariance + return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr.instantiate(inst), tl.instantiate(inst), inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } # contravariance #return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr.instantiate(inst), tl.instantiate(inst), inst, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } # contravariance if left.block && right.block - return false unless leq(right.block.instantiate(inst), left.block.instantiate(inst), inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # contravariance + return false unless leq(right.block.instantiate(inst), left.block.instantiate(inst), inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) # contravariance elsif (left.block && !left.block.is_a?(VarType) && !right.block) || (right.block && !right.block.is_a?(VarType) && !left.block) return false # one has a block and the other doesn't end - return leq(left.ret.instantiate(inst), right.ret.instantiate(inst), inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # covariance + return leq(left.ret.instantiate(inst), right.ret.instantiate(inst), inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) # covariance end return true if left.is_a?(MethodType) && right.is_a?(NominalType) && right.name == 'Proc' @@ -321,7 +633,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # allow width subtyping - methods of right have to be in left, but not vice-versa return right.methods.all? { |m, t| # in recursive call set inst to nil since those method types have implicit quantifier - left.methods.has_key?(m) && leq(left.methods[m], t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + left.methods.has_key?(m) && leq(left.methods[m], t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } end # Note we do not allow a structural type to be a subtype of a nominal type or generic type, @@ -331,7 +643,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(TupleType) && right.is_a?(TupleType) # Tuples are immutable, so covariant subtyping allowed return false unless left.params.length == right.params.length - return false unless left.params.zip(right.params).all? { |lt, rt| leq(lt, rt, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } + return false unless left.params.zip(right.params).all? { |lt, rt| leq(lt, rt, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } # subyping check passed left.ubounds << right unless no_constraint right.lbounds << left unless no_constraint @@ -340,7 +652,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(TupleType) && right.is_a?(GenericType) && right.base == RDL::Globals.types[:array] # TODO !ileft and right carries a free variable return false unless left.promote! - return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # recheck for promoted type + return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) # recheck for promoted type end # finite hash @@ -354,10 +666,10 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con return false if tl.is_a?(OptionalType) && !tr.is_a?(OptionalType) # optional left, required right not allowed, since left may not have key tl = tl.type if tl.is_a? OptionalType tr = tr.type if tr.is_a? OptionalType - return false unless leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + return false unless leq(tl, tr, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) right_elts.delete k else - return false unless right.rest && leq(tl, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + return false unless right.rest && leq(tl, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) end } right_elts.each_pair { |k, t| @@ -365,7 +677,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con } unless left.rest.nil? # If left has optional stuff, right needs to accept it - return false unless !(right.rest.nil?) && leq(left.rest, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) + return false unless !(right.rest.nil?) && leq(left.rest, right.rest, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) end left.ubounds << right unless no_constraint right.lbounds << left unless no_constraint @@ -374,7 +686,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if left.is_a?(FiniteHashType) && right.is_a?(GenericType) && right.base == RDL::Globals.types[:hash] # TODO !ileft and right carries a free variable return false unless left.promote! - return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) # recheck for promoted type + return leq(left, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) # recheck for promoted type end ## precise string diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index 8811876a..7929fea2 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -46,7 +46,7 @@ def canonicalize! # for any type such that a supertype is already in ts, set its position to nil for i in 0..(@types.length-1) for j in (i+1)..(@types.length-1) - next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType) && @types[i].to_infer) || (@types[j].is_a?(VarType) && @types[j].to_infer) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union + next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType) && @types[i].to_infer) || (@types[j].is_a?(VarType) && @types[j].to_infer) || @types[i].is_a?(ChoiceType) || @types[j].is_a?(ChoiceType) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union #next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType)) || (@types[j].is_a?(VarType)) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union (@types[i] = nil; break) if Type.leq(@types[i], @types[j], {}, true, []) (@types[j] = nil) if Type.leq(@types[j], @types[i], {}, true, []) diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index d958da2d..3c924cba 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -4,6 +4,7 @@ class VarType < Type attr_accessor :lbounds, :ubounds, :solution @@cache = {} + @@print_XXX = false class << self alias :__new__ :new @@ -30,6 +31,7 @@ def self.new(name_or_hash) def initialize(name_or_hash) if name_or_hash.is_a?(Symbol) || name_or_hash.is_a?(String) + raise "weird" if name_or_hash.to_s == "expression" @name = name_or_hash @to_infer = false elsif name_or_hash.is_a?(Hash) @@ -64,7 +66,7 @@ def add_and_propagate_upper_bound(typ, ast, new_cons = {}) if lower_t.is_a?(VarType) lower_t.add_and_propagate_upper_bound(typ, ast, new_cons) unless lower_t.ubounds.any? { |t, _| t == typ } else - if typ.is_a?(VarType) + if typ.is_a?(VarType) && !typ.lbounds.any? { |t, _| t == lower_t } new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:lower, lower_t, ast]] : [[:lower, lower_t, ast]] end unless RDL::Type::Type.leq(lower_t, typ, {}, false, ast: ast, no_constraint: true, propagate: true, new_cons: new_cons) @@ -93,10 +95,12 @@ def add_and_propagate_lower_bound(typ, ast, new_cons = {}) if upper_t.is_a?(VarType) upper_t.add_and_propagate_lower_bound(typ, ast, new_cons) unless upper_t.lbounds.any? { |t, _| t == typ } else - if typ.is_a?(VarType) + if typ.is_a?(VarType) && !typ.ubounds.any? { |t, _| t == upper_t } new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:upper, upper_t, ast]] : [[:upper, upper_t, ast]] end unless RDL::Type::Type.leq(typ, upper_t, {}, false, ast: ast, no_constraint: true, propagate: true, new_cons: new_cons) + #puts "FAILED" + # TZInfo::DataSource <= { [s]TZInfo::DataSource#get ret: ret }. d1 = ast.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") d2 = a.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{typ} <= #{upper_t} generated during inference.\n #{d1}\n #{d2}") @@ -106,23 +110,27 @@ def add_and_propagate_lower_bound(typ, ast, new_cons = {}) end def add_ubound(typ, ast, new_cons = {}, propagate: false) + raise "ABOUT TO ADD UBOUND #{self} <= #{typ}" if typ.is_a?(VarType) && !typ.to_infer + #typ = typ.canonical if propagate add_and_propagate_upper_bound(typ, ast, new_cons) - else + elsif !@ubounds.any? { |t, a| t == typ } #puts "1. About to add upper bound #{self} <= #{typ}" new_cons[self] = new_cons[self] ? new_cons[self] | [[:upper, typ, ast]] : [[:upper, typ, ast]] - @ubounds << [typ, ast] unless @ubounds.any? { |t, a| t == typ } + @ubounds << [typ, ast] #unless @ubounds.any? { |t, a| t == typ } end end def add_lbound(typ, ast, new_cons = {}, propagate: false) + raise "ABOUT TO ADD LBOUND #{typ} <= #{self}" if typ.is_a?(VarType) && !typ.to_infer + #typ = typ.canonical if propagate add_and_propagate_lower_bound(typ, ast, new_cons) - else + elsif !@lbounds.any? { |t, a| t == typ } #puts "2. About to add lower bound #{typ} <= #{self}" #raise "blah" if typ.to_s == "Array" new_cons[self] = new_cons[self] ? new_cons[self] | [[:lower, typ, ast]] : [[:lower, typ, ast]] - @lbounds << [typ, ast] unless @lbounds.any? { |t, a| t == typ } + @lbounds << [typ, ast] #unless @lbounds.any? { |t, a| t == typ } end end @@ -131,6 +139,7 @@ def to_s # :nodoc: if @solution return @solution.to_s else + return "XXX" if @@print_XXX return "{ #{@cls}##{@meth} #{@category}: #{@name} }" end else @@ -186,5 +195,13 @@ def copy self end + def self.print_XXX! + @@print_XXX = true + end + + def self.no_print_XXX! + @@print_XXX = false + end + end end diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index ec31a6b9..6258c56f 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -126,8 +126,9 @@ def Array.plus_input(targs) when RDL::Type::TupleType return targs[0] when RDL::Type::GenericType, RDL::Type::VarType - parse_string = defined? Rails ? "Array or ActiveRecord::Relation" : "Array" - return RDL::Globals.parser.scan_str "#T #{parse_string}" + parse_string = defined?(Rails) ? "Array or ActiveRecord::Relation" : "Array" + x = RDL::Globals.parser.scan_str "#T #{parse_string}" + x else RDL::Globals.types[:array] end @@ -145,7 +146,7 @@ def Array.plus_output(trec, targs) promoted = RDL.type_cast(targs[0], "RDL::Type::TupleType", force: true).promote param_union = RDL::Type::UnionType.new(promoted.params[0], trec.params[0]) return RDL::Type::GenericType.new(trec.base, param_union) - when RDL::Type::GenericType + when RDL::Type::GenericType, RDL::Type::VarType return RDL::Globals.parser.scan_str "#T Array" else ## targs[0] should just be array here @@ -548,6 +549,7 @@ def Array.reverse_output(trec) RDL.type :Array, :last, '(Integer) -> Array' RDL.type :Array, :member?, '(u) -> %bool', effect: [:+, :+] RDL.type :Array, :length, '() -> Integer', effect: [:+, :+] +RDL.type :Array, :pack, "(String) -> String" RDL.type :Array, :permutation, '(?Integer) -> Enumerator' RDL.type :Array, :permutation, '(?Integer) { (Array) -> %any } -> Array' RDL.type :Array, :pop, '(Integer) -> Array' diff --git a/lib/types/core/enumerable.rb b/lib/types/core/enumerable.rb index 74a6d4ae..add62233 100644 --- a/lib/types/core/enumerable.rb +++ b/lib/types/core/enumerable.rb @@ -54,9 +54,9 @@ RDL.type :Enumerable, :max, '(Integer) -> Array' RDL.type :Enumerable, :max, '(Integer) { (t, t) -> Integer } -> Array' RDL.type :Enumerable, :max_by, '() -> Enumerator' -RDL.type :Enumerable, :max_by, '() { (t, t) -> Integer } -> t' +RDL.type :Enumerable, :max_by, '() { (t) -> Integer } -> t' RDL.type :Enumerable, :max_by, '(Integer) -> Enumerator' -RDL.type :Enumerable, :max_by, '(Integer) { (t, t) -> Integer } -> Array' +RDL.type :Enumerable, :max_by, '(Integer) { (t) -> Integer } -> Array' RDL.rdl_alias :Enumerable, :member?, :include? RDL.type :Enumerable, :min, '() -> t' RDL.type :Enumerable, :min, '() { (t, t) -> Integer } -> t' diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 3e6ca654..4f4325bf 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -257,7 +257,8 @@ def Hash.delete_output(trec, targs, block) RDL.type :Hash, :each_value, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), any_or_v(trec))``' RDL.type :Hash, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) -> ``output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true)``' -RDL.type :Hash, :fetch, '(``any_or_k(trec)``, u) -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' +#RDL.type :Hash, :fetch, '(``any_or_k(trec)``, u) -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' +RDL.type :Hash, :fetch, '(``any_or_k(trec)``, ``targs[1] ? targs[1] : RDL::Globals.types[:top]``) -> ``RDL::Type::UnionType.new(targs[1] ? targs[1] : RDL::Globals.types[:top], output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``RDL::Type::OptionalType.new(any_or_k(trec))``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :member?, '(%any) -> ``output_type(trec, targs, :member?, "%bool")``' RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``', effect: [:+, :+] diff --git a/lib/types/core/string.rb b/lib/types/core/string.rb index d9c7226f..567090cd 100644 --- a/lib/types/core/string.rb +++ b/lib/types/core/string.rb @@ -73,7 +73,7 @@ def String.string_promote!(trec) RDL.type :String, :initialize, '(?String str) -> self new_str' RDL.type :String, 'self.try_convert', '(Object obj) -> String or nil new_string' -RDL.type :String, :%, '(x) -> ``output_type(trec, targs, :%, "String")``' +RDL.type :String, :%, '(``targs[0]``) -> ``output_type(trec, targs, :%, "String")``' RDL.type :String, :*, '(Numeric) -> ``output_type(trec, targs, :*, "String")``' def String.plus_output(trec, targs) @@ -216,7 +216,8 @@ def String.clear_output(trec) RDL.type :String, :gsub, '(Regexp or String, String) -> ``output_type(trec, targs, :gsub, "String")``', wrap: false RDL.type :String, :gsub, '(Regexp or String) -> ``output_type(trec, targs, :gsub, "String")``' RDL.type :String, :gsub!, '(Regexp or String, String) -> ``string_promote!(trec)``', wrap: false -RDL.type :String, :gsub!, '(Regexp or String) {(?String) -> %any } -> ``string_promote!(trec)``', wrap: false +RDL.type :String, :gsub!, '(Regexp or String) {(String) -> %any } -> ``string_promote!(trec)``', wrap: false +RDL.type :String, :gsub!, '(Regexp or String) {() -> %any } -> ``string_promote!(trec)``', wrap: false RDL.type :String, :gsub!, '(Regexp or String) -> ``string_promote!(trec); RDL::Type::NominalType.new(Enumerator)``', wrap: false RDL.type :String, :hash, '() -> Integer' RDL.type :String, :hex, '() -> ``output_type(trec, targs, :getbyte, "Integer")``' @@ -332,6 +333,9 @@ def String.mutate_output(trec, meth) RDL.type :String, :rstrip, '() -> ``output_type(trec, targs, :rstrip, "String")``' RDL.type :String, :rstrip!, '() -> ``lrstrip_output(trec, :rstrip!)``' RDL.type :String, :scan, '(Regexp or String) -> ``output_type(trec, targs, :scan, "Array>")``', wrap: false # :String, Can't wrap or screws up last_match +RDL.type :String, :scan, '(Regexp or String) {() -> %any} -> ``output_type(trec, targs, :scan, "Array>")``', wrap: false +RDL.type :String, :scan, '(Regexp or String) {(String) -> %any} -> ``output_type(trec, targs, :scan, "Array>")``', wrap: false +RDL.type :String, :scan, '(Regexp or String) {(String, String) -> %any} -> ``output_type(trec, targs, :scan, "Array>")``', wrap: false RDL.type :String, :scan, '(Regexp or String) {(*String) -> %any} -> ``output_type(trec, targs, :scan, "Array>")``', wrap: false RDL.type :String, :scrub, '(?String) -> ``output_type(trec, targs, :scrub, "String")``' RDL.type :String, :scrub, '(?String) {(%any) -> %any} -> String' From 5b98c1d8c4cadc7a58b0fcd686cc3e1786f396a0 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Tue, 14 Jan 2020 17:08:39 -0500 Subject: [PATCH 012/124] updates for dynamic type collection --- lib/rdl.rb | 1 + lib/rdl/boot_rails.rb | 1 + lib/rdl/class_indexer.rb | 37 +++++++++-- lib/rdl/config.rb | 78 ++++++++++++++++++++++- lib/rdl/constraint.rb | 56 +++++++---------- lib/rdl/heuristics.rb | 9 ++- lib/rdl/typecheck.rb | 108 +++++++++++++++++++++++--------- lib/rdl/types/intersection.rb | 1 - lib/rdl/types/type.rb | 6 +- lib/rdl/types/var.rb | 2 +- lib/rdl/wrap.rb | 109 ++++++++++++++++++++++++++++++--- lib/types/core/array.rb | 4 +- lib/types/core/basic_object.rb | 2 +- lib/types/core/enumerable.rb | 5 +- lib/types/core/hash.rb | 13 ++-- lib/types/core/io.rb | 2 +- lib/types/core/kernel.rb | 9 ++- lib/types/core/set.rb | 3 +- lib/types/core/string.rb | 4 +- lib/types/core/time.rb | 2 +- lib/types/core/yaml.rb | 5 ++ 21 files changed, 355 insertions(+), 102 deletions(-) diff --git a/lib/rdl.rb b/lib/rdl.rb index 486f3b19..a9a2c8b3 100644 --- a/lib/rdl.rb +++ b/lib/rdl.rb @@ -1,5 +1,6 @@ # This wrapper file allows us to completely disable RDL in certain modes. if defined?(Rails) + require 'active_record' if !defined?(ActiveRecord) require 'rdl/boot_rails' else require 'rdl/boot' diff --git a/lib/rdl/boot_rails.rb b/lib/rdl/boot_rails.rb index 39d32cc5..3b131d45 100644 --- a/lib/rdl/boot_rails.rb +++ b/lib/rdl/boot_rails.rb @@ -17,3 +17,4 @@ def self.params_type(typs); end else raise RuntimeError, "Don't know what to do in Rails environment #{Rails.env}" end + diff --git a/lib/rdl/class_indexer.rb b/lib/rdl/class_indexer.rb index 2cc41785..a8c0cd33 100644 --- a/lib/rdl/class_indexer.rb +++ b/lib/rdl/class_indexer.rb @@ -11,6 +11,18 @@ def initialize @class_list = Hash.new { |h,k| h[k] = [] } end + def get_const_name(ast) + if ast.nil? + ast + elsif ast.type == :const + inner = ast.children[1].to_s + outer = get_const_name(ast.children[0]) + outer.nil? ? inner : outer + "::" + inner + else + raise "unexpected const ast #{ast}" + end + end + def reset_class @current_class = "main" end @@ -20,7 +32,7 @@ def add_method(method_name, line_num) end def on_class(node) - class_name = node.children[0].children[1].to_s + class_name = get_const_name(node.children[0])#node.children[0].children[1].to_s if @current_class == "main" @current_class = class_name @@ -38,7 +50,7 @@ def on_class(node) end def on_module(node) - module_name = node.children[0].children[1].to_s + module_name = get_const_name(node.children[0])#node.children[0].children[1].to_s if @current_class == "main" @current_class = module_name @@ -49,17 +61,32 @@ def on_module(node) node.children.each { |c| process(c) } - @current_class.sub!("::"+module_name, "") + if @current_class.include?("::") + @current_class.sub!("::"+module_name, "") + else + reset_class + end end def on_sclass(node) - raise "Not currently supported." unless node.children[0].type == :self - + #raise "Not currently supported." unless (node.children[0].type == :self) || (node.children[0].loc.expression.source == @current_class) +=begin @current_class.prepend("[s]") node.children.each { |c| process(c) } @current_class.sub!("[s]", "") +=end + if node.children[0].type == :self + @current_class.prepend("[s]") + node.children.each { |c| process(c) } + @current_class.sub!("[s]", "") + else + old_class = @current_class + @current_class = "[s]" + node.children[0].loc.expression.source + node.children.each { |c| process(c) } + @current_class = old_class + end end # Instance methods diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index 0723ad66..f3715ee9 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -15,6 +15,7 @@ def initialize @gather_stats = false @report = false # if this is enabled by default, modify @at_exit_installed @guess_types = [] # same as above + @get_types = [] # Array>: list of class/method name pairs to collect type data for @at_exit_installed = false @weak_update_promote = false @promote_widen = false @@ -47,6 +48,11 @@ def guess_types=(val) @guess_types = val end + def get_types + install_at_exit + return @get_types + end + def add_nowrap(*klasses) klasses.each { |klass| @nowrap.add klass.to_s.to_sym @@ -190,6 +196,50 @@ def do_report end end + def do_get_meth_type(klass, meth, the_meth) + puts "GETTING METH TYPE FOR #{klass} AND #{meth}" + params = the_meth.parameters + param_types = params.keep_if { |p| p.size == 2 }.to_h + param_names = [] + params.each_with_index { |param, i| + kind, name = param + ## TODO: Differentiate based on kind here? + ## TODO: Anything with block here? + param_names << name if name + } + otypes = RDL::Globals.info.get(klass, meth, :otype) if RDL::Globals.info.has?(klass, meth, :otype) # observed types + return if otypes.nil? + #otargs = [] + otargs = {} + otret = RDL::Globals.types[:bot] + otblock = false + binding_meth_name = "RDL_#{klass}_#{(meth.hash + meth.to_s.hash).abs}".gsub("::", "__").gsub("[s]", "singleton_") + otypes.each { |ot| + binds_hash = RDL.send(binding_meth_name, *ot[:args]) + binds_hash.each { |param_name, typ| + if param_types[param_name] == :rest + raise "Expected Array of classes, got #{typ}" unless typ.is_a?(Array) + new_typ = RDL::Type::UnionType.new(*typ).canonical + else + new_typ = typ.is_a?(RDL::Type::Type) ? typ : RDL::Wrap.val_to_type(typ) ## may not be a Type for default arguments + end + otargs[param_name] = otargs[param_name] ? RDL::Type::UnionType.new(otargs[param_name], new_typ).canonical : new_typ + } + otret = RDL::Type::UnionType.new(otret, ot[:ret]).canonical if defined? otret + otblock = otblock || ot[:block] + } + require 'method_source' + otargs.each { |param, typ| if param_types[param] == :rest then otargs[param] = RDL::Type::VarargType.new(typ) end } + printed_args = "" + otargs.each { |param, typ| printed_args << "#{param} => #{typ}\n" } + #otargs.transform_values { |v| v.to_s }.to_s + CSV.open("#{File.basename(Dir.getwd)}_observed_types.csv", "a+") { |csv| + csv << [klass.to_s, meth.to_s, param_names.join(", "), printed_args, otret.to_s, the_meth.source, the_meth.comment] + } + + end + + def guess_meth(klass, meth, is_sing, the_meth) # first print based on signature according to Ruby first = true @@ -230,9 +280,11 @@ def guess_meth(klass, meth, is_sing, the_meth) otypes.each { |ot| ot[:args].each_with_index { |t, i| otargs[i] = RDL::Globals.types[:bot] if otargs[i].nil? - otargs[i] = RDL::Type::UnionType.new(otargs[i], RDL::Type::NominalType.new(t)).canonical + begin + otargs[i] = RDL::Type::UnionType.new(otargs[i], t).canonical + rescue NameError; end } - otret = RDL::Type::UnionType.new(otret, RDL::Type::NominalType.new(ot[:ret])).canonical + otret = RDL::Type::UnionType.new(otret, RDL::Type::NominalType.new(ot[:ret])).canonical if defined? otret otblock = otblock || ot[:block] } otargs.each { |t| @@ -265,8 +317,29 @@ def do_guess_types puts "end" } end + + + def do_get_types + return if @get_types.empty? + require 'csv' + + CSV.open("#{File.basename(Dir.getwd)}_observed_types.csv", "wb") { |csv| + csv << ["Class", "Method", "Parameter Names", "Observed Arg Types", "Observed Return Type", "Source Code", "Comments"] + } + + RDL::Config.instance.get_types.each { |klass, meth| + the_klass = RDL::Util.to_class(klass) + sklass = RDL::Util.add_singleton_marker(klass.to_s) + wrapped_name = RDL::Wrap.wrapped_name(klass, meth) + begin + the_meth = RDL::Util.to_class(klass).instance_method(wrapped_name) + do_get_meth_type(klass, meth, the_meth) + rescue NameError; end + } + end end + private def install_at_exit @@ -274,6 +347,7 @@ def install_at_exit at_exit do RDL::Config.instance.do_report RDL::Config.instance.do_guess_types + RDL::Config.instance.do_get_types end @at_exit_installed = true end diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 52aecf86..7496252c 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -75,7 +75,6 @@ def self.extract_var_sol(var, category) ## Try each rule. Return first non-nil result. ## If no non-nil results, return original solution. ## TODO: check constraints. - RDL::Heuristic.rules.each { |name, rule| #puts "Trying rule `#{name}` for variable #{var}." typ = rule.call(var) @@ -86,13 +85,15 @@ def self.extract_var_sol(var, category) typ = typ.canonical var.add_and_propagate_upper_bound(typ, nil, new_cons) var.add_and_propagate_lower_bound(typ, nil, new_cons) - - new_cons.each_key { |var_type| - new_cons[var_type].each { |u_or_l, bound, _| - puts "Here 1 with #{u_or_l} bound #{bound} of kind #{bound.class} on #{var_type}" +=begin + new_cons.each { |var, bounds| + bounds.each { |u_or_l, t, _| + puts "1. Added #{u_or_l} bound constraint #{t} of kind #{t.class} to variable #{var}" + puts "It has upper bounds: " + var.ubounds.each { |t, _| puts t } } - } - + } +=end @new_constraints = true if !new_cons.empty? return typ #sol = typ @@ -114,16 +115,14 @@ def self.extract_var_sol(var, category) sol = sol.canonical var.add_and_propagate_upper_bound(sol, nil, new_cons) var.add_and_propagate_lower_bound(sol, nil, new_cons) - @new_constraints = true if !new_cons.empty? - - new_cons.each_key { |var_type| - new_cons[var_type].each { |u_or_l, bound, _| - puts "Here 2 with #{u_or_l} bound #{bound} of kind #{bound.class} on #{var_type}" - puts "It has name #{bound.name}" if bound.is_a?(RDL::Type::NominalType) - puts "#{var_type} already has bounds:" - var_type.ubounds.each { |t, _| puts "#{t} of kind #{t.class} and name #{t.name if t.is_a?(RDL::Type::NominalType)}" } +=begin + new_cons.each { |var, bounds| + bounds.each { |u_or_l, t, _| + puts "2. Added #{u_or_l} bound constraint #{t} to variable #{var}" } } +=end + @new_constraints = true if !new_cons.empty? if sol.is_a?(RDL::Type::GenericType) new_params = sol.params.map { |p| if p.is_a?(RDL::Type::VarType) && !p.to_infer then p else extract_var_sol(p, category) end } @@ -139,6 +138,7 @@ def self.extract_var_sol(var, category) ## no new constraints in this case so we'll leave it as is sol = var end + return sol end @@ -174,39 +174,28 @@ def self.extract_meth_sol(tmeth) extract_var_sol(a, :arg) end } - ## BLOCK SOLUTION if tmeth.block && !tmeth.block.ubounds.empty? non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } non_vartype_ubounds.reject! { |t| t.is_a?(RDL::Type::StructuralType) && (t.methods.size == 1) && t.methods.has_key?(:to_proc) } - - if non_vartype_ubounds.size == 0 + if non_vartype_ubounds.size == 0 block_sol = tmeth.block elsif non_vartype_ubounds.size > 1 block_sols = [] - RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical.types.each { |m| + inter = RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + typs = inter.is_a?(RDL::Type::IntersectionType) ? inter.types : [inter] + typs.each { |m| raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) block_sols << RDL::Type::MethodType.new(*extract_meth_sol(m)) } - block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical + block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical else block_sol = RDL::Type::MethodType.new(*extract_meth_sol(non_vartype_ubounds[0])) end -=begin - block_sol = non_vartype_ubounds.size > 1 ? RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical : non_vartype_ubounds[0] ## doing this once and calling canonical to remove any supertypes that would be eliminated anyway - block_sols = [] - puts "HERE 1 WITH #{block_sol.class}" - block_sol.types.each { |m| - puts "here with #{m}" - raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) - block_sols << RDL::Type::MethodType.new(*extract_meth_sol(m)) - } - block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical - puts "HERE 2" -=end else block_sol = nil end + ## RET SOLUTION if tmeth.ret.to_s == "self" ret_sol = tmeth.ret @@ -226,14 +215,15 @@ def self.extract_solutions typ_sols = {} puts "\n\nRunning solution extraction..." RDL::Globals.constrained_types.each { |klass, name| + puts "HERE WORKING ON #{klass}##{name}" RDL::Type::VarType.no_print_XXX! typ = RDL::Globals.info.get(klass, name, :type) if typ.is_a?(Array) raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 tmeth = typ[0] - arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) + block_string = block_sol ? " { #{block_sol} }" : nil puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index f6fa841c..9eaa88f1 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -8,6 +8,11 @@ def self.add(name, &blk) @rules[name] = blk end + def self.matching_classes(meth_names) + matching_classes = ObjectSpace.each_object(Class).select { |c| + class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) + (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods + end def self.struct_to_nominal(var_type) return unless (var_type.category == :arg) || (var_type.category == :var)#(var_type.category == :ivar) || (var_type.category == :cvar) || (var_type.category == :gvar) ## this rule only applies to args and (instance/class/global) variables @@ -17,9 +22,7 @@ def self.struct_to_nominal(var_type) struct_types.map! { |t, loc| t } return if struct_types.empty? meth_names = struct_types.map { |st| st.methods.keys }.flatten.uniq - matching_classes = ObjectSpace.each_object(Class).select { |c| - class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) - (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods + matching_classes = matching_classes(meth_names) matching_classes.reject! { |c| c.to_s.start_with?("# into *t elsif ti.is_a?(RDL::Type::VarType) - new_arr_type = RDL::Type::GenericType.new(RDL::Globals.types[:array], v = make_unknown_var_type(ti, :splat_param, "splat param")) - RDL::Type::Type.leq(ti, new_arr_type, ast: e) + v = make_unknown_var_type(ti, :splat_param, "splat param") + #new_arr_type = RDL::Type::GenericType.new(RDL::Globals.types[:array], v = make_unknown_var_type(ti, :splat_param, "splat param")) + #RDL::Type::Type.leq(ti, new_arr_type, ast: e) tactuals << RDL::Type::VarargType.new(v) #tactuals << RDL::Type::VarargType.new(RDL::Globals.types[:top]) elsif ti.is_a?(RDL::Type::UnionType) && ti.types.all? { |t| t.is_a?(RDL::Type::GenericType) && t.base == RDL::Globals.types[:array] } params_union = RDL::Type::UnionType.new(*ti.types.map { |t| t.params[0] }).canonical tactuals << RDL::Type::VarargType.new(params_union) + elsif ti.is_a?(RDL::Type::UnionType) && ti.types.any? { |t| t.is_a?(RDL::Type::TupleType) || (t.is_a?(RDL::Type::GenericType) && t.base == RDL::Globals.types[:array]) } + raise "TODO" else - error :cant_splat, [ti], ei.children[0] + #error :cant_splat, [ti], ei.children[0] + # Turns out you can splat anything! + tactuals << ti end elsif ei.type == :block_pass raise RuntimeError, "impossible to pass block arg and literal block" if scope[:block] envi, ti = tc(sscope, envi, ei.children[0]) # convert using to_proc if necessary - if e.children[1] == :map + if (e.children[1] == :map) || e.children[1] == :sum ## block_pass calling map is a weird case: ## it takes a symbol representing method being called, ## where receiver is Array elements. @@ -1052,10 +1062,10 @@ def self.tc(scope, env, e) envguards << envi } initial_env = Env.join(e, *envguards) - if (tguards.all? { |typ| typ.is_a?(RDL::Type::SingletonType) && typ.val.is_a?(Class) }) && (e.children[0].type == :lvar) + if (tguards.all? { |typ| typ.is_a?(RDL::Type::SingletonType) && (typ.val.is_a?(Class) || typ.val.nil?) }) && (e.children[0].type == :lvar) # Special case! We're branching on the type of the guard, which is a local variable. # So rebind that local variable to have the union of the guard types - new_typ = RDL::Type::UnionType.new(*(tguards.map { |typ| RDL::Type::NominalType.new(typ.val) })).canonical + new_typ = RDL::Type::UnionType.new(*(tguards.map { |typ| typ.val.nil? ? typ : RDL::Type::NominalType.new(typ.val) })).canonical # TODO adjust following for generics! if tcontrol.is_a? RDL::Type::GenericType if new_typ == tcontrol.base @@ -1094,6 +1104,7 @@ def self.tc(scope, env, e) envelse, telse, effelse = tc(scope, envi, e.children[-1]) effi = effect_union(effi, effelse) tbodies << telse + #envelse = envelse.bind(e.children[0].children[0], tcontrol, force: :true) if e.children[0].type == :lvar envbodies << envelse end return [Env.join(e, *envbodies), RDL::Type::UnionType.new(*tbodies).canonical, effi] @@ -1294,7 +1305,7 @@ def self.tc(scope, env, e) e.children[0].children.each { |exn| envi, texn, eff_new = tc(scope, envi, exn) effi = effect_union(effi, eff_new) - error :exn_type, [], exn unless texn.is_a?(RDL::Type::SingletonType) && texn.val.is_a?(Class) + error :exn_type, [], exn unless texn.is_a?(RDL::Type::SingletonType) && (texn.val.is_a?(Class) || texn.val.is_a?(Module)) texns << RDL::Type::NominalType.new(texn.val) } else @@ -1360,7 +1371,8 @@ def self.tc(scope, env, e) sklass_str = RDL::Util.to_class_str sklass stype = lookup(scope, sklass_str, mname, e)[0]#RDL::Globals.info.get_with_aliases(sklass_str, mname, :type) error :no_instance_method_type, [sklass_str, mname], e unless stype - raise Exception, "unsupported intersection type in super, e = #{e}" if stype.size > 1 + stype = filter_comp_types(stype, RDL::Config.instance.use_comp_types) + raise Exception, "unsupported intersection type in super, e = #{e}, stype = #{stype}" if stype.size > 1 tactuals = stype[0].args scope_merge(scope, block: nil, break: env, next: env) { |sscope| @@ -1662,12 +1674,12 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) # Returns array of possible return types, or throws exception if there are none def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) raise "Type checking not currently supported for method #{meth}." if [:define_method, :module_exec].include?(meth) -=begin + puts "----------------------" puts "Type checking method call to #{meth} for receiver #{trecv} and tactuals of size #{tactuals.size}:" tactuals.each { |t| puts t } puts "----------------------" -=end + if (trecv == RDL::Globals.types[:array]) trecv = RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Globals.types[:bot]) elsif (trecv == RDL::Globals.types[:hash]) @@ -1680,8 +1692,9 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, case trecv when RDL::Type::SingletonType if trecv.val.is_a? Class or trecv.val.is_a? Module - if meth == :new then + if (meth == :new) && trecv.val.method(:new).owner == Class then ## second condition makes sure :new isn't overriden meth_lookup = :initialize + init = true trecv_lookup = trecv.val.to_s self_inst = RDL::Type::NominalType.new(trecv.val) elsif (defined? ApplicationMailer) && trecv.val.ancestors.include?(ApplicationMailer) @@ -1695,7 +1708,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, self_inst = trecv end ts, es = lookup(scope, trecv_lookup, meth_lookup, e) - ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if (meth == :new) && (ts.nil?) # there's always a nullary new if initialize is undefined + ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if init && (ts.nil?) # there's always a nullary new if initialize is undefined error :no_singleton_method_type, [trecv.val, meth], e unless ts inst = {self: self_inst} self_klass = trecv.val @@ -1703,11 +1716,14 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, # Symbol#to_proc on a singleton symbol type produces a Proc for the method of the same name if env[:self].is_a?(RDL::Type::NominalType) klass = env[:self].klass - else # SingletonType(class) + #elsif env[:self].is_a?( + elsif env[:self].is_a?(RDL::Type::SingletonType) # SingletonType(class) klass = env[:self].val + else + raise "unsupported self type for to_proc #{env[:self]}" end - puts "TRYING FOR KLASS #{klass}" ts, es = lookup(scope, klass.to_s, trecv.val, e) + ts = filter_comp_types(ts, false) ## no comp types in this case error :no_type_for_symbol, [trecv.val.inspect], e if ts.nil? return [ts, nil] ## TODO: not sure what to do hear about effect else @@ -1947,7 +1963,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, end } end - if trecv.is_a?(RDL::Type::SingletonType) && meth == :new + if init#trecv.is_a?(RDL::Type::SingletonType) && meth == :new init_typ = RDL::Type::NominalType.new(trecv.val) if (tmeth.ret.instance_of?(RDL::Type::GenericType)) error :bad_initialize_type, [], e unless (tmeth.ret.base == init_typ) @@ -2047,7 +2063,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, RUBY msg.chomp! # remove trailing newline name = if trecv.is_a?(RDL::Type::SingletonType) && trecv.val.is_a?(Class) && (meth == :new) then - :initialize + trecv.val.to_s elsif trecv.is_a? RDL::Type::SingletonType trecv.val.class.to_s elsif [RDL::Type::NominalType, RDL::Type::GenericType, RDL::Type::FiniteHashType, RDL::Type::TupleType, RDL::Type::AstNode, RDL::Type::PreciseStringType].any? { |t| trecv.is_a? t } @@ -2180,7 +2196,7 @@ def self.tc_arg_types(tmeth, tactuals, deferred_constraints=[]) ## want to match it with the subsequent FHT. else states << [formal+1, actual, inst, deferred_constraints] # types don't match; must skip this formal - end + end when RDL::Type::VarargType if actual == tactuals.size states << [formal+1, actual, inst, deferred_constraints] # skip to allow empty vararg at end @@ -2188,8 +2204,8 @@ def self.tc_arg_types(tmeth, tactuals, deferred_constraints=[]) states << [formal, actual+1, inst, deferred_constraints] # match, more varargs coming states << [formal+1, actual+1, inst, deferred_constraints] # match, no more varargs states << [formal+1, actual, inst, deferred_constraints] # skip over even though matches - elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, deferred_constraints) #&& - #RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, deferred_constraints) + + elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, deferred_constraints) #&& RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, deferred_constraints) states << [formal+1, actual+1, inst, deferred_constraints] # match, no more varargs; no other choices! states << [formal, actual+1, inst, deferred_constraints] else @@ -2391,7 +2407,7 @@ def self.lookup(scope, klass, name, e, make_unknown: true) klass = '(singleton) ' + klass end =end - return nil if the_klass.to_s.start_with?('#. When provided, can be used to roll back constraints in case an error pops up. + # [+ new_cons +] is a Hash>. When provided, can be used to roll back constraints in case an error pops up. def add_and_propagate_upper_bound(typ, ast, new_cons = {}) #puts "1a. Adding upper bound #{self} <= #{typ}" return if self.equal?(typ) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index ba4ec0be..4d0796d5 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -29,6 +29,7 @@ def self.wrap(klass_str, meth) RDL::Globals.wrap_switch.off { klass_str = klass_str.to_s klass = RDL::Util.to_class klass_str + the_meth = klass.instance_method(meth) return if wrapped? klass, meth return if RDL::Config.instance.nowrap.member? klass_str.to_sym raise ArgumentError, "Attempt to wrap #{RDL::Util.pp_klass_method(klass, meth)}" if klass.to_s =~ /^RDL::/ @@ -76,8 +77,9 @@ def #{meth}(*args, &blk) ret = RDL::Type::MethodType.check_ret_types(self, "#{full_method_name}", types, inst, matches, ret, bind, *args, &blk) unless (meth.to_sym == :initialize) end end - if RDL::Config.instance.guess_types.include?("#{klass_str_without_singleton}".to_sym) - RDL::Globals.info.add(klass, meth, :otype, { args: (args.map { |arg| arg.class }), ret: ret.class, block: block_given? }) + if RDL::Config.instance.guess_types.include?("#{klass_str_without_singleton}".to_sym) || RDL::Config.instance.get_types.include?([klass.to_s, meth]) + #puts "adding to otypes!" + RDL::Globals.info.add(klass, meth, :otype, { args: (args.map { |arg| RDL::Wrap.val_to_type(arg) }), ret: RDL::Wrap.val_to_type(ret), block: block_given? }) end return ret } @@ -214,10 +216,37 @@ def self.type_to_type(type) end end + def self.val_to_type(val) + case val + when TrueClass, FalseClass, NilClass + RDL::Type::SingletonType.new(val) + when Array + typs = [] + val.each { |t| typs << val_to_type(t) } + param = RDL::Type::UnionType.new(*typs).canonical + RDL::Type::GenericType.new(RDL::Globals.types[:array], param) + when Hash + key_typs = [] + val_typs = [] + val.each_key { |k| key_typs << val_to_type(k) } + val.each_value { |v| val_typs << val_to_type(v) } + key_param = RDL::Type::UnionType.new(*key_typs).canonical + val_param = RDL::Type::UnionType.new(*val_typs).canonical + RDL::Type::GenericType.new(RDL::Globals.types[:hash], key_param, val_param) + else + RDL::Type::NominalType.new(val.class) + end + end + # called by Object#method_added (sing=false) and Object#singleton_method_added (sing=true) def self.do_method_added(the_self, sing, klass, meth) + if sing + loc = the_self.singleton_method(meth).source_location + else + loc = the_self.instance_method(meth).source_location + end + RDL::Globals.info.set(klass, meth, :source_location, loc) # Apply any deferred contracts and reset list - if RDL::Globals.deferred.size > 0 if sing loc = the_self.singleton_method(meth).source_location @@ -280,6 +309,10 @@ def self.do_method_added(the_self, sing, klass, meth) # Added a method with no type annotation from a class we want to guess types for RDL::Wrap.wrap(klass, meth) end + if RDL::Config.instance.get_types.include?([klass, meth]) + RDL.create_binding_meth(klass, meth) + RDL::Wrap.wrap(klass, meth) + end end end @@ -499,6 +532,64 @@ def infer_file(file, more: false) #RDL.do_infer :all end + def get_path_types(path, no_include = []) + require 'pathname' + path = ::Pathname.new(path).expand_path + rb_files = Dir.entries(path).keep_if { |f| f.end_with?(".rb") } + rb_files.each { |f| get_file_types(path+f) unless no_include.include?(f) } + end + + def get_file_types(file) + file = Pathname.new(file).expand_path.to_s + puts "ABOUT TO INDEX FILE #{file}" + index = ClassIndexer.process_file(file) + index.each { |klass, meth_list| + meth_list.each { |meth| + meth = meth[:name].to_sym + #RDL::Config.instance.get_types << [klass, meth] + #RDL::Wrap.wrap(klass, meth) if RDL::Util.method_defined?(klass, meth) + get_type(klass, meth) + } + } + end + + def get_type(klass, meth) + klass = klass.to_s + meth = meth.to_sym + RDL::Config.instance.get_types << [klass, meth] + if RDL::Util.method_defined?(klass, meth) + create_binding_meth(klass, meth) + RDL::Wrap.wrap(klass, meth) + end + end + + def create_binding_meth(klass, meth) + puts "CREATING BINDING METH FOR #{klass} AND #{meth}" + require 'method_source' + klass = klass.to_s + meth = meth.to_sym + the_meth = RDL::Util.to_class(klass).instance_method(meth) + new_meth_name = "RDL_#{klass}_#{(meth.hash + meth.to_s.hash).abs}".gsub("::", "__").gsub("[s]", "singleton_") + return if RDL.singleton_class.method_defined? new_meth_name + if !RDL.method_defined?(new_meth_name) + ast = Parser::CurrentRuby.parse(the_meth.source) #RDL::Typecheck.get_ast(klass, meth) + if (ast.type != :def) && (ast.type != :defs) + ast = RDL::Typecheck.get_ast(klass, meth) + end + #args_expression = ast.children[1].loc.expression.source + args_expression = ast.children[1].loc.expression + args_string = args_expression ? args_expression.source : "" ## args_expression is nil if there are no arguments + arg_vals = [] + the_meth.parameters.each { |kind, name| + arg_vals << "#{name}: #{name}" if name + } + vals_hash = "{ " + arg_vals.join(",") + " }" + meth_string = "def RDL.#{new_meth_name} #{args_string} \n #{vals_hash} \n end" + puts "ABOUT TO EVALUATE #{meth_string}" + RDL.class_eval meth_string + end + end + def no_infer_meth(klass, meth) RDL::Globals.no_infer_meths << [klass.to_s, meth.to_s] end @@ -920,7 +1011,7 @@ def self.type_cast(obj, typ, force: false) else RDL::Globals.parser.scan_str "#T #{typ}" end - raise RuntimeError, "type cast error: self not a member of #{new_typ}" unless force || new_typ.member?(obj) + raise RuntimeError, "type cast error: self of class #{self.class} not a member of #{new_typ}" unless force || new_typ.member?(obj) new_obj = SimpleDelegator.new(obj) new_obj.instance_variable_set('@__rdl_type', new_typ) new_obj @@ -1005,11 +1096,11 @@ def method_added(meth) } end -class Class - def ===(x) - if x.method(:is_a?).owner == SimpleDelegator then super(x.__getobj__) else super(x) end - end -end +#class Class +# def ===(x) +# if x.method(:is_a?).owner == SimpleDelegator then super(x.__getobj__) else super(x) end +# end +#end class SimpleDelegator ## pass methods through to wrapped object diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 6258c56f..3f5a47a5 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -249,7 +249,7 @@ def Array.map_output(trec) RDL.type :Array, :combination, '(Integer) { (self) -> %any } -> self' RDL.type :Array, :combination, '(Integer) -> Enumerator' RDL.type :Array, :push, '(``any_or_t(trec, true)``) -> ``append_push_output(trec, targs, :push)``' -RDL.type ::Array, :compact, '() -> ``RDL::Type::GenericType.new(RDL::Globals.types[:array], promoted_or_t(trec))``' +RDL.type :Array, :compact, '() -> ``RDL::Type::GenericType.new(RDL::Globals.types[:array], promoted_or_t(trec))``' RDL.type :Array, :compact!, '() -> ``promote_tuple!(trec)``' RDL.type :Array, :concat, '(``promote_tuple(trec)``) -> ``promote_tuple!(trec)``' ## could be more precise here RDL.type :Array, :count, '() -> ``output_type(trec, targs, :count, "Integer")``' @@ -259,7 +259,7 @@ def Array.map_output(trec) RDL.type :Array, :cycle, '(?Integer) -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :delete, '(%any) -> ``promote_tuple!(trec); targs[0]``' RDL.type :Array, :delete, '(u) { () -> v } -> ``promote_tuple!(trec); RDL::Globals.parser.scan_str "#T u or v"``' -RDL.type :Array, :delete_at, '(Integer) -> ``promote_tuple!(trec)``' +RDL.type :Array, :delete_at, '(Integer) -> ``promote_tuple!(trec).params[0]``' RDL.type :Array, :delete_if, '() { (``promoted_or_t(trec)``) -> %bool } -> ``promote_tuple!(trec)``' RDL.type :Array, :delete_if, '() -> ``promote_tuple!(trec); RDL::Globals.parser.scan_str "#T Enumerator"``' RDL.type :Array, :drop, '(Integer) -> ``promote_tuple!(trec)``' diff --git a/lib/types/core/basic_object.rb b/lib/types/core/basic_object.rb index 13f5c986..2fbd79e9 100644 --- a/lib/types/core/basic_object.rb +++ b/lib/types/core/basic_object.rb @@ -7,6 +7,6 @@ RDL.type :BasicObject, :instance_eval, '(String, ?String filename, ?Integer lineno) -> %any' RDL.type :BasicObject, :instance_eval, '() { () -> %any } -> %any' RDL.type :BasicObject, :instance_exec, '(*%any args) { (*%any) -> %any } -> %any' -RDL.type :BasicObject, :__send__, '(Symbol, *%any) -> %any obj' +RDL.type :BasicObject, :__send__, '(Symbol or String, *%any) -> %any obj' RDL.rdl_alias :BasicObject, :__id__, :object_id RDL.type :BasicObject, :object_id, '() -> Integer' diff --git a/lib/types/core/enumerable.rb b/lib/types/core/enumerable.rb index add62233..3b7cc3cf 100644 --- a/lib/types/core/enumerable.rb +++ b/lib/types/core/enumerable.rb @@ -4,10 +4,13 @@ RDL.type :Enumerable, :all?, '() -> %bool', effect: [:blockdep, :blockdep] RDL.type :Enumerable, :all?, '() { (t) -> %bool } -> %bool', effect: [:blockdep, :blockdep] +RDL.type :Enumerable, :all?, '() { (k, v) -> %bool } -> %bool', effect: [:blockdep, :blockdep] RDL.type :Enumerable, :any?, '() -> %bool' RDL.type :Enumerable, :any?, '() { (t) -> %any } -> %bool' # RDL.type :Enumerable, :chunk, '(XXXX : *XXXX)' # TODO -RDL.type :Enumerable, :collect, '() { (?t) -> u } -> Array' +RDL.type :Enumerable, :collect, '() { (t) -> u } -> Array' +RDL.type :Enumerable, :collect, '() { () -> u } -> Array' +RDL.type :Enumerable, :collect, '() { (k, v) -> u } -> Array' RDL.type :Enumerable, :collect, '() -> Enumerator' # RDL.type :Enumerable, :collect_concat # TODO RDL.type :Enumerable, :count, '() -> Integer' diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 4f4325bf..a8ab9264 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -205,7 +205,7 @@ def Hash.assign_output(trec, targs) RDL.type Hash, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] RDL.type Hash, :initialize, "(*%any) -> ``RDL::Type::FiniteHashType.new({}, nil, default: targs[0])``" -RDL.type Hash, :initialize, "() { (Hash, x) -> y } -> ``RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:top], RDL::Globals.types[:top])``" +RDL.type Hash, :initialize, "() { (Hash, x) -> y } -> ``RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:top], RDL::Globals.types[:top])``" RDL.type :Hash, :store, '(``any_or_k(trec)``, ``any_or_v(trec)``) -> ``assign_output(trec, targs)``' RDL.type :Hash, :assoc, '(``any_or_k(trec)``) -> ``RDL::Type::TupleType.new(targs[0], output_type(trec, targs, :[], :promoted_val, "v", nil_default: true))``' @@ -245,7 +245,7 @@ def Hash.delete_output(trec, targs, block) end RDL.type Hash, 'self.delete_output', "(RDL::Type::Type, Array, %bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] -RDL.type :Hash, :delete_if, '() { (``any_or_k(trec)``, ``any_or_v(trec)``) -> %any } -> self' +RDL.type :Hash, :delete_if, '() { (``promoted_or_k(trec)``, ``promoted_or_v(trec)``) -> %any } -> self' RDL.type :Hash, :delete_if, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. RDL.type :Hash, :each, '() { (``promoted_or_k(trec)``, ``promoted_or_v(trec)``) -> %any } -> self' RDL.type :Hash, :each, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. @@ -299,11 +299,15 @@ def Hash.merge_input(trec, targs, mutate=false) return targs[0] else #when RDL::Type::GenericType if mutate - raise "Unable to promote #{trec}." unless trec.promote! + raise "Unable to promote #{trec}." if trec.is_a?(RDL::Type::FiniteHashType) && !trec.promote! return trec.canonical #return RDL::Globals.parser.scan_str "#T Hash" else - return RDL::Globals.parser.scan_str "#T Hash" + if trec.is_a?(RDL::Type::GenericType) + return RDL::Globals.parser.scan_str "#T Hash" + else + return targs[0] + end end #else # RDL::Globals.types[:hash] @@ -532,4 +536,5 @@ def Hash.values_at_output(trec, targs) RDL.type :Hash, :to_hash, '() -> Hash' RDL.type :Hash, :values, '() -> Array' RDL.type :Hash, :values_at, '(*k) -> Array', effect: [:+, :+] +RDL.type :Hash, :with_indifferent_access, '() -> self' diff --git a/lib/types/core/io.rb b/lib/types/core/io.rb index 1f66d8dd..3b8a17b6 100644 --- a/lib/types/core/io.rb +++ b/lib/types/core/io.rb @@ -17,7 +17,7 @@ RDL.type :IO, 'self.popen', '(?Hash env, String cmd, ?String mode, %open_args) { (IO) -> t } -> t' RDL.type :IO, 'self.read', '(String name, ?Integer length, ?Integer offset, %open_args) -> String' RDL.type :IO, 'self.readlines', '(String name, ?String sep, ?Integer limit, %open_args) -> Array' -RDL.type :IO, 'self.select', '(Array read_array, ?Array write_array, ?Array error_array, ?Integer timeout) -> Array or nil' +RDL.type :IO, 'self.select', '(Array read_array, ?Array write_array, ?Array error_array, ?Integer timeout) -> Array> or nil' RDL.type :IO, 'self.sysopen', '(String path, ?String mode, ?String perm) -> Integer' # TODO unsure of RDL.type of perm RDL.type :IO, 'self.try_convert', '([to_io: () -> IO]) -> IO or nil' RDL.type :IO, 'self.write', '(String name, String, ?Integer offset, %open_args) -> Integer' diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index 27eb7b67..d79aed2a 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -30,7 +30,7 @@ RDL.type :Kernel, 'self.caller', '(Range) -> Array or nil' RDL.type :Kernel, 'self.caller_locations', '(?Integer start, ?Integer length) -> Array or nil' RDL.type :Kernel, 'self.caller_locations', '(Range) -> Array or nil' -# RDL.type :Kernel, 'self.catch' # TODO + RDL.type :Kernel, 'self.catch', "(x) { (?x) -> u } -> u" RDL.type :Kernel, 'self.eval', '(String, ?Binding, ?String filename, ?Integer lineno) -> %any' # RDL.type :Kernel, 'self.exec' #TODO RDL.type :Kernel, 'self.exit', '() -> %bot' @@ -65,7 +65,8 @@ RDL.type :Kernel, 'raise', '() -> %bot', effect: [:+, :+] # RDL.type :Kernel, 'self.raise', '(String or [exception : () -> String], ?String, ?Array) -> %any' # TODO: above same as fail? -RDL.type :Kernel, 'self.rand', '(Integer or Range max) -> Numeric' +RDL.type :Kernel, 'self.rand', '(Integer or Range max) -> Integer' +RDL.type :Kernel, 'self.rand', '() -> Float' RDL.type :Kernel, 'self.readline', '(?String, ?Integer) -> String' RDL.type :Kernel, 'self.readlines', '(?String, ?Integer) -> Array' RDL.type :Kernel, 'self.require', '(String name) -> %bool' @@ -73,10 +74,12 @@ RDL.type :Kernel, 'self.select', '(Array read, ?Array write, ?Array error, ?Integer timeout) -> Array' # TODO: return RDL.type? # RDL.type :Kernel, 'self.set_trace_func' #TODO +RDL.type :Kernel, 'self.singleton_class', '() -> Class' RDL.type :Kernel, 'self.sleep', '(Numeric duration) -> Integer' # RDL.type :Kernel, 'self.spawn' #TODO RDL.rdl_alias :Kernel, :'self.sprintf', :'self.format' # TODO: are they aliases? -RDL.type :Kernel, 'self.srand', '(Numeric number) -> Numeric' +RDL.type :Kernel, :sprintf, "(String, %any) -> String" +RDL.type :Kernel, 'self.srand', '(Numeric number) -> Integer' RDL.type :Kernel, 'self.syscall', '(Integer num, *%any args) -> %any' # TODO : ? # RDL.type :Kernel, 'self.system' # TODO RDL.type :Kernel, 'self.test', '(String cmd, String file1, ?String file2) -> %bool or Time' # TODO: better, dependent RDL.type? diff --git a/lib/types/core/set.rb b/lib/types/core/set.rb index 85a4c516..152e34fc 100644 --- a/lib/types/core/set.rb +++ b/lib/types/core/set.rb @@ -3,7 +3,8 @@ RDL.type_params :Set, [:t], :all? RDL.type :Set, 'self.[]', '(*u) -> Set' -RDL.type :Set, :initialize, '(?Enumerable enum) -> self' +RDL.type :Set, :initialize, '(Enumerable enum) -> self' +RDL.type :Set, :initialize, '() -> self' RDL.rdl_alias :Set, :&, :intersection RDL.type :Set, :+, '(Enumerable enum) -> Set' diff --git a/lib/types/core/string.rb b/lib/types/core/string.rb index 567090cd..3ca446f6 100644 --- a/lib/types/core/string.rb +++ b/lib/types/core/string.rb @@ -142,7 +142,7 @@ def String.cap_down_output(trec, meth) RDL.type :String, :casecmp, '(String) -> ``output_type(trec, targs, :casecmp, "Integer")``' RDL.type :String, :center, '(Integer, ?String) -> ``output_type(trec, targs, :center, "String")``' -RDL.type :String, :chars, '() -> ``output_type(trec, targs, :chars, "Array")``' #deprecated +RDL.type :String, :chars, '() -> ``output_type(trec, targs, :chars, "Array")``' #deprecated RDL.type :String, :chomp, '(?String) -> ``output_type(trec, targs, :chomp, "String")``' RDL.type :String, :chomp!, '(?String) -> ``string_promote!(trec)``' ## chomp! depends on the value of $/, which is hard to reason about during type checking. So, keeping this imprecise. RDL.type :String, :chop, '() -> ``output_type(trec, targs, :chop, "String")``' @@ -423,7 +423,7 @@ def String.mutate_output(trec, meth) RDL.type :String, :capitalize!, '() -> String or nil' RDL.type :String, :casecmp, '(String) -> nil or Integer' RDL.type :String, :center, '(Integer, ?String) -> String' -RDL.type :String, :chars, '() -> Array' #deprecated +RDL.type :String, :chars, '() -> Array' #deprecated RDL.type :String, :chomp, '(?String) -> String' RDL.type :String, :chomp!, '(?String) -> String or nil' RDL.type :String, :chop, '() -> String' diff --git a/lib/types/core/time.rb b/lib/types/core/time.rb index 3632cf0e..2a858858 100644 --- a/lib/types/core/time.rb +++ b/lib/types/core/time.rb @@ -1,6 +1,6 @@ RDL.nowrap :Time -RDL.type :Time, 'self.at', '(Numeric seconds, Numeric microseconds_with_frac) -> ``RDL::Type::NominalType.new(trec.val)``' +RDL.type :Time, 'self.at', '(Numeric, ?Numeric) -> ``RDL::Type::NominalType.new(trec.val)``' RDL.type :Time, 'self.at', '(Time) -> Time' RDL.type :Time, 'self.at', '(Numeric seconds_with_frac) -> Time' RDL.type :Time, 'self.at', '(Numeric seconds, Numeric microseconds_with_frac) -> Time' diff --git a/lib/types/core/yaml.rb b/lib/types/core/yaml.rb index 0f258e8f..3dbf364a 100644 --- a/lib/types/core/yaml.rb +++ b/lib/types/core/yaml.rb @@ -1,3 +1,8 @@ RDL.nowrap :YAML +RDL.nowrap :Psych RDL.type :YAML, 'self.load_file', '(String) -> Array' +RDL.type :YAML, 'self.load', '(String) -> Array' + +RDL.type :Psych, 'self.load_file', '(String) -> Array' +RDL.type :Psych, 'self.load', '(String) -> Array' From b51d9147e12a69451b556e655e966162e0cdc9de Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Thu, 16 Jan 2020 17:15:37 -0500 Subject: [PATCH 013/124] function params for read write effects --- lib/rdl/wrap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index cd289eee..92245a34 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -324,7 +324,7 @@ def post(*args, wrap: RDL::Config.instance.post_defaults[:wrap], version: nil, & # type(klass, meth, type) # type(meth, type) # type(type) - def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil, effect: nil) + def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil, effect: nil, read: [], write: []) return if version && !(Gem::Requirement.new(version).satisfied_by? Gem.ruby_version) klass, meth, type = begin RDL::Wrap.process_type_args(self, *args) From 8e1f810a5af06669647dc679c21c97b6b280c99d Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Tue, 21 Jan 2020 23:33:48 -0500 Subject: [PATCH 014/124] leq for optional type, is it sound though? --- lib/rdl/types/optional.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/rdl/types/optional.rb b/lib/rdl/types/optional.rb index 838baa3d..614009dd 100644 --- a/lib/rdl/types/optional.rb +++ b/lib/rdl/types/optional.rb @@ -36,6 +36,10 @@ def match(other) return (other.instance_of? OptionalType) && (@type.match(other.type)) end + def <=(other) + return Type.leq(self.type, other) + end + # Note: no member?, because these can only appear in MethodType, where they're handled specially def instantiate(inst) From 704eb5b5bb07d7613968a64c932f684b079143ac Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Tue, 28 Jan 2020 11:55:43 -0500 Subject: [PATCH 015/124] some clean up, and updates to class indexer --- lib/rdl/class_indexer.rb | 13 +++ lib/rdl/constraint.rb | 3 +- lib/rdl/heuristics.rb | 8 +- lib/rdl/typecheck.rb | 125 +++++++++------------------- lib/rdl/types/type.rb | 175 +++++---------------------------------- lib/rdl/types/var.rb | 17 +--- lib/types/core/array.rb | 10 +-- lib/types/core/hash.rb | 3 +- lib/types/core/kernel.rb | 7 +- 9 files changed, 97 insertions(+), 264 deletions(-) diff --git a/lib/rdl/class_indexer.rb b/lib/rdl/class_indexer.rb index a8c0cd33..88f2ea1e 100644 --- a/lib/rdl/class_indexer.rb +++ b/lib/rdl/class_indexer.rb @@ -36,17 +36,24 @@ def on_class(node) if @current_class == "main" @current_class = class_name + entered_class = class_name else @current_class << "::" + class_name + entered_class = "::" + class_name end node.children.each { |c| process(c) } + @current_class.sub!(entered_class, "") + reset_class if @current_class.empty? + +=begin if @current_class.include?("::") @current_class.sub!("::" + class_name, "") else reset_class end +=end end def on_module(node) @@ -54,18 +61,24 @@ def on_module(node) if @current_class == "main" @current_class = module_name + entered_class = module_name else #@current_class.prepend(module_name + "::") @current_class << "::" + module_name + entered_class = "::" + module_name end node.children.each { |c| process(c) } + @current_class.sub!(entered_class, "") + reset_class if @current_class.empty? +=begin if @current_class.include?("::") @current_class.sub!("::"+module_name, "") else reset_class end +=end end def on_sclass(node) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 7496252c..2b313b64 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -177,7 +177,7 @@ def self.extract_meth_sol(tmeth) ## BLOCK SOLUTION if tmeth.block && !tmeth.block.ubounds.empty? non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } - non_vartype_ubounds.reject! { |t| t.is_a?(RDL::Type::StructuralType) && (t.methods.size == 1) && t.methods.has_key?(:to_proc) } + non_vartype_ubounds.reject! { |t| t.is_a?(RDL::Type::StructuralType) }#&& (t.methods.size == 1) && (t.methods.has_key?(:to_proc) || t.methods.has_key?(:call)) } if non_vartype_ubounds.size == 0 block_sol = tmeth.block elsif non_vartype_ubounds.size > 1 @@ -215,7 +215,6 @@ def self.extract_solutions typ_sols = {} puts "\n\nRunning solution extraction..." RDL::Globals.constrained_types.each { |klass, name| - puts "HERE WORKING ON #{klass}##{name}" RDL::Type::VarType.no_print_XXX! typ = RDL::Globals.info.get(klass, name, :type) if typ.is_a?(Array) diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 9eaa88f1..051b4460 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -9,9 +9,14 @@ def self.add(name, &blk) end def self.matching_classes(meth_names) + meth_names.delete(:initialize) + meth_names.delete(:new) matching_classes = ObjectSpace.each_object(Class).select { |c| class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods + matching_classes += ObjectSpace.each_object(Module).select { |c| + class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) + (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods end def self.struct_to_nominal(var_type) @@ -26,8 +31,7 @@ def self.struct_to_nominal(var_type) matching_classes.reject! { |c| c.to_s.start_with?("# 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } union = RDL::Type::UnionType.new(*nom_sing_types).canonical diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 94e11bec..a988597c 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -755,7 +755,7 @@ def self.tc(scope, env, e) envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], rhs.pop, left) } splat = lhs[splat_ind] - envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::TupleType.new(*rhs), splat) + envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::TupleType.new(*rhs), splat) if splat.children.any? [envi, tright, effi] else error :masgn_num, [rhs.length, lhs.length], e unless lhs.length == rhs.length @@ -786,16 +786,35 @@ def self.tc(scope, env, e) [env, tright, effi] elsif tright.is_a?(RDL::Type::VarType) splat_ind = lhs.index { |lhs_elt| lhs_elt.type == :splat } - raise "not yet implemented" if splat_ind - new_tuple = [] - count = 0 - lhs.length.times { new_tuple << RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "tuple_element_#{count}") } - lhs.zip(new_tuple).each { |left, right| - envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left) - } - tuple_type = RDL::Type::TupleType.new(*new_tuple) - #RDL::Type::Type.leq(tright, tuple_type, ast: e) # don't think this is necessary - [envi, tright, effi] + #raise "not yet implemented" if splat_ind + if splat_ind + count = 0 + if splat_ind > 0 + lhs[0..splat_ind-1].each { |left| + # before splat + envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "tuple_element_#{count}"), left) + count += 1 + } + end + lhs[splat_ind+1..-1].reverse_each { |left| + # after splat + envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "tuple_element_#{count}"), left) + count += 1 + } + splat = lhs[splat_ind] + envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "array_param_#{count}")), splat) if splat.children.any? ## could be empty splat + [envi, tright, effi] + else + new_tuple = [] + count = 0 + lhs.length.times { new_tuple << RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "tuple_element_#{count}") } + lhs.zip(new_tuple).each { |left, right| + envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left) + } + tuple_type = RDL::Type::TupleType.new(*new_tuple) + #RDL::Type::Type.leq(tright, tuple_type, ast: e) # don't think this is necessary + [envi, tright, effi] + end else error :masgn_bad_rhs, [tright], e.children[1] end @@ -893,6 +912,8 @@ def self.tc(scope, env, e) if e.children[0].nil? && e.children[1] == :ENV ## ENV c = RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:string], RDL::Globals.types[:string]) + elsif e.children[0].nil? && e.children[1] == :ARGV + c = RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Globals.types[:string]) else c = to_type(find_constant(env, e)) end @@ -942,7 +963,7 @@ def self.tc(scope, env, e) raise RuntimeError, "impossible to pass block arg and literal block" if scope[:block] envi, ti = tc(sscope, envi, ei.children[0]) # convert using to_proc if necessary - if (e.children[1] == :map) || e.children[1] == :sum + if (e.children[1] == :map) || e.children[1] == :sum || e.children[1] == :keep_if ## block_pass calling map is a weird case: ## it takes a symbol representing method being called, ## where receiver is Array elements. @@ -1333,7 +1354,7 @@ def self.tc(scope, env, e) effi = effect_union(eff_new, effi) if ti.is_a? RDL::Type::TupleType tactuals.concat ti.params - elsif ti.is_a?(RDL::Type::GenericType) && ti.base == $__rdl_array_type + elsif ti.is_a?(RDL::Type::GenericType) && ti.base == RDL::Globals.types[:array] tactuals << RDL::Type::VarargType.new(ti.params[0]) # Turn Array into *t else error :cant_splat, [ti], ei.children[0] @@ -1618,7 +1639,6 @@ def self.tc_instantiate!(scope, env, e) # [+ op_asgn +] is a bool telling us that we are type checking the mutation method for an op_asgn node. used for ast rewriting. def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception - # convert trecvs to array containing all receiver types trecvs = trecvs.canonical trecvs = if trecvs.is_a? RDL::Type::UnionType then union = true; trecvs.types else union = false; [trecvs] end @@ -1674,12 +1694,12 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) # Returns array of possible return types, or throws exception if there are none def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) raise "Type checking not currently supported for method #{meth}." if [:define_method, :module_exec].include?(meth) - +=begin puts "----------------------" puts "Type checking method call to #{meth} for receiver #{trecv} and tactuals of size #{tactuals.size}:" tactuals.each { |t| puts t } puts "----------------------" - +=end if (trecv == RDL::Globals.types[:array]) trecv = RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Globals.types[:bot]) elsif (trecv == RDL::Globals.types[:hash]) @@ -1928,13 +1948,6 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, #apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? if tmeth_inst begin -=begin - if (meth == :define_method) && trecv.is_a?(RDL::Type::SingletonType) - new_env = env.bind(:self, RDL::Type::NominalType.new(trecv.val), force: true) ## define_method defines a new *instance* method... self in block is nominal, not singleton - else - new_env = env - end -=end effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block rescue BlockTypeError => err block_mismatch = true @@ -2188,12 +2201,14 @@ def self.tc_arg_types(tmeth, tactuals, deferred_constraints=[]) t = t.type if actual == tactuals.size states << [formal+1, actual, inst, deferred_constraints] # skip over optinal formal - elsif RDL::Type::Type.leq(tactuals[actual], t, inst, false, deferred_constraints) #&& (not (tactuals[actual].is_a?(RDL::Type::VarargType))) + elsif (not tactuals[actual].is_a?(RDL::Type::VarargType)) && RDL::Type::Type.leq(tactuals[actual], t, inst, false, deferred_constraints) #&& (not (tactuals[actual].is_a?(RDL::Type::VarargType))) states << [formal+1, actual+1, inst, deferred_constraints] # match #states << [formal+1, actual, inst, deferred_constraints] unless tactuals[actual].is_a?(RDL::Type::VarType) && tformals[formal+1].is_a?(RDL::Type::FiniteHashType)# skip ## I added the `unless` guard above because of cases like `redirect_to @list, action: 'something'`. ## The clear intention of the programmer is for @list to match a non-FHT formal arg, so don't ## want to match it with the subsequent FHT. + elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(RDL::Type::GenericType.new(RDL::Globals.types[:array], tactuals[actual].type), t, inst, false, deferred_constraints) + states << [formal+1, actual+1, inst, deferred_constraints] # match else states << [formal+1, actual, inst, deferred_constraints] # types don't match; must skip this formal end @@ -2462,7 +2477,7 @@ def self.make_unknown_method_type(klass, meth) when :block ## all method types will be given a variable type for blocks anyway, so no need to add a new param here when :keyrest - raise "Not currently supported" + raise "Not currently supported, for method #{meth} of class #{klass}" else raise "Unexpected parameter type #{param[0]}." end @@ -2534,6 +2549,7 @@ def self.get_singleton_name(name) def self.find_constant(env, e) # https://cirw.in/blog/constant-lookup.html # First look in Module.nesting for a lexically scoped variable + const_string = e.loc.expression.source raise "Expected @cur_meth." unless @cur_meth if (RDL::Util.has_singleton_marker(@cur_meth[0])) klass = RDL::Util.to_class(RDL::Util.remove_singleton_marker(@cur_meth[0])) @@ -2559,68 +2575,9 @@ def self.find_constant(env, e) else method_binding = klass.method(meth_name).to_proc.binding end - const_string = e.loc.expression.source return method_binding.send(:eval, const_string) end end -=begin - if e.children[0] && e.children[0].type == :cbase - c = get_leaves(e).inject(Object) { |m, c2| m.const_get(c2) } - return c - end - if @cur_meth - if (RDL::Util.has_singleton_marker(@cur_meth[0])) - klass = RDL::Util.to_class(RDL::Util.remove_singleton_marker(@cur_meth[0])) - mod_inst = false - else - klass = RDL::Util.to_class(@cur_meth[0]) - if klass.instance_of?(Module) - mod_inst = true - else - mod_inst = false - klass = klass.send :allocate - end - end - if RDL::Wrap.wrapped?(@cur_meth[0], @cur_meth[1]) - meth_name = RDL::Wrap.wrapped_name(@cur_meth[0], @cur_meth[1]) - else - meth_name = @cur_meth[1] - end - if mod_inst ## TODO: Is there a better way to do this? Module method bindings are made at runtime, so not sure. - nesting = klass.module_eval('Module.nesting') - else - method = klass.method(meth_name) - nesting = method.to_proc.binding.eval('Module.nesting') - end - nesting.each do |ic| - c = get_leaves(e).inject(ic) {|m, c2| m && m.const_defined?(c2, false) && m.const_get(c2, false)} - # My first time using ruby's stupid return-from-block correctly - return c if c - end - end - - # Check the ancestors - if e.children[0].nil? - case env[:self] - when RDL::Type::SingletonType - ic = env[:self].val - when RDL::Type::NominalType - ic = env[:self].klass - else - raise Exception, "unsupported env[self]=#{env[:self]}" - end - c = get_leaves(e).inject(ic) {|m, c2| m.const_get(c2)} - elsif e.children[0].type == :lvar - raise "const lvar not implemented yet" # TODO! - elsif e.children[0].type == :const - child = find_constant(env, e.children[0]) - c = get_leaves(e).inject(child) {|m, c2| m.const_get(c2)} - else - raise "const other not implemented yet" - end - end -end -=end # Use parser's Diagnostic to output RDL typechecker error messages class Diagnostic < Parser::Diagnostic diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 8f6a3b7d..d9dedcc1 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -74,12 +74,9 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con right = right.type if right.is_a? NonNullType left = left.canonical right = right.canonical - #puts "ABOUT TO TRY #{left} <= #{right}" return true if left.equal?(right) - #left = left.nominal if left.is_a?(RDL::Type::SingletonType) && left.val.is_a?(Integer) && RDL::Config.instance.number_mode - #right = right.nominal if right.is_a?(RDL::Type::SingletonType) && right.val.is_a?(Integer) && RDL::Config.instance.number_mode # top and bottom return true if left.is_a? BotType @@ -120,7 +117,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con ## choice types if left.is_a?(ChoiceType) && right.is_a?(ChoiceType) - #puts "HERE 1 TRYING #{left} <= #{right}" ## ChoiceTypes can't contain VarTypes to be inferred within them, so no need to worry about constraints here. if left.connecteds.include?(right) ## if left and right are connected, left <= right whenever each individual left choice is <= the corresponding right choice @@ -152,7 +148,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end end elsif left.is_a?(ChoiceType) || right.is_a?(ChoiceType) - #puts "HERE 2 TRYING #{left} <= #{right}" if left.is_a?(ChoiceType) main_ct = left lct = true @@ -170,15 +165,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con new_dcs.each { |t1, t2| ub_var_choices[t1][num] = RDL::Type::UnionType.new(ub_var_choices[t1][num], t2).canonical if t1.is_a?(VarType) lb_var_choices[t2][num] = RDL::Type::UnionType.new(lb_var_choices[t2][num], t1).canonical if t2.is_a?(VarType) -=begin - if t1.is_a?(VarType) - raise "Not currently supported, for var types: #{t1} and #{t2}." if t2.is_a?(VarType) - ub_var_choices[t1][num] = RDL::Type::UnionType.new(ub_var_choices[t1][num], t2).canonical - else - raise "Expected VarType, got #{t2}" unless t2.is_a?(VarType) - lb_var_choices[t2][num] = RDL::Type::UnionType.new(lb_var_choices[t2][num], t1).canonical - end -=end } else to_remove << num @@ -216,40 +202,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con return true end - -=begin - to_remove << num unless Type.leq(t, right, inst, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices)#t <= right - ## TODO: actually use deferred_constraints or new_cons here - } - if to_remove.size == left.choices.size - ## none of the choices match, so left is not a subtype of right - return false - else - to_remove.each { |num| left.remove!(num) } - return true - end - #to_remove.each { |num| left.remove!(num) } - #return !left.choices.empty? -=end - -=begin - elsif right.is_a?(ChoiceType) - puts "HERE 3 TRYING #{left} <= #{right}" - # VarTypes would have been handled by now - to_remove = [] - right.choices.each { |num, t| - to_remove << num unless Type.leq(left, t, inst, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices)#left <= t - } - if to_remove.size == right.choices.size - return false - else - to_remove.each { |num| right.remove!(num) } - return true - end - #to_remove.each { |num| right.remove!(num) } - #return !right.choices.empty? - end -=end # union return left.types.all? { |t| leq(t, right, inst, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } if left.is_a?(UnionType) if right.instance_of?(UnionType) @@ -324,7 +276,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con right.methods.each_pair { |m, t| return false unless lklass.method_defined?(m) || RDL::Typecheck.lookup({}, klass_lookup, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) ## Added the second condition because Rails lazily defines some methods. types, _ = RDL::Typecheck.lookup({}, lklass.to_s, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) - if RDL::Config.instance.use_comp_types types = RDL::Typecheck.filter_comp_types(types, true) else @@ -352,15 +303,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con new_dcs.each { |t1, t2| ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical if t1.is_a?(VarType) lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical if t2.is_a?(VarType) -=begin - if t1.is_a?(VarType) - raise "Not currently supported, for var types: #{t1} and #{t2}." if t2.is_a?(VarType) ## This shouldn't happen, because there shouldn't be intersection types with VarTypes in them. - ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical - else - raise "Expected VarType, got #{t2}" unless t2.is_a?(VarType) - lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical - end -=end } else new_dcs.each { |t1, t2| RDL::Type::Type.leq(t1, t2, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } @@ -398,63 +340,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end -=begin - (raise errs[0] if !errs.empty? ; return false ) unless types.any? { |tlm| ## raising err[0] if it exist, but really could be any error we raise - blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] - if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } - ## In this case, need to actually evaluate the ComputedType. - ## Going to do this using the receiver `left` and the args from `t` - ## If subtyping holds for this, then we know `left` does indeed have a method of the relevant type. - tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) - end - begin - attempted_cons = {} - ret = leq(tlm.instantiate(base_inst), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: attempted_cons, removed_choices: removed_choices) p - # inst above is nil because the method types inside the class and - # inside the structural type have an implicit quantifier on them. So - # even if we're allowed to instantiate type variables we can't do that - # inside those types - new_cons.merge(attempted_cons) { |key, orig_val, new_val| orig_val | new_val } - ret - rescue RDL::Typecheck::StaticTypeError => err ## could be raised when propagating constraints - RDL::Typecheck.undo_constraints(attempted_cons) - errs << err - false - end - } - end - } - return true - end - - - if (left.is_a?(SingletonType) && left.val.class == Class) && right.is_a?(StructuralType) - lklass = left.val - right.methods.each_pair { |m, t| - return false unless lklass.methods.include?(m) || RDL::Typecheck.lookup({}, "[s]"+lklass.to_s, m, nil, make_unknown: false) - types, _ = RDL::Typecheck.lookup({}, "[s]"+lklass.to_s, m, nil, make_unknown: false) - if types - errs = [] - return false unless types.any? { |tlm| - blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] - - tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } - begin - attempted_cons = {} - ret = leq(tlm, t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: attempted_cons, removed_choices: removed_choices) - new_cons.merge(attempted_cons) { |key, orig_val, new_val| orig_val | new_val } - ret - rescue RDL::Typecheck::StaticTypeError => err ## could be raised when propagating constraints - RDL::Typecheck.undo_constraints(attempted_cons) - errs << err - false - end - } - end - } - return true - end -=end # singleton return left.val == right.val if left.is_a?(SingletonType) && right.is_a?(SingletonType) return true if left.is_a?(SingletonType) && left.val.nil? # right cannot be a SingletonType due to above conditional @@ -523,15 +408,6 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con new_dcs.each { |t1, t2| ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical if t1.is_a?(VarType) lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical if t2.is_a?(VarType) -=begin - if t1.is_a?(VarType) - raise "Not currently supported, for var types: #{t1} and #{t2}." if t2.is_a?(VarType) ## This shouldn't happen, because there shouldn't be intersection types with VarTypes in them. - ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical - else - raise "Expected VarType, got #{t2}" unless t2.is_a?(VarType) - lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical - end -=end } else new_dcs.each { |t1, t2| RDL::Type::Type.leq(t1, t2, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } @@ -568,40 +444,24 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end -=begin - errs = [] - return false unless types.any? { |tlm| - blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] - tlm = RDL::Typecheck.compute_types(tlm, klass, left, t.args) if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } - begin - attempted_cons = {} - ret = leq(tlm.instantiate(base_inst.merge({ self: left})), t, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: attempted_cons, removed_choices: removed_choices) - new_cons.merge(attempted_cons) { |key, orig_val, new_val| orig_val | new_val } - ret - rescue RDL::Typecheck::StaticTypeError => err - RDL::Typecheck.undo_constraints(attempted_cons) - errs << err - false - end - } - end - } - return true - end -=end - # Note we do not allow raw subtyping leq(GenericType, NominalType, ...) - # method + # method if left.is_a?(MethodType) && right.is_a?(MethodType) inst = {} if not inst if left.args.last.is_a?(VarargType) - return false unless right.args.size >= left.args.size - new_args = right.args[(left.args.size - 1) ..-1] - if left.args.size == 1 - left = RDL::Type::MethodType.new(new_args, left.block, left.ret) + #return false unless right.args.size >= left.args.size + if right.args.size >= left.args.size + new_args = right.args[(left.args.size - 1) ..-1] + if left.args.size == 1 + left = RDL::Type::MethodType.new(new_args, left.block, left.ret) + else + left = RDL::Type::MethodType.new(left.args[0..(left.args.size-2)]+new_args, left.block, left.ret) + end + elsif right.args.size == left.args.size + left = RDL::Type::MethodType.new(left.args[0..left.args.size-2] + [left.args[-1].type], left.block, left.ret) else - left = RDL::Type::MethodType.new(left.args[0..(left.args.size-2)]+new_args, left.block, left.ret) + left = RDL::Type::MethodType.new(left.args[0..left.args.size-2], left.block, left.ret) end end @@ -626,7 +486,16 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end return true if left.is_a?(MethodType) && right.is_a?(NominalType) && right.name == 'Proc' - return true if left.is_a?(MethodType) && right.is_a?(StructuralType) && (right.methods.size == 1) && right.methods.has_key?(:to_proc) + + if left.is_a?(MethodType) && right.is_a?(StructuralType) + return true if (right.methods.size == 1) && right.methods.has_key?(:to_proc) + right.methods.each_pair { |m, t| + if m == :call + return false unless left.args.size == t.args.size + end + } + return true + end # structural if left.is_a?(StructuralType) && right.is_a?(StructuralType) diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index 36828f06..2ff9ca47 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -55,14 +55,12 @@ def initialize(name_or_hash) # [+ ast +] is the AST where the bound originates from, used for error messages. # [+ new_cons +] is a Hash>. When provided, can be used to roll back constraints in case an error pops up. def add_and_propagate_upper_bound(typ, ast, new_cons = {}) - #puts "1a. Adding upper bound #{self} <= #{typ}" return if self.equal?(typ) if !@ubounds.any? { |t, a| t == typ } @ubounds << [typ, ast] new_cons[self] = new_cons[self] ? new_cons[self] | [[:upper, typ, ast]] : [[:upper, typ, ast]] end @lbounds.each { |lower_t, a| - #puts "2a. Adding bound #{lower_t} <= #{typ}" if lower_t.is_a?(VarType) lower_t.add_and_propagate_upper_bound(typ, ast, new_cons) unless lower_t.ubounds.any? { |t, _| t == typ } else @@ -81,17 +79,13 @@ def add_and_propagate_upper_bound(typ, ast, new_cons = {}) ## Similar to above. def add_and_propagate_lower_bound(typ, ast, new_cons = {}) - #puts "1b. Adding lower bound #{typ} <= #{self}" raise if typ.to_s == "v" return if self.equal?(typ) if !@lbounds.any? { |t, a| t == typ } @lbounds << [typ, ast] new_cons[self] = new_cons[self] ? new_cons[self] | [[:lower, typ, ast]] : [[:lower, typ, ast]] end - #puts "The upper bounds are: "# - #@ubounds.each { |u, _| puts u } @ubounds.each { |upper_t, a| - #puts "2b. Adding bound #{typ} <= #{upper_t}." if upper_t.is_a?(VarType) upper_t.add_and_propagate_lower_bound(typ, ast, new_cons) unless upper_t.lbounds.any? { |t, _| t == typ } else @@ -99,8 +93,6 @@ def add_and_propagate_lower_bound(typ, ast, new_cons = {}) new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:upper, upper_t, ast]] : [[:upper, upper_t, ast]] end unless RDL::Type::Type.leq(typ, upper_t, {}, false, ast: ast, no_constraint: true, propagate: true, new_cons: new_cons) - #puts "FAILED" - # TZInfo::DataSource <= { [s]TZInfo::DataSource#get ret: ret }. d1 = ast.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") d2 = a.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{typ} <= #{upper_t} generated during inference.\n #{d1}\n #{d2}") @@ -110,25 +102,20 @@ def add_and_propagate_lower_bound(typ, ast, new_cons = {}) end def add_ubound(typ, ast, new_cons = {}, propagate: false) - raise "ABOUT TO ADD UBOUND #{self} <= #{typ}" if typ.is_a?(VarType) && !typ.to_infer - #typ = typ.canonical + raise "About to add upper bound #{self} <= #{typ}" if typ.is_a?(VarType) && !typ.to_infer if propagate add_and_propagate_upper_bound(typ, ast, new_cons) elsif !@ubounds.any? { |t, a| t == typ } - #puts "1. About to add upper bound #{self} <= #{typ}" new_cons[self] = new_cons[self] ? new_cons[self] | [[:upper, typ, ast]] : [[:upper, typ, ast]] @ubounds << [typ, ast] #unless @ubounds.any? { |t, a| t == typ } end end def add_lbound(typ, ast, new_cons = {}, propagate: false) - raise "ABOUT TO ADD LBOUND #{typ} <= #{self}" if typ.is_a?(VarType) && !typ.to_infer - #typ = typ.canonical + raise "About to add lower bound #{typ} <= #{self}" if typ.is_a?(VarType) && !typ.to_infer if propagate add_and_propagate_lower_bound(typ, ast, new_cons) elsif !@lbounds.any? { |t, a| t == typ } - #puts "2. About to add lower bound #{typ} <= #{self}" - #raise "blah" if typ.to_s == "Array" new_cons[self] = new_cons[self] ? new_cons[self] | [[:lower, typ, ast]] : [[:lower, typ, ast]] @lbounds << [typ, ast] #unless @lbounds.any? { |t, a| t == typ } end diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 3f5a47a5..708d02f6 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -3,10 +3,9 @@ RDL.type_params :Array, [:t], :all? def Array.to_type(t) - case t - when RDL::Type::Type + if t.is_a?(RDL::Type::Type) t - when Array + elsif t.is_a?(Array) RDL.type_cast(RDL::Type::TupleType.new(*(t.map { |i| to_type(RDL.type_cast(i, "Object")) })), "RDL::Type::TupleType", force: true) else t = "nil" if t.nil? @@ -313,7 +312,7 @@ def Array.each_arg2(trec) RDL.type :Array, :each_index, '() -> Enumerator' RDL.type :Array, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``', effect: [:+, :+] RDL.type :Array, :fetch, '(Integer) -> ``output_type(trec, targs, :[], :promoted_param, "t")``' -RDL.type :Array, :fetch, '(Integer, u) -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :[], :promoted_param, "t"))``' +RDL.type :Array, :fetch, '(Integer, %any) -> ``RDL::Type::UnionType.new(targs[1], output_type(trec, targs, :[], :promoted_param, "t"))``' RDL.type :Array, :fetch, '(Integer) { (Integer) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :[], :promoted_param, "t"))``' RDL.type :Array, :fill, '(``any_or_t(trec)``) -> ``fill_output(trec, targs)``' @@ -341,7 +340,7 @@ def Array.fill_output(trec, targs) RDL.type :Array, :fill, '() { (Integer) -> ``promoted_or_t(trec)`` } -> ``promote_tuple!(trec)``' RDL.type :Array, :fill, '(Integer, ?Integer) { (Integer) -> ``promoted_or_t(trec)`` } -> ``promote_tuple!(trec)``' RDL.type :Array, :fill, '() { (Range) -> ``promoted_or_t(trec)`` } -> ``promote_tuple!(trec)``' -RDL.type :Array, :flatten, '() -> Array<%any>' # Can't give a more precise RDL.type +RDL.type :Array, :flatten, '(?Integer) -> Array<%any>' # Can't give a more precise RDL.type RDL.type :Array, :index, '(u) -> ``t = output_type(trec, targs, :index, "Integer", use_sing_val: false, nil_false_default: true)``' RDL.type :Array, :index, '() { (``promoted_or_t(trec)``) -> %bool } -> Integer' RDL.type :Array, :index, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' @@ -386,6 +385,7 @@ def Array.include_output(trec, targs) RDL.type :Array, :product, '(*Array) -> ``RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(promoted_or_t(trec), RDL::Globals.parser.scan_str("#T u"))))``' RDL.type :Array, :rassoc, '(u) -> ``promoted_or_t(trec)``' RDL.type :Array, :reject, '() { (``promoted_or_t(trec)``) -> %bool } -> ``promote_tuple(trec)``' +RDL.type :Array, :reject, '() { () -> %bool } -> ``promote_tuple(trec)``' RDL.type :Array, :reject, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :reject!, '() { (``promoted_or_t(trec)``) -> %bool } -> ``promote_tuple!(trec)``' RDL.type :Array, :reject!, '() -> Enumerator' diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index a8ab9264..4c886a6d 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -259,7 +259,8 @@ def Hash.delete_output(trec, targs, block) RDL.type :Hash, :fetch, '(``any_or_k(trec)``) -> ``output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true)``' #RDL.type :Hash, :fetch, '(``any_or_k(trec)``, u) -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``, ``targs[1] ? targs[1] : RDL::Globals.types[:top]``) -> ``RDL::Type::UnionType.new(targs[1] ? targs[1] : RDL::Globals.types[:top], output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' -RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``RDL::Type::OptionalType.new(any_or_k(trec))``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' +RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``any_or_k(trec)``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' +RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { () -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :member?, '(%any) -> ``output_type(trec, targs, :member?, "%bool")``' RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``', effect: [:+, :+] RDL.type :Hash, :key?, '(%any) -> ``output_type(trec, targs, :key?, "%bool")``' diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index d79aed2a..e60e6337 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -30,7 +30,8 @@ RDL.type :Kernel, 'self.caller', '(Range) -> Array or nil' RDL.type :Kernel, 'self.caller_locations', '(?Integer start, ?Integer length) -> Array or nil' RDL.type :Kernel, 'self.caller_locations', '(Range) -> Array or nil' - RDL.type :Kernel, 'self.catch', "(x) { (?x) -> u } -> u" +RDL.type :Kernel, 'self.catch', "(x) { (?x) -> u } -> u" + RDL.type :Kernel, 'self.dup', '() -> self' RDL.type :Kernel, 'self.eval', '(String, ?Binding, ?String filename, ?Integer lineno) -> %any' # RDL.type :Kernel, 'self.exec' #TODO RDL.type :Kernel, 'self.exit', '() -> %bot' @@ -39,7 +40,7 @@ RDL.type :Kernel, 'self.fail', '() -> %bot' RDL.type :Kernel, 'self.fail', '(String) -> %bot' RDL.type :Kernel, 'self.fail', '(Class, Array) -> %bot' -RDL.type :Kernel, 'self.fail', '(Class, String, Array) -> %bot' +RDL.type :Kernel, 'self.fail', '(Class, String, ?Array) -> %bot' # RDL.type :Kernel, 'self.fail', '(String or [exception : () -> String], ?String, ?Array) -> %any' # RDL.type :Kernel, 'self.fork' #TODO RDL.type :Kernel, 'self.format', '(String format, *%any args) -> String' @@ -49,6 +50,7 @@ RDL.type :Kernel, 'self.instance_variable_set', '(Symbol or String, %any) -> Object', wrap: false # returns 2nd argument RDL.type :Kernel, 'self.iterator?', '() -> %bool' # RDL.type :Kernel, 'self.lambda' # TODO +RDL.type :Kernel, 'self.kind_of?', '(Class or Module) -> %bool' RDL.type :Kernel, 'self.load', '(String filename, ?%bool) -> %bool' RDL.type :Kernel, 'self.local_variables', '() -> Array' # RDL.type :Kernel, 'self.loop' #TODO @@ -71,6 +73,7 @@ RDL.type :Kernel, 'self.readlines', '(?String, ?Integer) -> Array' RDL.type :Kernel, 'self.require', '(String name) -> %bool' RDL.type :Kernel, 'self.require_relative', '(String name) -> %bool' +RDL.type :Kernel, 'self.respond_to?', '(String or Symbol) -> %bool' RDL.type :Kernel, 'self.select', '(Array read, ?Array write, ?Array error, ?Integer timeout) -> Array' # TODO: return RDL.type? # RDL.type :Kernel, 'self.set_trace_func' #TODO From a65b15448397324cb3e280443a0cd036f6cb1032 Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Tue, 28 Jan 2020 23:12:49 -0500 Subject: [PATCH 016/124] add read write effects to method info --- lib/rdl/wrap.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 92245a34..d13970b7 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -352,6 +352,8 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: # end RDL::Globals.info.add(klass, meth, :type, type) RDL::Globals.info.add(klass, meth, :effect, effect) + read.each { |r| RDL::Globals.info.add(klass, meth, :read, r) } + write.each { |w| RDL::Globals.info.add(klass, meth, :write, w) } unless RDL::Globals.info.set(klass, meth, :typecheck, typecheck) raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}" end From 9030c75547ae09872ed71273cc328ed9da3ace8d Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Fri, 31 Jan 2020 23:18:26 -0500 Subject: [PATCH 017/124] effect leq for synthesis --- lib/rdl/typecheck.rb | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index e257e13b..14f2935d 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -272,6 +272,45 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) RDL::Globals.info.set(klass, meth, :typechecked, true) end + def self.syn_effect_leq(e1, e2) + # TODO: unhandled right now: Singleton classes and self + case e1.size + when 0 + case e2.size + when 0 + true + when 1, 2 + false + else + raise RuntimeError, "unexpected effect format" + end + when 1 + case e2.size + when 0 + true + when 1 + e1.first == e2.first + when 2 + false + else + raise RuntimeError, "unexpected effect format" + end + when 2 + case e2.size + when 0 + true + when 1 + true + when 2 + e1 == e2 + else + raise RuntimeError, "unexpected effect format" + end + else + raise RuntimeError, "unexpected effect format" + end + end + def self.effect_leq(e1, e2) raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) } p1, t1 = e1 From d2f36e6091c87ac8014bc7ca3f3ff705e226d05c Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Wed, 5 Feb 2020 13:47:41 -0500 Subject: [PATCH 018/124] Revert "add read write effects to method info" This reverts commit a65b15448397324cb3e280443a0cd036f6cb1032. --- lib/rdl/wrap.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index d13970b7..92245a34 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -352,8 +352,6 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: # end RDL::Globals.info.add(klass, meth, :type, type) RDL::Globals.info.add(klass, meth, :effect, effect) - read.each { |r| RDL::Globals.info.add(klass, meth, :read, r) } - write.each { |w| RDL::Globals.info.add(klass, meth, :write, w) } unless RDL::Globals.info.set(klass, meth, :typecheck, typecheck) raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}" end From 7fe91dc8e0112178775d5ca2d69b8cbc40086a0b Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Thu, 6 Feb 2020 11:59:11 -0500 Subject: [PATCH 019/124] such a confused soul --- lib/rdl/typecheck.rb | 39 --------------------------------------- lib/rdl/wrap.rb | 2 ++ 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 14f2935d..e257e13b 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -272,45 +272,6 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) RDL::Globals.info.set(klass, meth, :typechecked, true) end - def self.syn_effect_leq(e1, e2) - # TODO: unhandled right now: Singleton classes and self - case e1.size - when 0 - case e2.size - when 0 - true - when 1, 2 - false - else - raise RuntimeError, "unexpected effect format" - end - when 1 - case e2.size - when 0 - true - when 1 - e1.first == e2.first - when 2 - false - else - raise RuntimeError, "unexpected effect format" - end - when 2 - case e2.size - when 0 - true - when 1 - true - when 2 - e1 == e2 - else - raise RuntimeError, "unexpected effect format" - end - else - raise RuntimeError, "unexpected effect format" - end - end - def self.effect_leq(e1, e2) raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) } p1, t1 = e1 diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 92245a34..d13970b7 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -352,6 +352,8 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: # end RDL::Globals.info.add(klass, meth, :type, type) RDL::Globals.info.add(klass, meth, :effect, effect) + read.each { |r| RDL::Globals.info.add(klass, meth, :read, r) } + write.each { |w| RDL::Globals.info.add(klass, meth, :write, w) } unless RDL::Globals.info.set(klass, meth, :typecheck, typecheck) raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}" end From 7f66dd9b50e3d78610121c51713d840b7746a72c Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 13 Feb 2020 09:58:45 -0500 Subject: [PATCH 020/124] fixes for test failures --- lib/rdl/config.rb | 3 ++- lib/rdl/typecheck.rb | 18 ++++++++---------- lib/rdl/types/type.rb | 2 -- lib/rdl/types/union.rb | 2 +- lib/rdl/types/var.rb | 7 +++---- lib/rdl/wrap.rb | 11 ++++++----- lib/types/core/array.rb | 2 +- lib/types/core/hash.rb | 23 +++++++++++++++-------- lib/types/sequel/comp_types.rb | 2 +- test/test_array_types.rb | 11 ++++++----- test/test_hash_types.rb | 7 ++++--- test/test_le.rb | 2 -- test/test_string_types.rb | 1 + test/test_typecheck.rb | 16 ++++++++-------- 14 files changed, 56 insertions(+), 51 deletions(-) diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index f3715ee9..eff6d4ba 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -8,7 +8,7 @@ class RDL::Config attr_reader :report # writer is custom defined attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types attr_accessor :type_defaults, :infer_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type - attr_accessor :practical_infer, :use_precise_string, :number_mode + attr_accessor :practical_infer, :use_precise_string, :number_mode, :use_unknown_types def initialize @nowrap = Set.new # Set of symbols @@ -31,6 +31,7 @@ def initialize @practical_infer = true @use_precise_string = false @number_mode = false + @use_unknown_types = false end def report=(val) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index a988597c..88ad0716 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -468,7 +468,7 @@ def self.args_hash(scope, env, type, args, ast, kind) env = env.merge(Env.new(arg.children[0] => targ)) tpos += 1 unless (targ.optional? || targ.vararg?) && !(index == args.children.size - 1) else - error :type_arg_kind_mismatch, [kind, 'optional', 'required'], arg, block: (kind == 'block') if (targ.optional? && !kind == 'block') ## block arg type can be optional while actual arg is required + error :type_arg_kind_mismatch, [kind, 'optional', 'required'], arg, block: (kind == 'block') if (targ.optional? && !(kind == 'block')) ## block arg type can be optional while actual arg is required error :type_arg_kind_mismatch, [kind, 'vararg', 'required'], arg, block: (kind == 'block') if targ.vararg? targs[arg.children[0]] = targ env = env.merge(Env.new(arg.children[0] => targ)) @@ -1454,11 +1454,10 @@ def self.tc_var(scope, env, kind, name, e) type = RDL::Globals.info.get(klass, name, :type) elsif RDL::Config.instance.assume_dyn_type type = RDL::Globals.types[:dyn] - elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } + elsif RDL::Config.instance.use_unknown_types || RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } type = make_unknown_var_type(klass, name, :var) else - type = make_unknown_var_type(klass, name, :var) - #error :untyped_var, [kind_text, name, klass], e + error :untyped_var, [kind_text, name, klass], e end [env, type.canonical] else @@ -1494,11 +1493,10 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) tleft = RDL::Globals.info.get(klass, name, :type) elsif RDL::Config.instance.assume_dyn_type tleft = RDL::Globals.types[:dyn] - elsif RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } + elsif RDL::Config.instance.use_unknown_types || RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } tleft = make_unknown_var_type(klass, name, :var) else - tleft = make_unknown_var_type(klass, name, :var) - #error :untyped_var, [kind_text, name, klass], e + error :untyped_var, [kind_text, name, klass], e end error :vasgn_incompat, [tright.to_s, tleft.to_s], e unless RDL::Type::Type.leq(tright, tleft, inst={}, true, ast: e) tright.instantiate(inst) @@ -1712,7 +1710,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, case trecv when RDL::Type::SingletonType if trecv.val.is_a? Class or trecv.val.is_a? Module - if (meth == :new) && trecv.val.method(:new).owner == Class then ## second condition makes sure :new isn't overriden + if (meth == :new) && trecv.val.methods.include?(:new) && trecv.val.method(:new).owner == Class then ## last condition makes sure :new isn't overriden meth_lookup = :initialize init = true trecv_lookup = trecv.val.to_s @@ -2370,7 +2368,7 @@ def self.tc_block(scope, env, tblock, block, inst) # if included, those methods are added to instance_methods # if extended, those methods are added to singleton_methods # (except Kernel is special...) - def self.lookup(scope, klass, name, e, make_unknown: true) + def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_unknown_types) if scope[:context_types] # return array of all matching types from context_types, if any ts = [] @@ -2387,7 +2385,7 @@ def self.lookup(scope, klass, name, e, make_unknown: true) return [t, e] if t # simplest case, no need to walk inheritance hierarchy return [[make_unknown_method_type(klass, name)]] if RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } klass_name = RDL::Util.has_singleton_marker(klass) ? RDL::Util.remove_singleton_marker(klass) : klass - return [[make_unknown_method_type(klass, name)]] if (name == :initialize) && (RDL::Util.to_class(klass_name).instance_method(:initialize).owner == RDL::Util.to_class(klass_name)) # don't want to walk up hierarchy in initialize case where it is specifically defined for this class + return [[make_unknown_method_type(klass, name)]] if make_unknown && (name == :initialize) && (RDL::Util.to_class(klass_name).instance_method(:initialize).owner == RDL::Util.to_class(klass_name)) # don't want to walk up hierarchy in initialize case where it is specifically defined for this class the_klass = RDL::Util.to_class(klass) is_singleton = RDL::Util.has_singleton_marker(klass) included = RDL::Util.to_class(klass.gsub("[s]", "")).included_modules diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index d9dedcc1..1e7672e1 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -74,10 +74,8 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con right = right.type if right.is_a? NonNullType left = left.canonical right = right.canonical - return true if left.equal?(right) - # top and bottom return true if left.is_a? BotType return true if right.is_a? TopType diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index 7929fea2..84554df0 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -46,7 +46,7 @@ def canonicalize! # for any type such that a supertype is already in ts, set its position to nil for i in 0..(@types.length-1) for j in (i+1)..(@types.length-1) - next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType) && @types[i].to_infer) || (@types[j].is_a?(VarType) && @types[j].to_infer) || @types[i].is_a?(ChoiceType) || @types[j].is_a?(ChoiceType) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union + next if (@types[j].nil?) || (@types[i].nil?) || @types[i].is_a?(ChoiceType) || @types[j].is_a?(ChoiceType) || @types[i].is_a?(VarType) || @types[j].is_a?(VarType)#(@types[i].is_a?(VarType) && (@types[i].to_infer || @types[j].is_a?(VarType))) || (@types[j].is_a?(VarType) && @types[j].to_infer)## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union #next if (@types[j].nil?) || (@types[i].nil?) || (@types[i].is_a?(VarType)) || (@types[j].is_a?(VarType)) ## now that we're doing inference, don't want to just treat VarType as a subtype of others in Union (@types[i] = nil; break) if Type.leq(@types[i], @types[j], {}, true, []) (@types[j] = nil) if Type.leq(@types[j], @types[i], {}, true, []) diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index 2ff9ca47..84923fd9 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -61,7 +61,7 @@ def add_and_propagate_upper_bound(typ, ast, new_cons = {}) new_cons[self] = new_cons[self] ? new_cons[self] | [[:upper, typ, ast]] : [[:upper, typ, ast]] end @lbounds.each { |lower_t, a| - if lower_t.is_a?(VarType) + if lower_t.is_a?(VarType) && lower_t.to_infer lower_t.add_and_propagate_upper_bound(typ, ast, new_cons) unless lower_t.ubounds.any? { |t, _| t == typ } else if typ.is_a?(VarType) && !typ.lbounds.any? { |t, _| t == lower_t } @@ -79,7 +79,6 @@ def add_and_propagate_upper_bound(typ, ast, new_cons = {}) ## Similar to above. def add_and_propagate_lower_bound(typ, ast, new_cons = {}) - raise if typ.to_s == "v" return if self.equal?(typ) if !@lbounds.any? { |t, a| t == typ } @lbounds << [typ, ast] @@ -102,7 +101,7 @@ def add_and_propagate_lower_bound(typ, ast, new_cons = {}) end def add_ubound(typ, ast, new_cons = {}, propagate: false) - raise "About to add upper bound #{self} <= #{typ}" if typ.is_a?(VarType) && !typ.to_infer + #raise "About to add upper bound #{self} <= #{typ}" if typ.is_a?(VarType) && !typ.to_infer if propagate add_and_propagate_upper_bound(typ, ast, new_cons) elsif !@ubounds.any? { |t, a| t == typ } @@ -112,7 +111,7 @@ def add_ubound(typ, ast, new_cons = {}, propagate: false) end def add_lbound(typ, ast, new_cons = {}, propagate: false) - raise "About to add lower bound #{typ} <= #{self}" if typ.is_a?(VarType) && !typ.to_infer + #raise "About to add lower bound #{typ} <= #{self}" if typ.is_a?(VarType) && !typ.to_infer if propagate add_and_propagate_lower_bound(typ, ast, new_cons) elsif !@lbounds.any? { |t, a| t == typ } diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 4d0796d5..75d31621 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -828,6 +828,7 @@ def self.do_typecheck(sym) def self.do_infer(sym) return unless RDL::Globals.to_infer[sym] + RDL::Config.instance.use_unknown_types = true $stn = 0 num_casts = 0 time = Time.now @@ -1096,11 +1097,11 @@ def method_added(meth) } end -#class Class -# def ===(x) -# if x.method(:is_a?).owner == SimpleDelegator then super(x.__getobj__) else super(x) end -# end -#end +class Class + def ===(x) + if x.method(:is_a?).owner == SimpleDelegator then super(x.__getobj__) else super(x) end + end +end class SimpleDelegator ## pass methods through to wrapped object diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 708d02f6..2484830a 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -125,7 +125,7 @@ def Array.plus_input(targs) when RDL::Type::TupleType return targs[0] when RDL::Type::GenericType, RDL::Type::VarType - parse_string = defined?(Rails) ? "Array or ActiveRecord::Relation" : "Array" + parse_string = defined?(Rails) && targs[0].is_a?(RDL::Type::VarType) ? "Array or ActiveRecord::Relation" : "Array" x = RDL::Globals.parser.scan_str "#T #{parse_string}" x else diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 4c886a6d..56cc99a4 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -58,14 +58,20 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_de def Hash.to_type(t) - case t - when RDL::Type::Type + if t.is_a?(RDL::Type::Type) t - when Array + elsif t.is_a? Array RDL::Type::TupleType.new(*(t.map { |i| to_type(i) })) - else - ## symbols, ints, nil, ... + elsif t.is_a? Numeric + if RDL::Config.instance.number_mode + RDL::Type::NominalType.new(Integer) + else + RDL::Type::SingletonType.new(t) + end + elsif t.is_a?(Symbol) || t.is_a?(TrueClass) || t.is_a?(FalseClass) || t.is_a?(Module) RDL::Type::SingletonType.new(t) + else + RDL::Type::NominalType.new(t.class) end end RDL.type Hash, 'self.to_type', "(%any) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] @@ -261,6 +267,7 @@ def Hash.delete_output(trec, targs, block) RDL.type :Hash, :fetch, '(``any_or_k(trec)``, ``targs[1] ? targs[1] : RDL::Globals.types[:top]``) -> ``RDL::Type::UnionType.new(targs[1] ? targs[1] : RDL::Globals.types[:top], output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``any_or_k(trec)``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { () -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' +RDL.type :Hash, :first, '() -> ``output_type(trec, targs, :first, :default_or_promoted_val, "v", nil_default: true)``', effect: [:+, :+] RDL.type :Hash, :member?, '(%any) -> ``output_type(trec, targs, :member?, "%bool")``' RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``', effect: [:+, :+] RDL.type :Hash, :key?, '(%any) -> ``output_type(trec, targs, :key?, "%bool")``' @@ -298,7 +305,7 @@ def Hash.merge_input(trec, targs, mutate=false) case targs[0] when RDL::Type::FiniteHashType return targs[0] - else #when RDL::Type::GenericType + when RDL::Type::GenericType, RDL::Type::VarType if mutate raise "Unable to promote #{trec}." if trec.is_a?(RDL::Type::FiniteHashType) && !trec.promote! return trec.canonical @@ -310,8 +317,8 @@ def Hash.merge_input(trec, targs, mutate=false) return targs[0] end end - #else - # RDL::Globals.types[:hash] + else + RDL::Globals.types[:hash] end end RDL.type Hash, 'self.merge_input', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] diff --git a/lib/types/sequel/comp_types.rb b/lib/types/sequel/comp_types.rb index fe4db6b9..9d683e43 100644 --- a/lib/types/sequel/comp_types.rb +++ b/lib/types/sequel/comp_types.rb @@ -175,7 +175,7 @@ def self.join_ret_type(trec, targs) qual_table_schema = get_schema(RDL::Globals.seq_db_schema[qual_table].elts) raise RDL::Typecheck::StaticTypeError, "No column #{qual_column} in table #{qual_table}." if qual_table_schema[qual_column].nil? else - raise "Unexpected column #{rec_join_column} to join on" + raise "Unexpected column #{rec_join_column} of class #{rec_join_column.class} to join on" end case targ1 when RDL::Type::SingletonType diff --git a/test/test_array_types.rb b/test/test_array_types.rb index 0c25b8cf..fbc4eb99 100644 --- a/test/test_array_types.rb +++ b/test/test_array_types.rb @@ -9,6 +9,7 @@ class TestArrayTypes < Minitest::Test def setup RDL.reset RDL.readd_comp_types + RDL::Config.instance.use_precise_string = false RDL.type_params :Array, [:t], :all? unless RDL::Globals.type_params["Array"] RDL.type_params(:Range, [:t], nil, variance: [:+]) { |t| t.member?(self.begin) && t.member?(self.end) } unless RDL::Globals.type_params["Range"] end @@ -36,7 +37,7 @@ def access_test4(a) a[3] end - type '([1,2,3]) -> Array<3 or 2 or 1>', typecheck: :now + type '([1,2,3]) -> Array', typecheck: :now def access_test5(a) a[1..4] end @@ -61,7 +62,7 @@ def first_test1(a) a.first end - type '([1,2,3], Integer) -> Array<3 or 2 or 1>', typecheck: :now + type '([1,2,3], Integer) -> Array', typecheck: :now def first_test2(a, i) a.first(i) end @@ -71,7 +72,7 @@ def mult_test1 [1,2,3]*2 end - type '([1,2,3], Integer) -> Array<3 or 2 or 1>', typecheck: :now + type '([1,2,3], Integer) -> Array', typecheck: :now def mult_test2(a, i) a * i end @@ -109,7 +110,7 @@ def append_fail_test1(a) a << 4 true end - + type '([1,2,3], [4,5,String]) -> [1,2,3,4,5,String]', typecheck: :now def plus_test1(x, y) x+y @@ -161,7 +162,7 @@ def assign_test1(x) x end - type '(Integer, [1,2,3]) -> Array<1 or 2 or 3 or String>', typecheck: :now + type '(Integer, [1,2,3]) -> Array', typecheck: :now def assign_test2(i, x) x[i] = "hi" x diff --git a/test/test_hash_types.rb b/test/test_hash_types.rb index e5f8af3a..f808bfd3 100644 --- a/test/test_hash_types.rb +++ b/test/test_hash_types.rb @@ -16,6 +16,7 @@ def setup def test_hash_methods self.class.class_eval { + type '({bar: Integer, baz: String}) -> String', typecheck: :now def access_test1(x) x[:baz] @@ -32,7 +33,7 @@ def access_test3(y, x) x[y] end - type '(Hash) -> v', typecheck: :now + type '(Hash) -> %bot', typecheck: :now def access_test4(x) x[:blah] end @@ -102,7 +103,7 @@ def merge_test6(x, y) y.merge(x) end - type '(Hash, { bar: Integer, baz: String }) -> Hash', typecheck: :now + type '(Hash, { bar: Integer, baz: String }) -> Hash', typecheck: :now def merge_test7(x, y) x.merge(y) end @@ -112,7 +113,7 @@ def merge_test8(x, y) y.merge(x) end - type '(Hash, Hash) -> Hash', typecheck: :now + type '(Hash, Hash) -> Hash', typecheck: :now def merge_test9(x, y) x.merge(y) end diff --git a/test/test_le.rb b/test/test_le.rb index 736c65a6..d5d2c01b 100644 --- a/test/test_le.rb +++ b/test/test_le.rb @@ -303,7 +303,6 @@ def test_nominal_structural def test_leq_inst RDL.type_params :Array, [:t], :all? RDL.type_params :Hash, [:k, :v], :all? - # when return of do_leq is false, ignore resulting inst, since that's very implementation dependent assert_equal [true, {t: @ta}], do_leq(tt("t"), @ta, true) assert_equal false, do_leq(tt("t"), @ta, false)[0] @@ -324,7 +323,6 @@ def test_leq_inst assert_equal [true, {t: RDL::Globals.types[:integer], u: RDL::Globals.types[:string]}], do_leq(tt("Hash"), tt("Hash"), true) assert_equal [true, {t: RDL::Globals.types[:integer]}], do_leq(tt("Hash"), tt("Hash"), true) assert_equal false, do_leq(tt("Hash"), tt("Hash"), true)[0] - assert_equal false, do_leq(tt("[m:()->t]"), tt("[m:()->Integer]"), true)[0] # no inst inside structural types end def do_leq(tleft, tright, ileft) diff --git a/test/test_string_types.rb b/test/test_string_types.rb index 8f8b341a..0c19649e 100644 --- a/test/test_string_types.rb +++ b/test/test_string_types.rb @@ -9,6 +9,7 @@ class TestStringTypes < Minitest::Test def setup RDL.reset + RDL::Config.instance.use_precise_string = true RDL.readd_comp_types end diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 5807c6ff..7f370cc8 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -531,7 +531,7 @@ def test_ivar_ivasgn assert do_tc("@foo", env: @env) <= RDL::Globals.types[:integer] assert do_tc("@@foo", env: @env) <= RDL::Globals.types[:integer] assert do_tc("$test_ivar_ivasgn_global") <= RDL::Globals.types[:integer] - assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("@bar", env: @env) } + assert_raises(RDL::Typecheck::StaticTypeError) { x = do_tc("@bar", env: @env) } assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("@bar", env: @env) } assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("@@bar", env: @env) } assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("$_test_ivar_ivasgn_global_2") } @@ -793,9 +793,9 @@ def test_send_union type :_send_union1, "(Integer) -> Float" type :_send_union1, "(String) -> Rational" } - assert do_tc("(if _any_object then Integer.new else String.new end) * 2", env: @env) <= RDL::Type::UnionType.new(@tfs, RDL::Globals.types[:integer]) - assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("(if _any_object then Object.new else Integer.new end) + 2", env: @env) } - assert do_tc("if _any_object then x = Integer.new else x = String.new end; _send_union1(x)", env: @env) <= tt("Float or Rational") + assert do_tc("(if _any_object then 6 else String.new end) * 2", env: @env) <= RDL::Type::UnionType.new(@tfs, RDL::Globals.types[:integer]) + assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("(if _any_object then Object.new else 5 end) + 2", env: @env) } + assert do_tc("if _any_object then x = 5 else x = String.new end; _send_union1(x)", env: @env) <= tt("Float or Rational") end def test_send_splat @@ -953,7 +953,7 @@ def _block_arg9() # end def test_new - assert do_tc("B.new", env: @env) <= RDL::Type::NominalType.new(TestTypecheck::B) + assert do_tc("TestTypecheck::B.new", env: @env) <= RDL::Type::NominalType.new(TestTypecheck::B) assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("B.new(3)", env: @env) } end @@ -1068,7 +1068,7 @@ def test_while_until def test_for assert do_tc("for i in 1..5 do end; i") <= RDL::Globals.types[:integer] - assert do_tc("for i in [1,2,3,4,5] do end; i") <= tt("1 or 2 or 3 or 4 or 5") + assert do_tc("for i in [1,2,3,4,5] do end; i") <= RDL::Globals.types[:integer] ## TODO: figure out why above fails to terminate assert do_tc("for i in 1..5 do break end", env: @env) <= tt("Range") assert do_tc("for i in 1..5 do next end", env: @env) <= tt("Range") @@ -1212,12 +1212,12 @@ def test_instantiate RDL.type :Array, :initialize, '() -> self', wrap: false RDL.type :Array, :initialize, '(Integer) -> self', wrap: false RDL.type :Array, :initialize, '(Integer, t) -> self', wrap: false - assert_raises(RDL::Typecheck::StaticTypeError) { + assert ( self.class.class_eval { type "(Integer, Integer) -> Array", typecheck: :now def def_inst_fail(x, y) a = Array.new(x,y); a; end } - } + ) assert ( self.class.class_eval { type "(Integer, Integer) -> Array", typecheck: :now From da86622701e69706f88421cdb00ba75853493aba Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Tue, 3 Mar 2020 19:25:44 -0500 Subject: [PATCH 021/124] add code to treat empty array, has with type var --- lib/rdl/config.rb | 12 +++---- lib/rdl/typecheck.rb | 78 +++++++++++++++++++++------------------- lib/types/core/kernel.rb | 4 +-- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index eff6d4ba..fa31c7f9 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -8,7 +8,7 @@ class RDL::Config attr_reader :report # writer is custom defined attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types attr_accessor :type_defaults, :infer_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type - attr_accessor :practical_infer, :use_precise_string, :number_mode, :use_unknown_types + attr_accessor :use_precise_string, :number_mode, :use_unknown_types, :infer_empties def initialize @nowrap = Set.new # Set of symbols @@ -28,10 +28,10 @@ def initialize @use_comp_types = true @check_comp_types = false ## this for dynamically checking that the result of a computed type still holds @rerun_comp_types = false ## this is for dynamically checking that a type computation still evaluates to the same thing as it did at type checking time - @practical_infer = true @use_precise_string = false @number_mode = false @use_unknown_types = false + @infer_empties = true ## if [] and {} should be typed as Array and Hash end def report=(val) @@ -237,7 +237,7 @@ def do_get_meth_type(klass, meth, the_meth) CSV.open("#{File.basename(Dir.getwd)}_observed_types.csv", "a+") { |csv| csv << [klass.to_s, meth.to_s, param_names.join(", "), printed_args, otret.to_s, the_meth.source, the_meth.comment] } - + end @@ -281,7 +281,7 @@ def guess_meth(klass, meth, is_sing, the_meth) otypes.each { |ot| ot[:args].each_with_index { |t, i| otargs[i] = RDL::Globals.types[:bot] if otargs[i].nil? - begin + begin otargs[i] = RDL::Type::UnionType.new(otargs[i], t).canonical rescue NameError; end } @@ -327,12 +327,12 @@ def do_get_types CSV.open("#{File.basename(Dir.getwd)}_observed_types.csv", "wb") { |csv| csv << ["Class", "Method", "Parameter Names", "Observed Arg Types", "Observed Return Type", "Source Code", "Comments"] } - + RDL::Config.instance.get_types.each { |klass, meth| the_klass = RDL::Util.to_class(klass) sklass = RDL::Util.add_singleton_marker(klass.to_s) wrapped_name = RDL::Wrap.wrapped_name(klass, meth) - begin + begin the_meth = RDL::Util.to_class(klass).instance_method(wrapped_name) do_get_meth_type(klass, meth, the_meth) rescue NameError; end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 88ad0716..723e5dcc 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -239,11 +239,11 @@ def self.infer(klass, meth) arg_types = meth_type.args block_type = meth_type.block - ret_vartype = meth_type.ret + ret_vartype = meth_type.ret - raise "Expected VarTypes in MethodType to be inferred, got #{meth_type}." unless (arg_types + [block_type] + [ret_vartype]).all? { |t| !t.nil? && (t.kind_of_var_input? || (meth == :initialize)) } + raise "Expected VarTypes in MethodType to be inferred, got #{meth_type}." unless (arg_types + [block_type] + [ret_vartype]).all? { |t| !t.nil? && (t.kind_of_var_input? || (meth == :initialize)) } end - + if ast.type == :def name, args, body = *ast elsif ast.type == :defs @@ -267,7 +267,7 @@ def self.infer(klass, meth) _, targs = args_hash({}, Env.new(inst), meth_type, args, ast, 'method') targs[:self] = self_type - scope = { tret: meth_type.ret, tblock: meth_type.block, captured: Hash.new, context_types: context_types } + scope = { task: :infer, tret: meth_type.ret, tblock: meth_type.block, captured: Hash.new, context_types: context_types } begin old_captured = scope[:captured].dup @@ -288,13 +288,13 @@ def self.infer(klass, meth) else RDL::Type::Type.leq(body_type, ret_vartype, ast: ast) end - + RDL::Globals.info.set(klass, meth, :typechecked, true) RDL::Globals.constrained_types << [klass, meth] puts "Done with constraint generation." end - + def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) @cur_meth = [klass, meth] ast = get_ast(klass, meth) unless ast @@ -303,7 +303,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) if effects.empty? || effects[0] == nil effect = nil else - effect = [:+, :+] + effect = [:+, :+] effects.each { |e| effect = effect_union(effect, e) unless e.nil? } ## being very lazy about this right now, conservatively taking the union of all effects if there are multiple ones end raise RuntimeError, "Can't typecheck method with no types?!" if types.nil? or types == [] @@ -333,7 +333,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) type = type.instantiate inst _, targs = args_hash({}, Env.new(:self => self_type), type, args, ast, 'method') targs[:self] = self_type - scope = { tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types, eff: effect } + scope = { task: :check, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types, eff: effect } begin old_captured = scope[:captured].dup if body.nil? @@ -358,7 +358,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) def self.get_num_casts return @num_casts end - + def self.effect_leq(e1, e2) raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) } p1, t1 = e1 @@ -435,7 +435,7 @@ def self.widen_scopes(h1, h2) end } } - [h1new, h2new] + [h1new, h2new] end def self.widen_envs(e1, e2) @@ -656,6 +656,8 @@ def self.tc(scope, env, e) } if is_array [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(*tis).canonical), effi] + elsif scope[:task] == :infer && RDL::Config.instance.infer_empties && tis.empty? + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new("array_param_" + e.loc.to_s)), effi] else [envi, RDL::Type::TupleType.new(*tis), effi] end @@ -696,7 +698,11 @@ def self.tc(scope, env, e) if is_fh # keys are all symbols fh = tlefts.map { |t| t.val }.zip(trights).to_h - [envi, RDL::Type::FiniteHashType.new(fh, nil), effi] + if scope[:task] == :infer && RDL::Config.instance.infer_empties && fh.empty? + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new("hash_param_key_" + e.loc.to_s), RDL::Type::VarType.new("hash_param_val_" + e.loc.to_s)), effi] + else + [envi, RDL::Type::FiniteHashType.new(fh, nil), effi] + end else tleft = RDL::Type::UnionType.new(*tlefts) tright = RDL::Type::UnionType.new(*trights) @@ -713,7 +719,7 @@ def self.tc(scope, env, e) error :nonmatching_range_type, [t1, t2], e unless t1 <= t2 || t2 <= t1 [env2, RDL::Type::GenericType.new(RDL::Globals.types[:range], t1), effect_union(eff1, eff2)] when :self - [env, env[:self], [:+, :+]] + [env, env[:self], [:+, :+]] when :lvar, :ivar, :cvar, :gvar if e.type == :lvar then eff = [:+, :+] else eff = [:-, :+] end tc_var(scope, env, e.type, e.children[0], e) + [eff] @@ -1125,7 +1131,7 @@ def self.tc(scope, env, e) envelse, telse, effelse = tc(scope, envi, e.children[-1]) effi = effect_union(effi, effelse) tbodies << telse - #envelse = envelse.bind(e.children[0].children[0], tcontrol, force: :true) if e.children[0].type == :lvar + #envelse = envelse.bind(e.children[0].children[0], tcontrol, force: :true) if e.children[0].type == :lvar envbodies << envelse end return [Env.join(e, *envbodies), RDL::Type::UnionType.new(*tbodies).canonical, effi] @@ -1392,7 +1398,7 @@ def self.tc(scope, env, e) sklass_str = RDL::Util.to_class_str sklass stype = lookup(scope, sklass_str, mname, e)[0]#RDL::Globals.info.get_with_aliases(sklass_str, mname, :type) error :no_instance_method_type, [sklass_str, mname], e unless stype - stype = filter_comp_types(stype, RDL::Config.instance.use_comp_types) + stype = filter_comp_types(stype, RDL::Config.instance.use_comp_types) raise Exception, "unsupported intersection type in super, e = #{e}, stype = #{stype}" if stype.size > 1 tactuals = stype[0].args @@ -1410,7 +1416,7 @@ def self.to_type(val, as_key=false) case val when Symbol if as_key then val else RDL::Type::SingletonType.new(val) end - when TrueClass, FalseClass, Class, Module + when TrueClass, FalseClass, Class, Module RDL::Type::SingletonType.new(val) when Complex, Rational, Integer, Float if RDL::Config.instance.number_mode# && !(val.class == Integer) @@ -1653,7 +1659,7 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) t = t.canonical tarms = t.is_a?(RDL::Type::UnionType) ? t.types : [t] tarms.each { |t| - ts, es = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) + ts, es = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) choice_hash[num] = RDL::Type::UnionType.new(*ts).canonical } rescue StaticTypeError => err @@ -1735,7 +1741,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, if env[:self].is_a?(RDL::Type::NominalType) klass = env[:self].klass #elsif env[:self].is_a?( - elsif env[:self].is_a?(RDL::Type::SingletonType) # SingletonType(class) + elsif env[:self].is_a?(RDL::Type::SingletonType) # SingletonType(class) klass = env[:self].val else raise "unsupported self type for to_proc #{env[:self]}" @@ -1801,7 +1807,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, error :no_instance_method_type, ["Hash", meth], e unless ts #inst = trecv.to_inst.merge(self: trecv) k_bind = trecv.promote.params[0].to_s == "k" ? RDL::Globals.types[:bot] : trecv.promote.params[0] - v_bind = trecv.promote.params[1].to_s == "v" ? RDL::Globals.types[:bot] : trecv.promote.params[1] + v_bind = trecv.promote.params[1].to_s == "v" ? RDL::Globals.types[:bot] : trecv.promote.params[1] inst = { self: trecv , k: k_bind, v: v_bind } self_klass = Hash else @@ -1838,7 +1844,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, else t end } - + if meth == :to_s ret_type = RDL::Globals.types[:string] elsif meth == :to_i @@ -1865,20 +1871,20 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, meth_type = RDL::Type::MethodType.new(tactuals, block_type, ret_type) tmeth_inst = tc_arg_types(meth_type, tactuals) - + raise "Expected method to be instantiated." unless tmeth_inst tc_block(scope, env, block_type, block, tmeth_inst) end else meth_type = RDL::Type::MethodType.new(tactuals, nil, ret_type) end - + RDL::Type::Type.leq(trecv, RDL::Type::StructuralType.new({ meth => meth_type }), ast: e) - #tmeth_inter = [meth_type] - + #tmeth_inter = [meth_type] + #self_klass = nil #error :recv_var_type, [trecv], e return [[ret_type]] @@ -1902,7 +1908,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, arg_choices = Hash.new { |h, k| h[k] = {} } # Hash>. The keys are tactual arguments that are VarTypes. The values are choice hashes, to be turned into ChoiceTypes that will be upper bounds on the keys. ret_choice = {} # Hash for return ChoiceType deferred_constraints = [] ## Array> of deferred constraints to apply. - + # for ALL of the expanded lists of actuals... if RDL::Config.instance.use_comp_types ts = filter_comp_types(ts, true) @@ -1915,7 +1921,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, # AT LEAST ONE of the possible intesection arms must match trets_tmp = [] #deferred_constraints = [] - choice_num = 0 + choice_num = 0 ts.each_with_index { |tmeth, ind| # MethodType got_match = false current_dcs = [] @@ -1951,7 +1957,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, block_mismatch = true end if es - es = es.map { |es_effect| if es_effect.nil? then es_effect else es_effect.clone end } + es = es.map { |es_effect| if es_effect.nil? then es_effect else es_effect.clone end } es.each { |es_effect| ## expecting just one effect per method right now. can clean this up later. if !es_effect.nil? && (es_effect[1] == :blockdep || es_effect[0] == :blockdep) #raise "Got block-dependent effect for method #{meth}, but no block." unless block && effblock @@ -2002,7 +2008,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, block_mismatch = false end end - + if got_match deferred_constraints += current_dcs if ts.size > 1 && tactuals_expanded.any? { |t| t.is_a?(RDL::Type::VarType) } @@ -2024,7 +2030,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ret_choice[choice_num] = RDL::Type::UnionType.new(current_ret, ret_choice[choice_num]).canonical end end - + } if trets_tmp.empty? # no arm of the intersection matched this expanded actuals lists, so reset trets to signal error and break loop @@ -2063,8 +2069,8 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, trets = [new_ret] apply_deferred_constraints(new_dcs, e) if !new_dcs.empty? end - - + + if trets.empty? # no possible matching call msg = < 1 ? - return [trets, es] + return [trets, es] end def self.apply_deferred_constraints(deferred_constraints, e) @@ -2209,7 +2215,7 @@ def self.tc_arg_types(tmeth, tactuals, deferred_constraints=[]) states << [formal+1, actual+1, inst, deferred_constraints] # match else states << [formal+1, actual, inst, deferred_constraints] # types don't match; must skip this formal - end + end when RDL::Type::VarargType if actual == tactuals.size states << [formal+1, actual, inst, deferred_constraints] # skip to allow empty vararg at end @@ -2279,7 +2285,7 @@ def self.tc_bind_arg_types(tmeth, tactuals) elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false, []) #&& #RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true, []) states << [formal+1, actual+1, inst, binds] # match, no more varargs; no other choices! - states << [formal, actual+1, inst, binds] + states << [formal, actual+1, inst, binds] else states << [formal+1, actual, inst, binds] # doesn't match, must skip end @@ -2498,7 +2504,7 @@ def self.make_unknown_method_type(klass, meth) end block_type = RDL::Type::VarType.new(cls: klass, meth: meth, category: :block, name: "block") - + meth_type = RDL::Type::MethodType.new(arg_types, block_type, ret_vartype) RDL::Globals.info.add(klass, meth, :type, meth_type) return meth_type @@ -2510,7 +2516,7 @@ def self.make_unknown_var_type(klass, name, kind_text) RDL::Globals.constrained_types << [klass, name] return var_type end - + def self.filter_comp_types(ts, use_dep_types) return nil unless ts dep_ts = [] @@ -2824,7 +2830,7 @@ def on_and_asgn(node) end end - + def initialize(offset) @offset = offset end diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index e60e6337..6ddd304a 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -1,7 +1,7 @@ RDL.nowrap :Kernel -# RDL.type :Kernel, 'self.Array', '([to_ary: () -> Array]) -> Array' -# RDL.type :Kernel, 'self.Array', '([to_a: () -> Array]) -> Array' +RDL.type :Kernel, 'self.Array', '([to_ary: () -> Array]) -> Array' +RDL.type :Kernel, 'self.Array', '([to_a: () -> Array]) -> Array' RDL.type :Kernel, 'self.===', "(%any) -> %bool" RDL.type :Kernel, 'self.Complex', '(Numeric x, Numeric y) -> Complex' RDL.type :Kernel, 'self.Complex', '(String x) -> Complex' From 9838b5824660a6b489e0bebdb53e1424f4c4491e Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Tue, 3 Mar 2020 19:38:14 -0500 Subject: [PATCH 022/124] start of infer tests --- test/test_infer.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 test/test_infer.rb diff --git a/test/test_infer.rb b/test/test_infer.rb new file mode 100644 index 00000000..d0d74182 --- /dev/null +++ b/test/test_infer.rb @@ -0,0 +1,26 @@ +require 'minitest/autorun' +$LOAD_PATH << File.dirname(__FILE__) + "/../lib" +require 'rdl' +require 'types/core' + +class TestTypecheck < Minitest::Test + extend RDL::Annotate + + def setup + RDL.reset + end + + # [+ a +] is the environment, a map from symbols to types; empty if omitted + # [+ expr +] is a string containing the expression to typecheck + # returns the type of the expression + def do_tc(expr, scope: Hash.new, env: RDL::Typecheck::Env.new) + ast = Parser::CurrentRuby.parse expr + t = RDL::Globals.types[:bot] + return t + end + + # convert arg string to a type + def tt(t) + RDL::Globals.parser.scan_str('#T ' + t) + end +end From c20fa7c28a20d6d5f630263d8258a03eda294aff Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 10:33:59 -0500 Subject: [PATCH 023/124] travis ruby versions --- .travis.yml | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fdcb0b4..69f56240 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,22 +2,6 @@ language: ruby gemfile: - gemfiles/Gemfile.travis rvm: - - 2.1.1 - - 2.1.6 - - 2.1.7 - - 2.1.8 - - 2.2.0 - - 2.2.1 - - 2.2.2 - - 2.2.3 - - 2.2.4 - - 2.2.5 - - 2.2.6 - - 2.3.0 - - 2.3.1 - - 2.3.2 - - 2.3.3 - - 2.3.4 - 2.4.0 - 2.4.1 - 2.4.2 @@ -27,6 +11,9 @@ rvm: - 2.5.5 - 2.6.2 - 2.6.3 + - 2.6.4 + - 2.6.5 + - 2.7.0 notifications: email: recipients: From 330257306cffb222b352b9f52cc6a29be058c0fc Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 10:53:38 -0500 Subject: [PATCH 024/124] fix some warnings --- lib/rdl/util.rb | 4 ++-- lib/rdl/wrap.rb | 25 ++++++++++++------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/rdl/util.rb b/lib/rdl/util.rb index 55274cc5..0ca754ba 100644 --- a/lib/rdl/util.rb +++ b/lib/rdl/util.rb @@ -31,11 +31,11 @@ def self.to_class_str(cls) end def self.has_singleton_marker(klass) - return (klass =~ /^#{SINGLETON_MARKER_REGEXP}/) + return (klass.to_s =~ /^#{SINGLETON_MARKER_REGEXP}/) end def self.remove_singleton_marker(klass) - if klass =~ /^#{SINGLETON_MARKER_REGEXP}(.*)/ + if klass.to_s =~ /^#{SINGLETON_MARKER_REGEXP}(.*)/ return $1 else return nil diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 75d31621..0f82b6da 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -29,7 +29,7 @@ def self.wrap(klass_str, meth) RDL::Globals.wrap_switch.off { klass_str = klass_str.to_s klass = RDL::Util.to_class klass_str - the_meth = klass.instance_method(meth) + # the_meth = klass.instance_method(meth) return if wrapped? klass, meth return if RDL::Config.instance.nowrap.member? klass_str.to_sym raise ArgumentError, "Attempt to wrap #{RDL::Util.pp_klass_method(klass, meth)}" if klass.to_s =~ /^RDL::/ @@ -237,7 +237,7 @@ def self.val_to_type(val) RDL::Type::NominalType.new(val.class) end end - + # called by Object#method_added (sing=false) and Object#singleton_method_added (sing=true) def self.do_method_added(the_self, sing, klass, meth) if sing @@ -303,7 +303,7 @@ def self.do_method_added(the_self, sing, klass, meth) if RDL::Globals.to_typecheck[:now].member? [klass, meth] RDL::Globals.to_typecheck[:now].delete [klass, meth] RDL::Typecheck.typecheck(klass, meth) - end + end if RDL::Config.instance.guess_types.include?(the_self.to_s.to_sym) && !RDL::Globals.info.has?(klass, meth, :type) # Added a method with no type annotation from a class we want to guess types for @@ -448,7 +448,7 @@ def orig_type(klass=self, meth, type, wrap: nil, typecheck: nil) def orig_var_type(klass=self, var, type) raise RuntimeError, "Variable cannot begin with capital" if var.to_s =~ /^[A-Z]/ return if var.to_s =~ /^[a-z]/ # local variables handled specially, inside type checker - klass = RDL::Util::GLOBAL_NAME if var.to_s =~ /^\$/ + klass = RDL::Util::GLOBAL_NAME if var.to_s =~ /^\$/ unless RDL::Globals.info.set(klass, var, :orig_type, RDL::Globals.parser.scan_str("#T #{type}")) raise RuntimeError, "Type already declared for #{var}" end @@ -497,7 +497,7 @@ def infer(*args, time: RDL::Config.instance.infer_defaults[:time]) RDL::Globals.to_infer[time].add([klass, meth]) else RDL::Globals.deferred << [klass, :infer, time, { }] - + end nil end @@ -588,7 +588,7 @@ def create_binding_meth(klass, meth) puts "ABOUT TO EVALUATE #{meth_string}" RDL.class_eval meth_string end - end + end def no_infer_meth(klass, meth) RDL::Globals.no_infer_meths << [klass.to_s, meth.to_s] @@ -597,7 +597,7 @@ def no_infer_meth(klass, meth) def no_infer_file(path) RDL::Globals.no_infer_files << Pathname.new(path).expand_path.to_s end - + # [+ klass +] is the class containing the variable; self if omitted; ignored for local and global variables # [+ var +] is a symbol or string containing the name of the variable # [+ typ +] is a string containing the type @@ -622,7 +622,7 @@ def infer_var_type(klass=self, var) ## solution extract for. VarTypes in methods get added to this list after calling RDL.do_infer. ## Not sure when/where to add variable VarTypes so I'm doing it here. RDL::Globals.constrained_types << [klass, var] - nil + nil end # In the following three methods @@ -960,7 +960,7 @@ def self.check_type_code end eval tmp_eval ast = Parser::CurrentRuby.parse tmp_meth - RDL::Typecheck.typecheck("[s]#{klass}", "tc_#{meth}#{count}".to_sym, ast, [code_type], [[:-, :+]]) + RDL::Typecheck.typecheck("[s]#{klass}", "tc_#{meth}#{count}".to_sym, ast, [code_type], [[:-, :+]]) count += 1 end } @@ -1079,14 +1079,14 @@ def singleton_method_added(meth) sklass = RDL::Util.add_singleton_marker(klass) RDL::Wrap.do_method_added(self, true, sklass, meth) nil - end + end end class Module define_method :singleton_method_added, Object.instance_method(:singleton_method_added) - RDL::Util.silent_warnings { - + RDL::Util.silent_warnings { + def method_added(meth) klass = self.to_s klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main") @@ -1135,4 +1135,3 @@ def nil? end end - From f1f5e72c47bed674e53d4a3aa26c099df34865d3 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 10:55:22 -0500 Subject: [PATCH 025/124] fix some warnings --- lib/types/core/hash.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 56cc99a4..943e4799 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -4,13 +4,13 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_default: false, use_sing_val: true) case trec - when RDL::Type::FiniteHashType + when RDL::Type::FiniteHashType if targs.empty? || targs.all? { |t| t.is_a?(RDL::Type::SingletonType) } vals = RDL.type_cast((if use_sing_val then targs.map { |t| RDL.type_cast(t, "RDL::Type::SingletonType").val } else targs end), "Array<%any>", force: true) res = RDL.type_cast(trec.elts.send(meth_name, *vals), "Object", force: true) if nil_default && res.nil? if default1 == :promoted_val - ret = trec.promote.params[1] + # ret = trec.promote.params[1] return trec.promote.params[1] elsif default1 == :promoted_key return trec.promote.params[0] @@ -215,7 +215,7 @@ def Hash.assign_output(trec, targs) RDL.type :Hash, :store, '(``any_or_k(trec)``, ``any_or_v(trec)``) -> ``assign_output(trec, targs)``' RDL.type :Hash, :assoc, '(``any_or_k(trec)``) -> ``RDL::Type::TupleType.new(targs[0], output_type(trec, targs, :[], :promoted_val, "v", nil_default: true))``' -RDL.type :Hash, :clear, '() -> self' +RDL.type :Hash, :clear, '() -> self' RDL.type :Hash, :compare_by_identity, '() -> self' RDL.type :Hash, :compare_by_identity?, '() -> %bool' RDL.type :Hash, :default, '() -> ``promoted_or_v(trec)``' @@ -285,7 +285,7 @@ def Hash.invert_output(trec) hash = Hash[hash.map { |k, v| if !RDL.type_cast(v, "Object", force: true).is_a?(RDL::Type::Type) then [k, RDL::Type::SingletonType.new(v)] else [k, v] end }] RDL::Type::FiniteHashType.new(RDL.type_cast(hash, "Hash<%any, RDL::Type::Type>", force: true), nil) else - RDL::Type::GenericType.new(RDL::Globals.types[:hash], trec.params[1], trec.params[0]) + RDL::Type::GenericType.new(RDL::Globals.types[:hash], trec.params[1], trec.params[0]) #RDL::Globals.parser.scan_str "#T Hash" end end @@ -371,7 +371,7 @@ def Hash.merge_output(trec, targs, mutate=false) value_union = RDL::Type::UnionType.new(promoted.params[1], arg0.params[1]).canonical if mutate raise "Unable to promote tuple #{trec} to Hash." unless trec.promote!(arg0.params[0], arg0.params[1]) - return trec + return trec else return RDL::Type::GenericType.new(arg0.base, key_union, value_union) end @@ -545,4 +545,3 @@ def Hash.values_at_output(trec, targs) RDL.type :Hash, :values, '() -> Array' RDL.type :Hash, :values_at, '(*k) -> Array', effect: [:+, :+] RDL.type :Hash, :with_indifferent_access, '() -> self' - From 2471e2c202e60492255a5a21a3287b6c52dad5ae Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 10:56:58 -0500 Subject: [PATCH 026/124] Don't call infer tests typecheck tests --- test/test_infer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_infer.rb b/test/test_infer.rb index d0d74182..ed274d74 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -3,7 +3,7 @@ require 'rdl' require 'types/core' -class TestTypecheck < Minitest::Test +class TestInfer < Minitest::Test extend RDL::Annotate def setup From 1170fb158af765d703c38f2484aae47d63a71b3e Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 10:59:44 -0500 Subject: [PATCH 027/124] Don't call infer tests typecheck tests --- lib/rdl/types/method.rb | 2 -- test/test_typecheck.rb | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index afaa333a..a066f289 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -120,7 +120,6 @@ def pre_cond?(blk, slf, inst, bind, *args) bind.local_variable_set(t.name.to_sym,args[actual]) preds.push(t) t = t.type.instantiate(inst) - the_actual = nil if actual == args.size next unless t.instance_of? FiniteHashType if t.member?({}, vars_wild: true) # try matching against the empty hash @@ -133,7 +132,6 @@ def pre_cond?(blk, slf, inst, bind, *args) else t = NominalType.new 'Proc' if t.instance_of? MethodType t = t.instantiate(inst) - the_actual = nil if actual == args.size next unless t.instance_of? FiniteHashType if t.member?({}, vars_wild: true) # try matching against the empty hash diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 7f370cc8..e42755e1 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -149,7 +149,7 @@ def setup RDL.readd_comp_types RDL.type_params :Hash, [:k, :v], :all? unless RDL::Globals.type_params["Hash"] RDL.type_params :Array, [:t], :all? unless RDL::Globals.type_params["Array"] - RDL.rdl_alias :Array, :size, :length + RDL.rdl_alias :Array, :size, :length RDL.type_params 'RDL::Type::SingletonType', [:t], :satisfies? unless RDL::Globals.type_params["RDL::Type::SingletonType"] =begin RDL.type_params :Array, [:t], :all? @@ -531,7 +531,7 @@ def test_ivar_ivasgn assert do_tc("@foo", env: @env) <= RDL::Globals.types[:integer] assert do_tc("@@foo", env: @env) <= RDL::Globals.types[:integer] assert do_tc("$test_ivar_ivasgn_global") <= RDL::Globals.types[:integer] - assert_raises(RDL::Typecheck::StaticTypeError) { x = do_tc("@bar", env: @env) } + assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("@bar", env: @env) } assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("@bar", env: @env) } assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("@@bar", env: @env) } assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("$_test_ivar_ivasgn_global_2") } @@ -774,7 +774,7 @@ def test_send_method_generic type :_send_method_generic4, '(t) { (t) -> t } -> t' type :_send_method_generic5, '() { (u) -> u } -> u' type :_send_method_generic6, '() { (Integer) -> u } -> u' - } + } assert do_tc('_send_method_generic1 3', env: @env) <= @t3 assert do_tc('_send_method_generic1 "foo"', env: @env) <= RDL::Globals.types[:string] assert do_tc('_send_method_generic2 3, "foo"', env: @env) <= tt("3 or String") @@ -1975,7 +1975,7 @@ def self.baz end end - + def test_sing_method_inheritence RDL.type SingletonInheritA, 'self.foo', '(Integer) -> Integer' self.class.class_eval do @@ -2036,7 +2036,7 @@ def self.gen_input_type(targs) return RDL::Globals.types[:integer] else raise RDL::Typecheck::StaticTypeError, "Unexpected input type." - end + end end type '(Integer, String) -> Integer', typecheck: :now From 9f22b20981f4a674d56d5f9862b88b02d19b4538 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 11:45:21 -0500 Subject: [PATCH 028/124] fix more warnings --- lib/rdl/config.rb | 6 +++--- lib/rdl/constraint.rb | 30 +++++++++++++++--------------- lib/rdl/typecheck.rb | 10 +++++----- lib/rdl/wrap.rb | 1 - 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index fa31c7f9..8feb6673 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -203,7 +203,7 @@ def do_get_meth_type(klass, meth, the_meth) param_types = params.keep_if { |p| p.size == 2 }.to_h param_names = [] params.each_with_index { |param, i| - kind, name = param + _, name = param ## TODO: Differentiate based on kind here? ## TODO: Anything with block here? param_names << name if name @@ -329,8 +329,8 @@ def do_get_types } RDL::Config.instance.get_types.each { |klass, meth| - the_klass = RDL::Util.to_class(klass) - sklass = RDL::Util.add_singleton_marker(klass.to_s) + # the_klass = RDL::Util.to_class(klass) + # sklass = RDL::Util.add_singleton_marker(klass.to_s) wrapped_name = RDL::Wrap.wrapped_name(klass, meth) begin the_meth = RDL::Util.to_class(klass).instance_method(wrapped_name) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 2b313b64..0ee27be4 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -15,9 +15,9 @@ def self.resolve_constraints else var_types = [typ] end - + var_types.each { |var_type| - if var_type.var_type? || var_type.optional_var_type? || var_type.vararg_var_type? + if var_type.var_type? || var_type.optional_var_type? || var_type.vararg_var_type? var_type = var_type.type if var_type.optional_var_type? || var_type.vararg_var_type? var_type.lbounds.each { |lower_t, ast| var_type.add_and_propagate_lower_bound(lower_t, ast) @@ -33,7 +33,7 @@ def self.resolve_constraints } vt.ubounds.each { |upper_t, ast| vt.add_and_propagate_upper_bound(upper_t, ast) - } + } } else raise "Got unexpected type #{var_type}." @@ -71,7 +71,7 @@ def self.extract_var_sol(var, category) else raise "Unexpected VarType category #{category}." end - if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) + if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) ## Try each rule. Return first non-nil result. ## If no non-nil results, return original solution. ## TODO: check constraints. @@ -93,7 +93,7 @@ def self.extract_var_sol(var, category) var.ubounds.each { |t, _| puts t } } } -=end +=end @new_constraints = true if !new_cons.empty? return typ #sol = typ @@ -107,7 +107,7 @@ def self.extract_var_sol(var, category) } end ## out here, none of the heuristics applied. - ## Try to use `sol` as solution -- there is a chance it will + ## Try to use `sol` as solution -- there is a chance it will begin new_cons = {} sol = var if sol == RDL::Globals.types[:bot] # just use var itself when result of solution extraction was %bot. @@ -178,7 +178,7 @@ def self.extract_meth_sol(tmeth) if tmeth.block && !tmeth.block.ubounds.empty? non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } non_vartype_ubounds.reject! { |t| t.is_a?(RDL::Type::StructuralType) }#&& (t.methods.size == 1) && (t.methods.has_key?(:to_proc) || t.methods.has_key?(:call)) } - if non_vartype_ubounds.size == 0 + if non_vartype_ubounds.size == 0 block_sol = tmeth.block elsif non_vartype_ubounds.size > 1 block_sols = [] @@ -205,7 +205,7 @@ def self.extract_meth_sol(tmeth) return [arg_sols, block_sol, ret_sol] end - + def self.extract_solutions ## Go through once to come up with solution for all var types. #until !@new_constraints @@ -220,7 +220,7 @@ def self.extract_solutions if typ.is_a?(Array) raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 tmeth = typ[0] - + arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) block_string = block_sol ? " { #{block_sol} }" : nil @@ -250,13 +250,13 @@ def self.extract_solutions #return unless $orig_types - complete_types = [] - incomplete_types = [] + # complete_types = [] + # incomplete_types = [] CSV.open("infer_data.csv", "wb") { |csv| csv << ["Class", "Method", "Inferred Type", "Original Type", "Source Code", "Comments"] } - + correct_types = 0 total_potential = 0 meth_types = 0 @@ -299,7 +299,7 @@ def self.extract_solutions total_potential += 1 var_types += 1 end - + end if !meth.to_s.include?("@") && !meth.to_s.include?("$")#orig_typ.is_a?(RDL::Type::MethodType) @@ -332,6 +332,6 @@ def self.extract_solutions puts "Total # individual types: #{total_potential}" end - - + + end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 723e5dcc..e743beed 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -817,7 +817,7 @@ def self.tc(scope, env, e) lhs.zip(new_tuple).each { |left, right| envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left) } - tuple_type = RDL::Type::TupleType.new(*new_tuple) + #tuple_type = RDL::Type::TupleType.new(*new_tuple) #RDL::Type::Type.leq(tright, tuple_type, ast: e) # don't think this is necessary [envi, tright, effi] end @@ -1202,7 +1202,7 @@ def self.tc(scope, env, e) case tcollect when RDL::Type::NominalType self_klass = tcollect.klass - teaches, eeaches = lookup(scope, tcollect.name, :each, e.children[1]) + teaches, _ = lookup(scope, tcollect.name, :each, e.children[1]) teaches = filter_comp_types(teaches, RDL::Config.instance.use_comp_types) when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::FiniteHashType, RDL::Type::PreciseStringType unless tcollect.is_a? RDL::Type::GenericType @@ -1210,7 +1210,7 @@ def self.tc(scope, env, e) tcollect = tcollect.canonical end self_klass = tcollect.base.klass - teaches, eeaches = lookup(scope, tcollect.base.name, :each, e.children[1]) + teaches, _ = lookup(scope, tcollect.base.name, :each, e.children[1]) teaches = filter_comp_types(teaches, RDL::Config.instance.use_comp_types) inst = tcollect.to_inst.merge(self: tcollect) teaches = teaches.map { |typ| @@ -1659,7 +1659,7 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) t = t.canonical tarms = t.is_a?(RDL::Type::UnionType) ? t.types : [t] tarms.each { |t| - ts, es = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) + ts, _ = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) choice_hash[num] = RDL::Type::UnionType.new(*ts).canonical } rescue StaticTypeError => err @@ -1953,7 +1953,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, if tmeth_inst begin effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block - rescue BlockTypeError => err + rescue BlockTypeError => _ block_mismatch = true end if es diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 0f82b6da..c2f2285b 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -837,7 +837,6 @@ def self.do_infer(sym) num_casts += RDL::Typecheck.get_num_casts } RDL::Globals.to_infer[sym] = Set.new - nil RDL::Typecheck.resolve_constraints RDL::Typecheck.extract_solutions time = Time.now - time From 12d36ee1c0b9a74e6cb1c64e2017e2d8ed5885ef Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Wed, 4 Mar 2020 11:58:52 -0500 Subject: [PATCH 029/124] get rid of @cur_meth, add klass/meth to scope --- lib/rdl/typecheck.rb | 50 ++++++++++++++++++++---------------------- test/test_typecheck.rb | 2 ++ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 723e5dcc..171def2f 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -221,7 +221,6 @@ def self.infer(klass, meth) puts "*************** Infering method #{meth} from class #{klass} ***************" RDL::Config.instance.use_comp_types = true RDL::Config.instance.number_mode = true - @cur_meth = [klass, meth] @var_cache = {} ast = get_ast(klass, meth) types = RDL::Globals.info.get(klass, meth, :type) @@ -267,7 +266,7 @@ def self.infer(klass, meth) _, targs = args_hash({}, Env.new(inst), meth_type, args, ast, 'method') targs[:self] = self_type - scope = { task: :infer, tret: meth_type.ret, tblock: meth_type.block, captured: Hash.new, context_types: context_types } + scope = { task: :infer, klass: klass, meth: meth, tret: meth_type.ret, tblock: meth_type.block, captured: Hash.new, context_types: context_types } begin old_captured = scope[:captured].dup @@ -296,7 +295,6 @@ def self.infer(klass, meth) end def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) - @cur_meth = [klass, meth] ast = get_ast(klass, meth) unless ast types = RDL::Globals.info.get(klass, meth, :type) unless types effects = RDL::Globals.info.get(klass, meth, :effect) unless effects @@ -333,7 +331,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) type = type.instantiate inst _, targs = args_hash({}, Env.new(:self => self_type), type, args, ast, 'method') targs[:self] = self_type - scope = { task: :check, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types, eff: effect } + scope = { task: :check, klass: klass, meth: meth, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types, eff: effect } begin old_captured = scope[:captured].dup if body.nil? @@ -724,7 +722,7 @@ def self.tc(scope, env, e) if e.type == :lvar then eff = [:+, :+] else eff = [:-, :+] end tc_var(scope, env, e.type, e.children[0], e) + [eff] when :lvasgn, :ivasgn, :cvasgn, :gvasgn - if e.type == :lvasgn || @cur_meth[1] == :initialize then eff = [:+, :+] else eff = [:-, :+] end + if e.type == :lvasgn || scope[:meth] == :initialize then eff = [:+, :+] else eff = [:-, :+] end x = e.children[0] # if local var, lhs is bound to nil before assignment is executed! only matters in type checking for locals env = env.bind(x, RDL::Globals.types[:nil]) if ((e.type == :lvasgn) && (not (env.has_key? x))) @@ -734,7 +732,7 @@ def self.tc(scope, env, e) # (masgn (mlhs (Xvasgn var-name) ... (Xvasgn var-name)) rhs) effi = [:+, :+] e.children[0].children.each { |asgn| - effi = effect_union(effi, [:-, :+]) if asgn.type != :lvasgn && @cur_meth != :initialize + effi = effect_union(effi, [:-, :+]) if asgn.type != :lvasgn && scope[:meth] != :initialize next unless asgn.type == :lvasgn x = e.children[0] env = env.bind(x, RDL::Globals.types[:nil]) if (not (env.has_key? x)) # see lvasgn @@ -798,22 +796,22 @@ def self.tc(scope, env, e) if splat_ind > 0 lhs[0..splat_ind-1].each { |left| # before splat - envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "tuple_element_#{count}"), left) + envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :tuple_element, name: "tuple_element_#{count}"), left) count += 1 } end lhs[splat_ind+1..-1].reverse_each { |left| # after splat - envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "tuple_element_#{count}"), left) + envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :tuple_element, name: "tuple_element_#{count}"), left) count += 1 } splat = lhs[splat_ind] - envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "array_param_#{count}")), splat) if splat.children.any? ## could be empty splat + envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :tuple_element, name: "array_param_#{count}")), splat) if splat.children.any? ## could be empty splat [envi, tright, effi] else new_tuple = [] count = 0 - lhs.length.times { new_tuple << RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :tuple_element, name: "tuple_element_#{count}") } + lhs.length.times { new_tuple << RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :tuple_element, name: "tuple_element_#{count}") } lhs.zip(new_tuple).each { |left, right| envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left) } @@ -921,7 +919,7 @@ def self.tc(scope, env, e) elsif e.children[0].nil? && e.children[1] == :ARGV c = RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Globals.types[:string]) else - c = to_type(find_constant(env, e)) + c = to_type(find_constant(env, scope, e)) end [env, c, [:+, :+]] when :defined? @@ -1015,7 +1013,7 @@ def self.tc(scope, env, e) eff = [:+, :+] e.children[0..-1].each { |ei| envi, ti, effi = tc(scope, envi, ei); tactuals << ti ; eff = effect_union(effi, eff)} if scope[:tblock].is_a?(RDL::Type::VarType) - block_ret_type = RDL::Type::VarType.new(cls: @cur_meth[0], meth: @cur_meth[1], category: :block_ret, name: "block_return") + block_ret_type = RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :block_ret, name: "block_return") block_type = RDL::Type::MethodType.new(tactuals, nil, block_ret_type) RDL::Type::Type.leq(scope[:tblock], block_type, ast: e) return [envi, block_ret_type, eff] @@ -1380,8 +1378,8 @@ def self.tc(scope, env, e) end } - trecv = get_super_owner(envi[:self], @cur_meth[1]) - tres, effres = tc_send(sscope, envi, trecv, @cur_meth[1], tactuals, block, e) + trecv = get_super_owner(envi[:self], scope[:meth]) + tres, effres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) [envi, tres.canonical, effect_union(effi, effres)] } when :zsuper @@ -1392,8 +1390,8 @@ def self.tc(scope, env, e) raise Exception, 'super method not supported' end =end - klass = RDL::Util.to_class @cur_meth[0] - mname = @cur_meth[1] + klass = RDL::Util.to_class scope[:klass] + mname = scope[:meth] sklass = get_super_owner_from_class klass, mname sklass_str = RDL::Util.to_class_str sklass stype = lookup(scope, sklass_str, mname, e)[0]#RDL::Globals.info.get_with_aliases(sklass_str, mname, :type) @@ -1403,8 +1401,8 @@ def self.tc(scope, env, e) tactuals = stype[0].args scope_merge(scope, block: nil, break: env, next: env) { |sscope| - trecv = get_super_owner(envi[:self], @cur_meth[1]) - tres, effres = tc_send(sscope, envi, trecv, @cur_meth[1], tactuals, block, e) + trecv = get_super_owner(envi[:self], scope[:meth]) + tres, effres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) [envi, tres.canonical, effres] } else @@ -2550,16 +2548,16 @@ def self.get_singleton_name(name) end - def self.find_constant(env, e) + def self.find_constant(env, scope, e) # https://cirw.in/blog/constant-lookup.html # First look in Module.nesting for a lexically scoped variable const_string = e.loc.expression.source - raise "Expected @cur_meth." unless @cur_meth - if (RDL::Util.has_singleton_marker(@cur_meth[0])) - klass = RDL::Util.to_class(RDL::Util.remove_singleton_marker(@cur_meth[0])) + raise "Expected class and method in `scope`." unless scope[:klass] && scope[:meth] + if (RDL::Util.has_singleton_marker(scope[:klass])) + klass = RDL::Util.to_class(RDL::Util.remove_singleton_marker(scope[:klass])) mod_inst = false else - klass = RDL::Util.to_class(@cur_meth[0]) + klass = RDL::Util.to_class(scope[:klass]) if klass.instance_of?(Module) mod_inst = true else @@ -2567,10 +2565,10 @@ def self.find_constant(env, e) klass = klass.send :allocate end end - if RDL::Wrap.wrapped?(@cur_meth[0], @cur_meth[1]) - meth_name = RDL::Wrap.wrapped_name(@cur_meth[0], @cur_meth[1]) + if RDL::Wrap.wrapped?(scope[:klass], scope[:meth]) + meth_name = RDL::Wrap.wrapped_name(scope[:klass], scope[:meth]) else - meth_name = @cur_meth[1] + meth_name = scope[:meth] end if mod_inst method = klass.instance_method(meth_name) diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 7f370cc8..d87e53b2 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -218,6 +218,8 @@ def setup # returns the type of the expression def do_tc(expr, scope: Hash.new, env: RDL::Typecheck::Env.new) ast = Parser::CurrentRuby.parse expr + scope[:klass] ||= "TestTypecheck" + scope[:meth] ||= :do_tc _, t = RDL::Typecheck.tc scope, env, ast return t end From efc538a5529b7c8e4632fcf62f93d92b8d2ac57a Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Wed, 4 Mar 2020 12:07:33 -0500 Subject: [PATCH 030/124] change naming of array/hash param VarTypes --- lib/rdl/typecheck.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index a71cdd37..a9b409f9 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -655,7 +655,7 @@ def self.tc(scope, env, e) if is_array [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(*tis).canonical), effi] elsif scope[:task] == :infer && RDL::Config.instance.infer_empties && tis.empty? - [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new("array_param_" + e.loc.to_s)), effi] + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :array_param, name: "array_param_#{e.loc}")), effi] else [envi, RDL::Type::TupleType.new(*tis), effi] end @@ -697,7 +697,8 @@ def self.tc(scope, env, e) # keys are all symbols fh = tlefts.map { |t| t.val }.zip(trights).to_h if scope[:task] == :infer && RDL::Config.instance.infer_empties && fh.empty? - [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new("hash_param_key_" + e.loc.to_s), RDL::Type::VarType.new("hash_param_val_" + e.loc.to_s)), effi] + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_key, name: "hash_param_key_#{e.loc}"), + RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_val, name: "hash_param_val_#{e.loc}"))] else [envi, RDL::Type::FiniteHashType.new(fh, nil), effi] end From 6b413ddf03f412590feca130d6e9b37280e53c01 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 13:51:16 -0500 Subject: [PATCH 031/124] fix a couple of tests --- lib/rdl/typecheck.rb | 16 ++++++++-------- test/test_typecheck.rb | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index a9b409f9..aeb94756 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -894,13 +894,13 @@ def self.tc(scope, env, e) else # e.type == :or_asgn if tleft.val then [envleft, tleft] else [envright, tright] end end - else - if trecv.is_a?(RDL::Type::VarType) - ## we get no new information from including VarType in union of return type. In fact, we can lose info due to promotion. So, leave it out. - [envright, tright] - else - [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical] - end + else + if trecv.is_a?(RDL::Type::VarType) + ## we get no new information from including VarType in union of return type. In fact, we can lose info due to promotion. So, leave it out. + [envright, tright] + else + [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical] + end end) if e.children[0].type == :send mutation_meth = (meth.to_s + '=').to_sym @@ -2704,7 +2704,7 @@ def align_combine(range, offset, attributes) raise IndexError, "The range #{range} is outside the bounds of the source of size #{@source_buffer.source.size}" end dummy_range = Parser::Source::Range.new(@source_buffer, range.begin_pos - offset, range.end_pos - offset) - action = TreeRewriter::Action.new(dummy_range, @enforcer, attributes) + action = TreeRewriter::Action.new(dummy_range, @enforcer, **attributes) @action_root = @action_root.combine(action) self end diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 81900527..1150b73f 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -1821,7 +1821,7 @@ def foo(x) when :b end end - type(:foo, '(Symbol) -> NilClass', {:typecheck => :call}) + type(:foo, '(Symbol) -> NilClass', :typecheck => :call) end assert_nil TestTypecheck::A5.new.foo(:a) From 05d925445c8e7072fc7d61d8dd1a4eee179202e6 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 13:57:27 -0500 Subject: [PATCH 032/124] bugfix --- lib/rdl/typecheck.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index aeb94756..2fbe64e3 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -698,7 +698,7 @@ def self.tc(scope, env, e) fh = tlefts.map { |t| t.val }.zip(trights).to_h if scope[:task] == :infer && RDL::Config.instance.infer_empties && fh.empty? [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_key, name: "hash_param_key_#{e.loc}"), - RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_val, name: "hash_param_val_#{e.loc}"))] + RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_val, name: "hash_param_val_#{e.loc}")), effi] else [envi, RDL::Type::FiniteHashType.new(fh, nil), effi] end From eaa01b1bfdfe9039b7a0b966ad7612091aeefd41 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 4 Mar 2020 14:29:00 -0500 Subject: [PATCH 033/124] fix a 2.7.0 nit --- test/test_lib_types.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_lib_types.rb b/test/test_lib_types.rb index 6b127d20..0f70bddb 100644 --- a/test/test_lib_types.rb +++ b/test/test_lib_types.rb @@ -106,10 +106,10 @@ def test_bigmath # From the Ruby stdlib documentation BigMath.E(10) BigMath.PI(10) - BigMath.atan(BigDecimal.new('-1'), 16) + BigMath.atan(BigDecimal('-1'), 16) BigMath.cos(BigMath.PI(4), 16) BigMath.sin(BigMath.PI(5)/4, 5) - BigMath.sqrt(BigDecimal.new('2'), 16) + BigMath.sqrt(BigDecimal('2'), 16) end def test_class From ddae8db25a635e5ea673c976af43f6fcd849058e Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Mon, 6 Apr 2020 21:25:02 -0400 Subject: [PATCH 034/124] fix printing solution --- lib/rdl/constraint.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 0ee27be4..1f35e494 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -252,7 +252,7 @@ def self.extract_solutions # complete_types = [] # incomplete_types = [] - + CSV.open("infer_data.csv", "wb") { |csv| csv << ["Class", "Method", "Inferred Type", "Original Type", "Source Code", "Comments"] } @@ -306,12 +306,12 @@ def self.extract_solutions CSV.open("infer_data.csv", "a+") { |csv| ast = RDL::Typecheck.get_ast(klass, meth) code = ast.loc.expression.source - if RDL::Util.has_singleton_marker(klass) - comment = RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)).method(meth).comment - else - comment = RDL::Util.to_class(klass).instance_method(meth).comment - end - csv << [klass, meth, typ, orig_typ, code, comment] + # if RDL::Util.has_singleton_marker(klass) + # comment = RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)).method(meth).comment + # else + # comment = RDL::Util.to_class(klass).instance_method(meth).comment + # end + csv << [klass, meth, typ, orig_typ, code] # , comment #if typ.include?("XXX") # incomplete_types << [klass, meth, typ, orig_typ, code, comment] #else From 9eb92dea03176f8f6bc81785c715192cc74e41f4 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Mon, 6 Apr 2020 21:36:44 -0400 Subject: [PATCH 035/124] fix reset a bit --- lib/rdl/boot.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 88dcd1c3..483f3265 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -71,13 +71,13 @@ module RDL::Globals @dep_types = [] ## Hash mapping node object IDs (integers) to a list [tmeth, tmeth_old, tmeth_res, self_klass, trecv_old, targs_old], where: tmeth is a MethodType that is fully evaluated (i.e., no ComputedTypes) *and instantiated*, tmeth_old is the unevaluated method type (i.e., with ComputedTypes), tmeth_res is the result of evaluating tmeth_old *but not instantiating it*, self_klass is the class where the MethodType is defined, trecv_old was the receiver type used to evaluate tmeth_old, and targs_old is an Array of the argument types used to evaluate tmeth_old. - @comp_type_map = {} + @comp_type_map = Hash.new # Map from ActiveRecord table names (symbols) to their schema types, which should be a Table type - @ar_db_schema = {} + @ar_db_schema = Hash.new # Map from Sequel table names (symbols) to their schema types, which should be a Table type - @seq_db_schema = {} + @seq_db_schema = Hash.new # Array<[String, String]>, where each first string is a class name and each second one is a method name. # klass/method pairs here should not be inferred. @@ -182,10 +182,17 @@ def self.reset @to_wrap = Set.new @to_typecheck = Hash.new @to_typecheck[:now] = Set.new + @to_infer = Hash.new + @to_infer[:now] = Set.new + @constrained_types = [] @to_do_at = Hash.new + @deferred = [] + # @dep_types = [] +# @comp_type_map = Hash.new @ar_db_schema = Hash.new @seq_db_schema = Hash.new - @deferred = [] + @no_infer_meths = [] + @no_infer_files = [] @parser = RDL::Type::Parser.new From e95fbbd3dae4469e539cdc0a86486796b57cf81f Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Mon, 6 Apr 2020 22:11:57 -0400 Subject: [PATCH 036/124] sub typing in optional --- lib/rdl/types/type.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 50e90b09..824cd97d 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -61,6 +61,9 @@ def self.leq(left, right, inst=nil, ileft=true, no_constraint: false) return left.name == right.name end + # optional + return leq(left, right.type, inst, ileft) if right.is_a? OptionalType + # union return left.types.all? { |t| leq(t, right, inst, ileft) } if left.is_a?(UnionType) if right.instance_of?(UnionType) From a2d8c99d3a40f218a25b45574c97f9064611c797 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Tue, 7 Apr 2020 21:41:05 -0400 Subject: [PATCH 037/124] Add RDL.infer_added, which takes a block and marks all methods created in that block as being inferred. --- lib/rdl/boot.rb | 6 ++++++ lib/rdl/wrap.rb | 33 ++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 483f3265..f3d55691 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -85,6 +85,10 @@ module RDL::Globals # Array of absolute file paths for files that should not be inferred. @no_infer_files = [] + + # If non-nil, should be a symbol. Added, untyped methods will be tagged + # with that symbol + @infer_added = nil end class << RDL::Globals # add accessors and readers for module variables @@ -104,6 +108,7 @@ class << RDL::Globals # add accessors and readers for module variables attr_accessor :seq_db_schema attr_accessor :no_infer_meths attr_accessor :no_infer_files + attr_accessor :infer_added end # Create switches to control whether wrapping happens and whether @@ -193,6 +198,7 @@ def self.reset @seq_db_schema = Hash.new @no_infer_meths = [] @no_infer_files = [] + @infer_added = nil @parser = RDL::Type::Parser.new diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index c2f2285b..58bc792e 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -240,13 +240,14 @@ def self.val_to_type(val) # called by Object#method_added (sing=false) and Object#singleton_method_added (sing=true) def self.do_method_added(the_self, sing, klass, meth) - if sing - loc = the_self.singleton_method(meth).source_location - else - loc = the_self.instance_method(meth).source_location - end - RDL::Globals.info.set(klass, meth, :source_location, loc) + if sing + loc = the_self.singleton_method(meth).source_location + else + loc = the_self.instance_method(meth).source_location + end + RDL::Globals.info.set(klass, meth, :source_location, loc) # Apply any deferred contracts and reset list + if RDL::Globals.deferred.size > 0 if sing loc = the_self.singleton_method(meth).source_location @@ -277,14 +278,20 @@ def self.do_method_added(the_self, sing, klass, meth) RDL::Globals.to_typecheck[h[:typecheck]].add([klass, meth]) end if kind == :infer - if contract == :now + if tag == :now RDL::Typecheck.infer(klass, meth) else - RDL::Globals.to_infer[contract] = Set.new unless RDL::Globals.to_infer[contract] - RDL::Globals.to_infer[contract].add([klass, meth]) + RDL::Globals.to_infer[tag] = Set.new unless RDL::Globals.to_infer[tag] + RDL::Globals.to_infer[tag].add([klass, meth]) end end } + elsif RDL::Globals.infer_added && + !(RDL::Globals.info.has_any?(klass, meth, [:typecheck, :infer])) + # Tag this method to be added to to-be-inferred set if it doesn't already have a type + tag = RDL::Globals.infer_added + RDL::Globals.to_infer[tag] = Set.new unless RDL::Globals.to_infer[tag] + RDL::Globals.to_infer[tag].add([klass, meth]) end # Wrap method if there was a prior contract for it. @@ -811,6 +818,14 @@ def self.at(sym, &blk) RDL::Globals.to_do_at[sym] << blk end + # Mark all untyped methods added in the passed block as being inferred + def self.infer_added(tag) + tmp = RDL::Globals.infer_added + RDL::Globals.infer_added = tag + yield + RDL::Globals.infer_added = tmp + end + # Invokes all callbacks from rdl_at(sym), in unspecified order. # Afterwards, type checks all methods that had annotation `typecheck: sym' at type call, in unspecified order. def self.do_typecheck(sym) From 81335101ddec686526eb739ec858202461a90d8c Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 9 Apr 2020 19:42:20 -0400 Subject: [PATCH 038/124] skip methods marked no_infer_meth --- lib/rdl/wrap.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 58bc792e..734820b3 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -288,10 +288,15 @@ def self.do_method_added(the_self, sing, klass, meth) } elsif RDL::Globals.infer_added && !(RDL::Globals.info.has_any?(klass, meth, [:typecheck, :infer])) - # Tag this method to be added to to-be-inferred set if it doesn't already have a type - tag = RDL::Globals.infer_added - RDL::Globals.to_infer[tag] = Set.new unless RDL::Globals.to_infer[tag] - RDL::Globals.to_infer[tag].add([klass, meth]) + # Tag this method to be added to to-be-inferred set if it doesn't already have a type, and as + # long as it isn't marked as being skipped + unless ((RDL::Util.has_singleton_marker(klass) && + RDL::Globals.no_infer_meths.include?([RDL::Util.remove_singleton_marker(klass).to_s, "self."+meth.to_s])) || + (RDL::Globals.no_infer_meths.include?([klass.to_s, meth.to_s]))) + tag = RDL::Globals.infer_added + RDL::Globals.to_infer[tag] = Set.new unless RDL::Globals.to_infer[tag] + RDL::Globals.to_infer[tag].add([klass, meth]) + end end # Wrap method if there was a prior contract for it. From a4990fa360743edefbd24ece8c480ca4e2b73a54 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 9 Apr 2020 20:06:35 -0400 Subject: [PATCH 039/124] Change misssing ast to warning for infer --- lib/rdl/typecheck.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 2fbe64e3..177814bd 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -213,7 +213,6 @@ def self.get_ast(klass, meth) RDL::Globals.parser_cache[file] = [digest, cache] end ast = RDL::Globals.parser_cache[file][1][:line_defs][line] - raise RuntimeError, "Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}" if ast.nil? return ast end @@ -223,6 +222,10 @@ def self.infer(klass, meth) RDL::Config.instance.number_mode = true @var_cache = {} ast = get_ast(klass, meth) + if ast.nil? + puts "Warning: Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}; skipping method" + return + end types = RDL::Globals.info.get(klass, meth, :type) if types == [] or types.nil? ## in this case, have to create new arg/ret VarTypes for this method @@ -296,6 +299,7 @@ def self.infer(klass, meth) def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) ast = get_ast(klass, meth) unless ast + raise RuntimeError, "Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}" if ast.nil? types = RDL::Globals.info.get(klass, meth, :type) unless types effects = RDL::Globals.info.get(klass, meth, :effect) unless effects if effects.empty? || effects[0] == nil From 381aa493711558491fd44fc63e2049f347322431 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Sun, 12 Apr 2020 19:57:47 -0400 Subject: [PATCH 040/124] Fix typo in readme, fix note_type --- README.md | 4 ++-- lib/rdl/typecheck.rb | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1c5833e3..42729fd3 100644 --- a/README.md +++ b/README.md @@ -580,7 +580,7 @@ Value types can also be declared as optional, indicating that the key/value pair ```ruby type MyClass, :foo, '(a: Integer, b: ?String) { () -> %any } -> %any' ``` -With this type, `foo` takes a hash where key `:a` is mapped to an `Integer`, and furthermore the hash may or may not include a key `:b` that is mapped to a `String`. +With this type, `foo` takes a hash where key `:a` is mapped to an `Integer`, and furthermore the hash may or may not include a key `:b` that is mapped to a `String`. RDL also allows a "rest" type in finite hashes (of course, they're not so finite if they use it!): ```ruby @@ -734,7 +734,7 @@ RDL uses the same approach for hashes: hash literals are treated as finite hashe ## Other Features and Limitations -*Displaying types.* As an aid to debugging, the method `RDL.note_type e` will display the type of `e` during type checking. At run time, this method returns its argument. Note that in certain cases RDL may type check the same code repeatedly, in which case an expression's type could be printed multiple times. +* *Displaying types.* As an aid to debugging, the method `RDL.note_type e` will display the type of `e` during type checking. At run time, this method returns its argument. Note that in certain cases RDL may type check the same code repeatedly, in which case an expression's type could be printed multiple times. * *Conditional guards and singletons.* If an `if` or `unless` guard has a singleton type, RDL will typecheck both branches but not include types from the unrealizable branch in the expression type. For example, `if true then 1 else 'two' end` has type `1`. RDL behaves similarly for `&&` and `||`. However, RDL does not implement this logic for `case`. diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 177814bd..c8ab79bf 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -936,7 +936,7 @@ def self.tc(scope, env, e) # children [2..] = actual args return tc_var_type(scope, env, e) + [[:+, :+]] if (e.children[0].nil? || is_RDL(e.children[0])) && e.children[1] == :var_type return tc_type_cast(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil? ## TODO: could be more precise with effects here, punting for now - return tc_note_type(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :rdl_note_type + return tc_note_type(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :note_type return tc_instantiate!(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :instantiate! envi = env tactuals = [] @@ -1572,9 +1572,9 @@ def self.tc_type_cast(scope, env, e) end def self.tc_note_type(scope, env, e) - error :note_type_format, [], e unless e.children.length == 4 && scope[:block].nil? - env, typ = tc(scope, env, e.children[3]) - note :note_type, [typ], e.children[3] + error :note_type_format, [], e unless e.children.length == 3 && scope[:block].nil? + env, typ = tc(scope, env, e.children[2]) + note :note_type, [typ], e.children[2] [env, typ] end @@ -2622,6 +2622,7 @@ def message block_type_error: "argument type error for block\n%s", type_cast_format: "type_cast must be called as `type_cast obj, type-string' or `type_cast obj, type-string, force: expr'", instantiate_format: "instantiate! must be called as `instantiate! type*' or `instantiate! type*, check: bool' where type is a string, symbol, or class for static type checking.", + note_type_format: "note_type must be called as `note_type e`", var_type_format: "var_type must be called as `var_type :var-name, type-string'", puts_type_format: "puts_type must be called as `puts_type e'", generic_error: "%s", From 8794511df549a1c9f2056bd7543a2e2fe9d3d8c2 Mon Sep 17 00:00:00 2001 From: Geoff Huston Date: Wed, 22 Apr 2020 11:27:34 -0400 Subject: [PATCH 041/124] [WIP] Infer unit tests (#95) * testing out different ways of unit testing * MethodTypes now have solutions * writing method tests * writing method tests * cleaned up some lint * cleaned up .gitignore * cleaned up some debugging code * more cleanup; fixed `method.comment` crash * matching up constraint * cleaned up wrap.rb * cleaned up boot, moved a require from `constraint` * Added more unit tests * re-adding needed library types * linting test_infer * added no_print_XXX! to tests, to show type variables in expected output * changed variable printing to print long names for type-variables * removed solution field from type --- .gitignore | 89 ++++++++++++------------- .rubocop.yml | 5 ++ lib/rdl/boot.rb | 3 +- lib/rdl/constraint.rb | 119 ++++++++++++++++++--------------- lib/rdl/types/method.rb | 10 +++ lib/rdl/types/type.rb | 56 +++++++++------- lib/rdl/types/var.rb | 19 +++--- lib/rdl/util.rb | 2 + lib/rdl/wrap.rb | 9 ++- rdl.gemspec | 2 + test/test_infer.rb | 143 ++++++++++++++++++++++++++++++++++++---- test/test_typecheck.rb | 2 +- 12 files changed, 314 insertions(+), 145 deletions(-) create mode 100644 .rubocop.yml diff --git a/.gitignore b/.gitignore index 0274b3ab..14cc89af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,46 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf210 -{\fonttbl\f0\fnil\fcharset0 Consolas;} -{\colortbl;\red255\green255\blue255;\red38\green38\blue38;} -\margl1440\margr1440\vieww10800\viewh8400\viewkind0 -\deftab720 -\pard\pardeftab720 +################### +*.com +*.class +*.dll +*.exe +*.o +*.so -\f0\fs24 \cf2 # Compiled source #\ -###################\ -*.com\ -*.class\ -*.dll\ -*.exe\ -*.o\ -*.so\ -\'a0\ -# Packages #\ -############\ -# it's better to unpack these files and commit the raw source\ -# git has its own built in compression methods\ -*.7z\ -*.dmg\ -*.gz\ -*.iso\ -*.jar\ -*.rar\ -*.tar\ -*.zip\ -*.gem\ -\'a0\ -# Logs and databases #\ -######################\ -*.log\ -*.sql\ -*.sqlite\ -\'a0\ -# OS generated files #\ -######################\ -.DS_Store\ -.DS_Store?\ -._*\ -.Spotlight-V100\ -.Trashes\ -ehthumbs.db\ -Thumbs.db} \ No newline at end of file +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip +*.gem + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +### Editors +.#* + +### Ruby +*.gem + +### RDL +*.csv diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..962e1d83 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,5 @@ +Metrics/MethodLength: + Max: 30 + +Lint/BooleanSymbol: + Enabled: false diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index f3d55691..fb3ad74a 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -2,6 +2,7 @@ require 'digest' require 'set' require 'parser/current' +#require 'method_source' module RDL @@ -193,7 +194,7 @@ def self.reset @to_do_at = Hash.new @deferred = [] # @dep_types = [] -# @comp_type_map = Hash.new + # @comp_type_map = Hash.new @ar_db_schema = Hash.new @seq_db_schema = Hash.new @no_infer_meths = [] diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 1f35e494..2fe5d5b6 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -160,7 +160,7 @@ def self.extract_meth_sol(tmeth) ## ARG SOLUTIONS arg_sols = tmeth.args.map { |a| if a.optional_var_type? - RDL::Type::OptionalType.new(extract_var_sol(a.type, :arg)) + soln = RDL::Type::OptionalType.new(extract_var_sol(a.type, :arg)) elsif a.fht_var_type? hash_sol = a.elts.transform_values { |v| if v.is_a?(RDL::Type::OptionalType) @@ -169,11 +169,15 @@ def self.extract_meth_sol(tmeth) extract_var_sol(v, :arg) end } - RDL::Type::FiniteHashType.new(hash_sol, nil) + soln = RDL::Type::FiniteHashType.new(hash_sol, nil) else - extract_var_sol(a, :arg) + soln = extract_var_sol(a, :arg) end + + a.solution = soln + soln } + ## BLOCK SOLUTION if tmeth.block && !tmeth.block.ubounds.empty? non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } @@ -192,6 +196,8 @@ def self.extract_meth_sol(tmeth) else block_sol = RDL::Type::MethodType.new(*extract_meth_sol(non_vartype_ubounds[0])) end + + tmeth.block.solution = block_sol else block_sol = nil end @@ -203,51 +209,13 @@ def self.extract_meth_sol(tmeth) ret_sol = tmeth.ret.is_a?(RDL::Type::VarType) ? extract_var_sol(tmeth.ret, :ret) : tmeth.ret end + tmeth.ret.solution = ret_sol + return [arg_sols, block_sol, ret_sol] end - def self.extract_solutions - ## Go through once to come up with solution for all var types. - #until !@new_constraints - typ_sols = {} - loop do - @new_constraints = false - typ_sols = {} - puts "\n\nRunning solution extraction..." - RDL::Globals.constrained_types.each { |klass, name| - RDL::Type::VarType.no_print_XXX! - typ = RDL::Globals.info.get(klass, name, :type) - if typ.is_a?(Array) - raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 - tmeth = typ[0] - - arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) - - block_string = block_sol ? " { #{block_sol} }" : nil - puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" - - RDL::Type::VarType.print_XXX! - block_string = block_sol ? " { #{block_sol} }" : nil - typ_sols[[klass.to_s, name.to_sym]] = "(#{arg_sols.join(', ')})#{block_string} -> #{ret_sol}" - elsif name.to_s == "splat_param" - else - ## Instance/Class (also some times splat parameter) variables: - ## There is no clear answer as to what to do in this case. - ## Just need to pick something in between bounds (inclusive). - ## For now, plan is to just use lower bound when it's not empty/%bot, - ## otherwise use upper bound. - ## Can improve later if desired. - var_sol = extract_var_sol(typ, :var) - #typ.solution = var_sol - puts "Extracted solution for #{klass} variable #{name} is #{var_sol}." - - RDL::Type::VarType.print_XXX! - typ_sols[[klass.to_s, name.to_sym]] = var_sol.to_s - end - } - break if !@new_constraints - end + def self.make_extraction_report(typ_sols) #return unless $orig_types # complete_types = [] @@ -311,20 +279,20 @@ def self.extract_solutions # else # comment = RDL::Util.to_class(klass).instance_method(meth).comment # end - csv << [klass, meth, typ, orig_typ, code] # , comment - #if typ.include?("XXX") + csv << [klass, meth, typ, orig_typ, code] #, comment + # if typ.include?("XXX") # incomplete_types << [klass, meth, typ, orig_typ, code, comment] - #else + # else # complete_types << [klass, meth, typ, orig_typ, code, comment] - #end + # end } end } - #CSV.open("infer_data.csv", "a+") { |csv| - #complete_types.each { |row| csv << row } - #csv << ["X", "X", "X", "X", "X", "X"] - #incomplete_types.each { |row| csv << row } - #} + # CSV.open("infer_data.csv", "a+") { |csv| + # complete_types.each { |row| csv << row } + # csv << ["X", "X", "X", "X", "X", "X"] + # incomplete_types.each { |row| csv << row } + # } puts "Total correct (that could be automatically inferred): #{correct_types}" puts "Total # method types: #{meth_types}" @@ -332,6 +300,51 @@ def self.extract_solutions puts "Total # individual types: #{total_potential}" end + def self.extract_solutions(render_report = true) + ## Go through once to come up with solution for all var types. + #until !@new_constraints + typ_sols = {} + loop do + @new_constraints = false + typ_sols = {} + puts "\n\nRunning solution extraction..." + RDL::Globals.constrained_types.each { |klass, name| + RDL::Type::VarType.no_print_XXX! + typ = RDL::Globals.info.get(klass, name, :type) + if typ.is_a?(Array) + raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 + tmeth = typ[0] + + arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) + + block_string = block_sol ? " { #{block_sol} }" : nil + puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" + + RDL::Type::VarType.print_XXX! + block_string = block_sol ? " { #{block_sol} }" : nil + typ_sols[[klass.to_s, name.to_sym]] = "(#{arg_sols.join(', ')})#{block_string} -> #{ret_sol}" + elsif name.to_s == "splat_param" + else + ## Instance/Class (also some times splat parameter) variables: + ## There is no clear answer as to what to do in this case. + ## Just need to pick something in between bounds (inclusive). + ## For now, plan is to just use lower bound when it's not empty/%bot, + ## otherwise use upper bound. + ## Can improve later if desired. + var_sol = extract_var_sol(typ, :var) + #typ.solution = var_sol + puts "Extracted solution for #{klass} variable #{name} is #{var_sol}." + + RDL::Type::VarType.print_XXX! + typ_sols[[klass.to_s, name.to_sym]] = var_sol.to_s + end + } + break if !@new_constraints + end + + make_extraction_report(typ_sols) if render_report + end + end diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index a066f289..baf0cf2d 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -54,6 +54,16 @@ def initialize(args, block, ret) super() end + # The solution to a method type is a method type made up of the solutions of + # its parts. + def solution + arg_sols = @args.map(&:solution) + block_sol = @block.solution + ret_sol = @ret.solution + + self.class.new arg_sols, block_sol, ret_sol + end + # TODO: Check blk # Very similar to Typecheck.check_arg_types def pre_cond?(blk, slf, inst, bind, *args) diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 1e7672e1..8a4781da 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -5,7 +5,6 @@ module RDL::Type class TypeError < StandardError; end class Type - @@contract_cache = {} def to_contract @@ -35,7 +34,7 @@ def optional_var_type? def vararg_var_type? is_a?(VarargType) && @type.var_type? end - + def fht_var_type? is_a?(FiniteHashType) && @elts.keys.all? { |k| k.is_a?(Symbol) } && @elts.values.all? { |v| v.optional_var_type? || v.var_type? } end @@ -101,7 +100,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con if deferred_constraints.nil? left.add_ubound(right, ast, new_cons, propagate: propagate) unless (left.ubounds.any? { |t, loc| t == right || t.hash == right.hash } || left.equal?(right)) else - deferred_constraints << [left, right] + deferred_constraints << [left, right] end return true elsif right.is_a?(VarType) && right.to_infer @@ -109,7 +108,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con right.add_lbound(left, ast, new_cons, propagate: propagate) unless (right.lbounds.any? { |t, loc| t == left || t.hash == left.hash } || right.equal?(left)) else deferred_constraints << [left, right] - end + end return true end @@ -211,7 +210,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con inst.update(new_inst) unless inst.nil? return true else - ## if a particular arm doesn't apply, undo the + ## if a particular arm doesn't apply, undo the #removed_choices.each { | end } @@ -225,8 +224,8 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # nominal return left.klass.ancestors.member?(right.klass) if left.is_a?(NominalType) && right.is_a?(NominalType) - if (left.is_a?(NominalType) || left.is_a?(TupleType) || left.is_a?(FiniteHashType) || left.is_a?(TopType) || left.is_a?(PreciseStringType) || (left.is_a?(SingletonType) && !left.val.nil?)) && right.is_a?(StructuralType) - + if (left.is_a?(NominalType) || left.is_a?(TupleType) || left.is_a?(FiniteHashType) || left.is_a?(TopType) || left.is_a?(PreciseStringType) || (left.is_a?(SingletonType) && !left.val.nil?)) && right.is_a?(StructuralType) + case left when TupleType lklass = Array @@ -270,7 +269,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end end klass_lookup = lklass.to_s unless klass_lookup - + right.methods.each_pair { |m, t| return false unless lklass.method_defined?(m) || RDL::Typecheck.lookup({}, klass_lookup, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) ## Added the second condition because Rails lazily defines some methods. types, _ = RDL::Typecheck.lookup({}, lklass.to_s, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) @@ -279,7 +278,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con else types = RDL::Typecheck.filter_comp_types(types, false) end - + ret = types.nil? #false if types choice_num = 0 @@ -295,7 +294,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) end new_dcs = [] - if leq(tlm.instantiate(base_inst), t, nil, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + if leq(tlm.instantiate(base_inst), t, nil, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) ret = true if types.size > 1 && !new_dcs.empty? ## method has intersection type, and vartype constraints were created new_dcs.each { |t1, t2| @@ -304,7 +303,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con } else new_dcs.each { |t1, t2| RDL::Type::Type.leq(t1, t2, nil, ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } - end + end end } @@ -336,7 +335,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con } return true end - + # singleton return left.val == right.val if left.is_a?(SingletonType) && right.is_a?(SingletonType) @@ -388,8 +387,8 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con types = RDL::Typecheck.filter_comp_types(types, true) else types = RDL::Typecheck.filter_comp_types(types, false) - end - + end + ret = types.nil? if types choice_num = 0 @@ -400,16 +399,16 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] tlm = RDL::Typecheck.compute_types(tlm, klass, left, t.args) if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } new_dcs = [] - if leq(tlm.instantiate(base_inst.merge({ self: left})), t, nil, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + if leq(tlm.instantiate(base_inst.merge({ self: left})), t, nil, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) ret = true if types.size > 1 && !new_dcs.empty? ## method has intersection type, and vartype constraints were new_dcs.each { |t1, t2| ub_var_choices[t1][choice_num] = RDL::Type::UnionType.new(ub_var_choices[t1][choice_num], t2).canonical if t1.is_a?(VarType) - lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical if t2.is_a?(VarType) + lb_var_choices[t2][choice_num] = RDL::Type::UnionType.new(lb_var_choices[t2][choice_num], t1).canonical if t2.is_a?(VarType) } else new_dcs.each { |t1, t2| RDL::Type::Type.leq(t1, t2, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } - end + end end } if !((lb_var_choices.empty?) && (ub_var_choices.empty?)) @@ -444,13 +443,13 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # Note we do not allow raw subtyping leq(GenericType, NominalType, ...) - # method + # method if left.is_a?(MethodType) && right.is_a?(MethodType) inst = {} if not inst if left.args.last.is_a?(VarargType) #return false unless right.args.size >= left.args.size if right.args.size >= left.args.size - new_args = right.args[(left.args.size - 1) ..-1] + new_args = right.args[(left.args.size - 1) ..-1] if left.args.size == 1 left = RDL::Type::MethodType.new(new_args, left.block, left.ret) else @@ -472,7 +471,20 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end end return false unless left.args.size == right.args.size - return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr.instantiate(inst), tl.instantiate(inst), inst, !ileft, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) } # contravariance + return false unless left.args.zip(right.args).all? { |tl, tr| + leq( + tr.instantiate(inst), + tl.instantiate(inst), + inst, + !ileft, + deferred_constraints, + no_constraint: no_constraint, + ast: ast, + propagate: propagate, + new_cons: new_cons, + removed_choices: removed_choices + ) + } # contravariance #return false unless left.args.zip(right.args).all? { |tl, tr| leq(tr.instantiate(inst), tl.instantiate(inst), inst, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons) } # contravariance if left.block && right.block @@ -494,7 +506,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con } return true end - + # structural if left.is_a?(StructuralType) && right.is_a?(StructuralType) # allow width subtyping - methods of right have to be in left, but not vice-versa @@ -557,7 +569,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end ## precise string - + if left.is_a?(PreciseStringType) if right.is_a?(PreciseStringType) return false if left.vals.size != right.vals.size diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index 84923fd9..731d3d60 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -2,7 +2,7 @@ module RDL::Type class VarType < Type attr_reader :name, :cls, :meth, :category, :to_infer attr_accessor :lbounds, :ubounds, :solution - + @@cache = {} @@print_XXX = false @@ -31,7 +31,7 @@ def self.new(name_or_hash) def initialize(name_or_hash) if name_or_hash.is_a?(Symbol) || name_or_hash.is_a?(String) - raise "weird" if name_or_hash.to_s == "expression" + raise "weird" if name_or_hash.to_s == "expression" @name = name_or_hash @to_infer = false elsif name_or_hash.is_a?(Hash) @@ -39,7 +39,7 @@ def initialize(name_or_hash) @lbounds = [] @ubounds = [] @solution = nil - + @cls = name_or_hash[:cls] @name = name_or_hash[:name] ## might be nil if category is :ret @meth = name_or_hash[:meth] ## might be nil if ccategory is :var @@ -122,21 +122,18 @@ def add_lbound(typ, ast, new_cons = {}, propagate: false) def to_s # :nodoc: if @to_infer - if @solution - return @solution.to_s - else - return "XXX" if @@print_XXX - return "{ #{@cls}##{@meth} #{@category}: #{@name} }" - end + return 'XXX' if @@print_XXX + + "{ #{@cls}##{@meth} #{@category}: #{@name} }" else - return @name.to_s + @name.to_s end end def base_name return nil unless @name ## if var represents returned value, then method name is closest thing we have to variable's name. - if @category == :ret then @meth.to_s else @name.to_s.delete("@") end + if @category == :ret then @meth.to_s else @name.to_s.delete("@") end end def ==(other) diff --git a/lib/rdl/util.rb b/lib/rdl/util.rb index 0ca754ba..9abe234a 100644 --- a/lib/rdl/util.rb +++ b/lib/rdl/util.rb @@ -1,3 +1,5 @@ +require 'stringio' + class RDL::Util SINGLETON_MARKER = "[s]" diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 734820b3..95766acb 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -254,7 +254,9 @@ def self.do_method_added(the_self, sing, klass, meth) else loc = the_self.instance_method(meth).source_location end + RDL::Globals.info.set(klass, meth, :source_location, loc) + a = RDL::Globals.deferred RDL::Globals.deferred = [] # Reset before doing more work to avoid infinite recursion a.each { |prev_klass, kind, contract, h| @@ -846,8 +848,9 @@ def self.do_typecheck(sym) nil end - def self.do_infer(sym) + def self.do_infer(sym, render_report: true) return unless RDL::Globals.to_infer[sym] + RDL::Config.instance.use_unknown_types = true $stn = 0 num_casts = 0 @@ -858,7 +861,9 @@ def self.do_infer(sym) } RDL::Globals.to_infer[sym] = Set.new RDL::Typecheck.resolve_constraints - RDL::Typecheck.extract_solutions + + RDL::Typecheck.extract_solutions render_report + time = Time.now - time puts "Total time taken: #{time}." puts "Total number of type casts used: #{num_casts}." diff --git a/rdl.gemspec b/rdl.gemspec index 7e847591..3707abde 100644 --- a/rdl.gemspec +++ b/rdl.gemspec @@ -21,4 +21,6 @@ EOF s.add_runtime_dependency 'parser', '~>2.3', '>= 2.3.1.4' s.add_runtime_dependency 'sql-parser', '~>0.0.2' s.add_runtime_dependency 'method_source' + s.add_development_dependency 'colorize', '~>0.8', '>= 0.8.1' + s.add_development_dependency 'coderay', '~>1.2', '>= 1.1.2' end diff --git a/test/test_infer.rb b/test/test_infer.rb index ed274d74..b4ebaf9a 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -1,26 +1,147 @@ +# frozen_string_literal: true + +require 'colorize' +require 'coderay' + require 'minitest/autorun' -$LOAD_PATH << File.dirname(__FILE__) + "/../lib" +$LOAD_PATH << File.dirname(__FILE__) + '/../lib' require 'rdl' require 'types/core' +# Testing Inference (constraint.rb) class TestInfer < Minitest::Test extend RDL::Annotate def setup RDL.reset + RDL::Config.instance.number_mode = true # treat all numeric classes the same + RDL.readd_comp_types + RDL.type_params :Hash, [:k, :v], :all? unless RDL::Globals.type_params['Hash'] + RDL.type_params :Array, [:t], :all? unless RDL::Globals.type_params['Array'] + RDL.rdl_alias :Array, :size, :length + RDL.type_params 'RDL::Type::SingletonType', [:t], :satisfies? unless RDL::Globals.type_params['RDL::Type::SingletonType'] + RDL.type_params(:Range, [:t], nil, variance: [:+]) { |t| t.member?(self.begin) && t.member?(self.end) } unless RDL::Globals.type_params['Range'] + RDL.type :Range, :each, '() { (t) -> %any } -> self' + RDL.type :Range, :each, '() -> Enumerator' + RDL.type :Integer, :to_s, '() -> String', wrap: false + RDL.type :Kernel, 'self.puts', '(*[to_s : () -> String]) -> nil', wrap: false + RDL.type :Kernel, :raise, '() -> %bot', wrap: false + RDL.type :Kernel, :raise, '(String) -> %bot', wrap: false + RDL.type :Kernel, :raise, '(Class, ?String, ?Array) -> %bot', wrap: false + RDL.type :Kernel, :raise, '(Exception, ?String, ?Array) -> %bot', wrap: false + RDL.type :Object, :===, '(%any other) -> %bool', wrap: false + RDL.type :Object, :clone, '() -> self', wrap: false + RDL.type :NilClass, :&, '(%any obj) -> false', wrap: false + + ### Uncomment below to see test names. Useful for hanging tests. + # puts "Start #{@NAME}" + end + + # convert a string to a method type + def tm(typ) + RDL::Globals.parser.scan_str('#Q ' + typ) + end + + def infer_method_type(method, depends_on: []) + depends_on.each { |m| RDL.infer self.class, m, time: :test } + + RDL.infer self.class, method, time: :test + + RDL.do_infer :test, render_report: false + + types = RDL::Globals.info.get 'TestInfer', method, :type + assert types.length == 1, msg: 'Expected one solution for type' + + types[0] + end + + def assert_type_equal(meth, expected_type, depends_on: []) + typ = infer_method_type meth, depends_on: depends_on + RDL::Type::VarType.no_print_XXX! + + if expected_type != typ.solution + ast = RDL::Typecheck.get_ast(self.class, meth) + code = CodeRay.scan(ast.loc.expression.source, :ruby).term + + error_str = 'Given'.yellow + ":\n #{code}\n\n" + error_str += 'Expected '.green + expected_type.to_s + "\n" + error_str += 'Got '.red + typ.solution.to_s + end + + assert expected_type == typ.solution, error_str + end + + def self.should_have_type(meth, typ, depends_on: []) + define_method "test_#{meth}" do + assert_type_equal meth, tm(typ), depends_on: depends_on + end + end + + # ---------------------------------------------------------------------------- + + def return_two + 2 + end + should_have_type :return_two, '() -> Integer' + + def return_two_plus_two + 2 + 2 + end + should_have_type :return_two_plus_two, '() -> Integer' + + def plus_two(val) + val + 2 + end + should_have_type :plus_two, '([ +: (Number) -> aaa ]) -> aaa' + + def print_it(val) + puts val + end + should_have_type :print_it, '([ to_s: () -> String ]) -> nil' + + def return_hash + { a: 1, b: 2, c: 3 } + end + should_have_type :return_hash, '() -> { a: Integer, b: Integer, c: Integer }' + + def return_hash_1 + { a: 1, b: 'b', c: :c } + end + should_have_type :return_hash_1, '() -> { a: Integer, b: String, c: :c }' + + def return_hash_val(val) + { a: 1, b: 'b', c: val } + end + should_have_type :return_hash_val, '(val) -> { a: Integer, b: String, c: val }' + + def concatenate + 'Hello' + ' World!' + end + should_have_type :concatenate, '() -> String' + + def concatenate_1(val) + 'Hello, ' + val + end + should_have_type :concatenate_1, '(String) -> String' + + def repeat + 'a' * 5 + end + should_have_type :repeat, '() -> String' + + def repeat_n(n) + 'a' * n end + should_have_type :repeat_n, '(Numeric) -> String' - # [+ a +] is the environment, a map from symbols to types; empty if omitted - # [+ expr +] is a string containing the expression to typecheck - # returns the type of the expression - def do_tc(expr, scope: Hash.new, env: RDL::Typecheck::Env.new) - ast = Parser::CurrentRuby.parse expr - t = RDL::Globals.types[:bot] - return t + def note(reason, args, ast) + Diagnostic.new :note, reason, args, ast.loc.expression end + should_have_type :note, '(reason, args, Parser::AST::Node or Parser::Source::Comment) -> Diagnostic' - # convert arg string to a type - def tt(t) - RDL::Globals.parser.scan_str('#T ' + t) + def print_note(reason, args, ast) + puts note(reason, args, ast).render end + should_have_type :print_note, '(reason, args, Parser::AST::Node or Parser::Source::Comment) -> nil', + depends_on: [:note] end diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 1150b73f..799eca11 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -9,7 +9,7 @@ class N2 def self.foo :sym end - type 'self.foo', '() -> :sym' + type 'self.foo', '() -> :sym' def self.foo2 :sym2 From 7666873fddc7f7a1015183fca061be4f45d9e374 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 23 Apr 2020 10:23:15 -0400 Subject: [PATCH 042/124] Add gems for travis --- gemfiles/Gemfile.travis | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gemfiles/Gemfile.travis b/gemfiles/Gemfile.travis index 2fc104a5..ee6ceee8 100644 --- a/gemfiles/Gemfile.travis +++ b/gemfiles/Gemfile.travis @@ -3,3 +3,7 @@ source 'https://rubygems.org' gem 'rake' gem 'minitest' gem 'parser' +gem 'sql-parser' +gem 'method_source' +gem 'colorize' +gem 'coderay' From 6f775f7be1fb8cd9e6563e1e0c627ecf15f0ccbe Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 23 Apr 2020 19:55:55 -0400 Subject: [PATCH 043/124] Added a config reset option --- lib/rdl/config.rb | 66 ++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index 8feb6673..d7deec3c 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -4,54 +4,38 @@ class RDL::Config include Singleton attr_accessor :nowrap - attr_accessor :gather_stats - attr_reader :report # writer is custom defined + attr_accessor :gather_stats, :at_exit_installed + attr_accessor :report, :get_types, :guess_types attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types attr_accessor :type_defaults, :infer_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type attr_accessor :use_precise_string, :number_mode, :use_unknown_types, :infer_empties def initialize - @nowrap = Set.new # Set of symbols - @gather_stats = false - @report = false # if this is enabled by default, modify @at_exit_installed - @guess_types = [] # same as above - @get_types = [] # Array>: list of class/method name pairs to collect type data for - @at_exit_installed = false - @weak_update_promote = false - @promote_widen = false - @type_defaults = { wrap: true, typecheck: false} - @infer_defaults = { time: nil } - @pre_defaults = { wrap: true } - @post_defaults = { wrap: true } - @assume_dyn_type = false - @widen_bound = 5 - @use_comp_types = true - @check_comp_types = false ## this for dynamically checking that the result of a computed type still holds - @rerun_comp_types = false ## this is for dynamically checking that a type computation still evaluates to the same thing as it did at type checking time - @use_precise_string = false - @number_mode = false - @use_unknown_types = false - @infer_empties = true ## if [] and {} should be typed as Array and Hash + RDL::Config.reset(self) end - def report=(val) - install_at_exit - @report = val - end - - def guess_types - install_at_exit - return @guess_types - end - - def guess_types=(val) - install_at_exit - @guess_types = val - end - - def get_types - install_at_exit - return @get_types + def self.reset(c=RDL::Config.instance) + c.nowrap = Set.new # Set of symbols + c.gather_stats = false + c.report = false # if this is enabled by default, modify @at_exit_installed + c.guess_types = [] # same as above + c.get_types = [] # Array>: list of class/method name pairs to collect type data for + c.at_exit_installed = false + c.weak_update_promote = false + c.promote_widen = false + c.type_defaults = { wrap: true, typecheck: false} + c.infer_defaults = { time: nil } + c.pre_defaults = { wrap: true } + c.post_defaults = { wrap: true } + c.assume_dyn_type = false + c.widen_bound = 5 + c.use_comp_types = true + c.check_comp_types = false ## this for dynamically checking that the result of a computed type still holds + c.rerun_comp_types = false ## this is for dynamically checking that a type computation still evaluates to the same thing as it did at type checking time + c.use_precise_string = false + c.number_mode = false + c.use_unknown_types = false + c.infer_empties = true ## if [] and {} should be typed as Array and Hash end def add_nowrap(*klasses) From a86a89bf277f8b29bbd8a9715a26f4d9c2f54f88 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 23 Apr 2020 20:12:24 -0400 Subject: [PATCH 044/124] suppress infer info messages unless RDL::config.instance.infer_verbose is set. --- lib/rdl/config.rb | 3 ++- lib/rdl/constraint.rb | 10 +++++----- lib/rdl/typecheck.rb | 22 +++++++++++++--------- lib/rdl/wrap.rb | 8 +++++--- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index d7deec3c..72564978 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -8,7 +8,7 @@ class RDL::Config attr_accessor :report, :get_types, :guess_types attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types attr_accessor :type_defaults, :infer_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type - attr_accessor :use_precise_string, :number_mode, :use_unknown_types, :infer_empties + attr_accessor :use_precise_string, :number_mode, :use_unknown_types, :infer_empties, :infer_verbose def initialize RDL::Config.reset(self) @@ -36,6 +36,7 @@ def self.reset(c=RDL::Config.instance) c.number_mode = false c.use_unknown_types = false c.infer_empties = true ## if [] and {} should be typed as Array and Hash + c.infer_verbose = false end def add_nowrap(*klasses) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 2fe5d5b6..690f624a 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -3,9 +3,9 @@ module RDL::Typecheck def self.resolve_constraints - puts "Starting constraint resolution..." + puts "Starting constraint resolution..." if RDL::Config.instance.infer_verbose RDL::Globals.constrained_types.each { |klass, name| - puts "Resolving constraints from #{klass} and #{name}" + puts "Resolving constraints from #{klass} and #{name}" if RDL::Config.instance.infer_verbose typ = RDL::Globals.info.get(klass, name, :type) ## If typ is an Array, then it's an array of method types ## but for inference, we only use a single method type. @@ -307,7 +307,7 @@ def self.extract_solutions(render_report = true) loop do @new_constraints = false typ_sols = {} - puts "\n\nRunning solution extraction..." + puts "\n\nRunning solution extraction..." if RDL::Config.instance.infer_verbose RDL::Globals.constrained_types.each { |klass, name| RDL::Type::VarType.no_print_XXX! typ = RDL::Globals.info.get(klass, name, :type) @@ -318,7 +318,7 @@ def self.extract_solutions(render_report = true) arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) block_string = block_sol ? " { #{block_sol} }" : nil - puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" + puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" if RDL::Config.instance.infer_verbose RDL::Type::VarType.print_XXX! block_string = block_sol ? " { #{block_sol} }" : nil @@ -333,7 +333,7 @@ def self.extract_solutions(render_report = true) ## Can improve later if desired. var_sol = extract_var_sol(typ, :var) #typ.solution = var_sol - puts "Extracted solution for #{klass} variable #{name} is #{var_sol}." + puts "Extracted solution for #{klass} variable #{name} is #{var_sol}." if RDL::Config.instance.infer_verbose RDL::Type::VarType.print_XXX! typ_sols[[klass.to_s, name.to_sym]] = var_sol.to_s diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index c8ab79bf..53b60985 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -217,7 +217,7 @@ def self.get_ast(klass, meth) end def self.infer(klass, meth) - puts "*************** Infering method #{meth} from class #{klass} ***************" + puts "*************** Infering method #{meth} from class #{klass} ***************" if RDL::Config.instance.infer_verbose RDL::Config.instance.use_comp_types = true RDL::Config.instance.number_mode = true @var_cache = {} @@ -284,7 +284,7 @@ def self.infer(klass, meth) end until old_captured == scope[:captured] #body_type = self_type if meth == :initialize - body_type = RDL::Globals.parser.scan_str "#T self" if meth == :initialize + body_type = RDL::Globals.parser.scan_str "#T self" if meth == :initialize # JF: Why not inst.self? if body_type.is_a?(RDL::Type::UnionType) body_type.types.each { |t| RDL::Type::Type.leq(t, ret_vartype, ast: ast) } else @@ -294,7 +294,7 @@ def self.infer(klass, meth) RDL::Globals.info.set(klass, meth, :typechecked, true) RDL::Globals.constrained_types << [klass, meth] - puts "Done with constraint generation." + puts "Done with constraint generation." if RDL::Config.instance.infer_verbose end def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) @@ -1682,7 +1682,6 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) ts, es = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) end - if es.nil? || (es.all? { |effect| effect.nil? }) ## could be multiple, because every time e is called, nil is added to effects ## should probably change default effect to be [:-, :-], but for now I want it like this, ## so I can easily see when a method has been used and its effect set to the default. @@ -1772,7 +1771,16 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ts = ts.map { |t| t.instantiate(inst) } when RDL::Type::NominalType ts, es = lookup(scope, trecv.name, meth, e) - error :no_instance_method_type, [trecv.name, meth], e unless ts + unless ts + klass = RDL::Util.to_class(scope[:klass]) + if klass.class == Module + # defer type checking until module is included + + + + end + error :no_instance_method_type, [trecv.name, meth], e unless ts + end inst = {self: trecv} self_klass = RDL::Util.to_class(trecv.name) when RDL::Type::GenericType @@ -1882,9 +1890,6 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, meth_type = RDL::Type::MethodType.new(tactuals, nil, ret_type) end - - - RDL::Type::Type.leq(trecv, RDL::Type::StructuralType.new({ meth => meth_type }), ast: e) #tmeth_inter = [meth_type] @@ -2073,7 +2078,6 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, apply_deferred_constraints(new_dcs, e) if !new_dcs.empty? end - if trets.empty? # no possible matching call msg = < Date: Sun, 3 May 2020 17:10:19 -0400 Subject: [PATCH 045/124] [WIP] Infer unit tests pattern matcher (#96) * testing out different ways of unit testing * MethodTypes now have solutions * writing method tests * writing method tests * cleaned up some lint * cleaned up .gitignore * cleaned up some debugging code * more cleanup; fixed `method.comment` crash * matching up constraint * cleaned up wrap.rb * cleaned up boot, moved a require from `constraint` * Added more unit tests * re-adding needed library types * linting test_infer * added no_print_XXX! to tests, to show type variables in expected output * changed variable printing to print long names for type-variables * removed solution field from type * added pattern matcher for type variables * Moved VarType matching into types' match functions * removed leading underscores from some match parameters --- lib/rdl/types/ast_node.rb | 2 +- lib/rdl/types/bot.rb | 2 +- lib/rdl/types/dependent_arg.rb | 4 ++-- lib/rdl/types/dynamic.rb | 2 +- lib/rdl/types/finite_hash.rb | 15 +++++++++------ lib/rdl/types/generic.rb | 6 ++++-- lib/rdl/types/intersection.rb | 7 ++++--- lib/rdl/types/method.rb | 9 +++++---- lib/rdl/types/nominal.rb | 4 ++-- lib/rdl/types/non_null.rb | 5 +++-- lib/rdl/types/optional.rb | 5 +++-- lib/rdl/types/singleton.rb | 2 +- lib/rdl/types/structural.rb | 7 ++++--- lib/rdl/types/top.rb | 2 +- lib/rdl/types/tuple.rb | 12 ++++++++---- lib/rdl/types/union.rb | 6 +++--- lib/rdl/types/var.rb | 15 +++++++++++++-- lib/rdl/types/vararg.rb | 5 +++-- lib/rdl/types/wild_query.rb | 2 +- test/test_infer.rb | 23 ++++++++++++++++------- 20 files changed, 85 insertions(+), 50 deletions(-) diff --git a/lib/rdl/types/ast_node.rb b/lib/rdl/types/ast_node.rb index 5fd2fde9..7f5d9e24 100644 --- a/lib/rdl/types/ast_node.rb +++ b/lib/rdl/types/ast_node.rb @@ -43,7 +43,7 @@ def ==(other) alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery diff --git a/lib/rdl/types/bot.rb b/lib/rdl/types/bot.rb index d81e28ac..5e0cda8c 100644 --- a/lib/rdl/types/bot.rb +++ b/lib/rdl/types/bot.rb @@ -25,7 +25,7 @@ def ==(other) alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery diff --git a/lib/rdl/types/dependent_arg.rb b/lib/rdl/types/dependent_arg.rb index 45c56f94..e7247456 100644 --- a/lib/rdl/types/dependent_arg.rb +++ b/lib/rdl/types/dependent_arg.rb @@ -28,8 +28,8 @@ def ==(other) # :nodoc: alias eql? == # match on the base type, ignoring refinement - def match(other) - return @type.match(other) + def match(other, type_var_table = {}) + return @type.match(other, type_var_table) end def hash # :nodoc: diff --git a/lib/rdl/types/dynamic.rb b/lib/rdl/types/dynamic.rb index 3a768215..daae1230 100644 --- a/lib/rdl/types/dynamic.rb +++ b/lib/rdl/types/dynamic.rb @@ -29,7 +29,7 @@ def ==(other) alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery diff --git a/lib/rdl/types/finite_hash.rb b/lib/rdl/types/finite_hash.rb index 40653063..23e41fdb 100644 --- a/lib/rdl/types/finite_hash.rb +++ b/lib/rdl/types/finite_hash.rb @@ -12,7 +12,7 @@ class FiniteHashType < Type attr_accessor :ubounds # upper bounds this tuple has been compared with using <= attr_accessor :lbounds # lower bounds... attr_accessor :default # For hashes created with Hash.new, gives the default type to return for non-existent keys - + # [+ elts +] is a map from keys to types def initialize(elts, rest, default: nil) elts.each { |k, t| @@ -48,16 +48,19 @@ def ==(other) # :nodoc: alias eql? == - def match(other) - return @the_hash.match(other) if @the_hash + def match(other, type_var_table = {}) + return @the_hash.match(other, type_var_table) if @the_hash + other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery return false unless other.instance_of? FiniteHashType + return false unless ((@rest.nil? && other.rest.nil?) || - (!@rest.nil? && !other.rest.nil? && @rest.match(other.rest))) - return (@elts.length == other.elts.length && - @elts.all? { |k, v| (other.elts.has_key? k) && (v.match(other.elts[k]))}) + (!@rest.nil? && !other.rest.nil? && @rest.match(other.rest, type_var_table))) + + return @elts.length == other.elts.length && + @elts.all? { |k, v| other.elts.has_key?(k) && v.match(other.elts[k], type_var_table) } end def promote(key=nil, value=nil) diff --git a/lib/rdl/types/generic.rb b/lib/rdl/types/generic.rb index 86f1f34a..6a8c9b8e 100644 --- a/lib/rdl/types/generic.rb +++ b/lib/rdl/types/generic.rb @@ -27,12 +27,14 @@ def ==(other) # :nodoc: alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery return false unless other.instance_of? GenericType - return @params.length == other.params.length && @params.zip(other.params).all? { |t,o| t.match(o) } + + return @params.length == other.params.length && + @params.zip(other.params).all? { |t,o| t.match(o, type_var_table) } end def <=(other) diff --git a/lib/rdl/types/intersection.rb b/lib/rdl/types/intersection.rb index f2b55621..ba105984 100644 --- a/lib/rdl/types/intersection.rb +++ b/lib/rdl/types/intersection.rb @@ -87,7 +87,7 @@ def drop_vars new_types << @types[i] unless @types[i].is_a? VarType end end - RDL::Type::IntersectionType.new(*new_types).canonical + RDL::Type::IntersectionType.new(*new_types).canonical end def to_s # :nodoc: @@ -103,13 +103,14 @@ def ==(other) # :nodoc: alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery return false unless other.instance_of? IntersectionType return false if @types.length != other.types.length - @types.all? { |t| other.types.any? { |ot| t.match(ot) } } + + @types.all? { |t| other.types.any? { |ot| t.match(ot, type_var_table) } } end def member?(obj, *args) diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index baf0cf2d..ce78eddb 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -338,16 +338,17 @@ def ==(other) alias eql? == # other may not be a query - def match(other) + def match(other, type_var_table = {}) other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery return false unless other.instance_of? MethodType - return false unless @ret.match(other.ret) + return false unless @ret.match(other.ret, type_var_table) + if @block == nil return false unless other.block == nil else return false if other.block == nil - return false unless @block.match(other.block) + return false unless @block.match(other.block, type_var_table) end # Check arg matches; logic is similar to pre_cond states = [[0,0]] # [position in self, position in other] @@ -369,7 +370,7 @@ def match(other) s_arg_t = s_arg_t.type if s_arg_t.instance_of? AnnotatedArgType o_arg_t = other.args[o_arg] o_arg_t = o_arg_t.type if o_arg_t.instance_of? AnnotatedArgType - next unless s_arg_t.match(o_arg_t) + next unless s_arg_t.match(o_arg_t, type_var_table) states << [s_arg+1, o_arg+1] end end diff --git a/lib/rdl/types/nominal.rb b/lib/rdl/types/nominal.rb index 94d00f2d..5ad8a266 100644 --- a/lib/rdl/types/nominal.rb +++ b/lib/rdl/types/nominal.rb @@ -29,7 +29,7 @@ def ==(other) alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery @@ -82,7 +82,7 @@ def widen def copy return self end - + @@cache.merge!({"NilClass" => SingletonType.new(nil), "TrueClass" => SingletonType.new(true), "FalseClass" => SingletonType.new(false), diff --git a/lib/rdl/types/non_null.rb b/lib/rdl/types/non_null.rb index f9ad8d85..a09b8ee4 100644 --- a/lib/rdl/types/non_null.rb +++ b/lib/rdl/types/non_null.rb @@ -23,11 +23,12 @@ def ==(other) # :nodoc: alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery - return (other.instance_of? NonNullType) && (@type.match(other.type)) + + return (other.instance_of? NonNullType) && (@type.match(other.type, type_var_table)) end def hash # :nodoc: diff --git a/lib/rdl/types/optional.rb b/lib/rdl/types/optional.rb index 838baa3d..52e29ac0 100644 --- a/lib/rdl/types/optional.rb +++ b/lib/rdl/types/optional.rb @@ -29,11 +29,12 @@ def ==(other) # :nodoc: alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery - return (other.instance_of? OptionalType) && (@type.match(other.type)) + + return (other.instance_of? OptionalType) && (@type.match(other.type, type_var_table)) end # Note: no member?, because these can only appear in MethodType, where they're handled specially diff --git a/lib/rdl/types/singleton.rb b/lib/rdl/types/singleton.rb index 836f4021..9de0faf3 100644 --- a/lib/rdl/types/singleton.rb +++ b/lib/rdl/types/singleton.rb @@ -33,7 +33,7 @@ def ==(other) alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery diff --git a/lib/rdl/types/structural.rb b/lib/rdl/types/structural.rb index 071099c7..12e79c71 100644 --- a/lib/rdl/types/structural.rb +++ b/lib/rdl/types/structural.rb @@ -63,13 +63,14 @@ def ==(other) # :nodoc: alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery return false unless other.instance_of? StructuralType - return (@methods.length == other.methods.length && - @methods.all? { |k, v| (other.methods.has_key? k) && (v.match(other.methods[k]))}) + + return @methods.length == other.methods.length && + @methods.all? { |k, v| other.methods.key?(k) && v.match(other.methods[k], type_var_table) } end def hash # :nodoc: diff --git a/lib/rdl/types/top.rb b/lib/rdl/types/top.rb index 03bcbc12..651c6746 100644 --- a/lib/rdl/types/top.rb +++ b/lib/rdl/types/top.rb @@ -27,7 +27,7 @@ def ==(other) alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery diff --git a/lib/rdl/types/tuple.rb b/lib/rdl/types/tuple.rb index d76fb680..9a13ee23 100644 --- a/lib/rdl/types/tuple.rb +++ b/lib/rdl/types/tuple.rb @@ -36,12 +36,16 @@ def ==(other) # :nodoc: alias eql? == - def match(other) - return @array.match(other) if @array + def match(other, type_var_table = {}) + return @array.match(other, type_var_table) if @array + other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery - return (other.instance_of? TupleType) && (@params.length == other.params.length) && (@params.zip(other.params).all? { |t,o| t.match(o) }) + + (other.instance_of? TupleType) && + (@params.length == other.params.length) && + (@params.zip(other.params).all? { |t, o| t.match(o, type_var_table) }) end def promote(t=nil) @@ -95,7 +99,7 @@ def widen end def copy - return TupleType.new(*@params.map { |t| t.copy }) + return TupleType.new(*@params.map { |t| t.copy }) end def hash diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index 84554df0..5861ed26 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -103,15 +103,15 @@ def ==(other) # :nodoc: alias eql? == - def match(other) + def match(other, type_var_table = {}) canonicalize! - return @canonical.match(other) if @canonical + return @canonical.match(other, type_var_table) if @canonical other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery return false unless other.instance_of? UnionType return false if @types.length != other.types.length - @types.all? { |t| other.types.any? { |ot| t.match(ot) } } + @types.all? { |t| other.types.any? { |ot| t.match(ot, type_var_table) } } end def <=(other) diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index 731d3d60..dd13096b 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -149,11 +149,22 @@ def <=(other) return Type.leq(self, other) end - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType + return true if other.instance_of? WildQuery - return self == other + return false unless other.instance_of? VarType + + name_sym = name.to_sym + + # If we've seen this type variable before, look up what it was originally + # referencing and test that for equality with the current `other` type + return type_var_table[name_sym] == other if type_var_table.key? name_sym + + # Otherwise, store the other type and return true. + type_var_table[name_sym] = other + true end def hash # :nodoc: diff --git a/lib/rdl/types/vararg.rb b/lib/rdl/types/vararg.rb index 8b666955..1f19a14c 100644 --- a/lib/rdl/types/vararg.rb +++ b/lib/rdl/types/vararg.rb @@ -40,11 +40,12 @@ def ==(other) # :nodoc: alias eql? == - def match(other) + def match(other, type_var_table = {}) other = other.canonical other = other.type if other.instance_of? AnnotatedArgType return true if other.instance_of? WildQuery - return (other.instance_of? VarargType) && (@type.match(other.type)) + + return (other.instance_of? VarargType) && (@type.match(other.type, type_var_table)) end # Note: no member?, because these can only appear in MethodType, where they're handled specially diff --git a/lib/rdl/types/wild_query.rb b/lib/rdl/types/wild_query.rb index d574dd73..254f8cd5 100644 --- a/lib/rdl/types/wild_query.rb +++ b/lib/rdl/types/wild_query.rb @@ -29,7 +29,7 @@ def <=(other) return self == other end - def match(other) + def match(other, type_var_table = {}) return true end end diff --git a/test/test_infer.rb b/test/test_infer.rb index b4ebaf9a..7b2c0acb 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -14,7 +14,11 @@ class TestInfer < Minitest::Test def setup RDL.reset - RDL::Config.instance.number_mode = true # treat all numeric classes the same + RDL::Config.instance.number_mode = true + + # TODO: this will go away after config/reset + RDL::Config.instance.use_precise_string = false + RDL.readd_comp_types RDL.type_params :Hash, [:k, :v], :all? unless RDL::Globals.type_params['Hash'] RDL.type_params :Array, [:t], :all? unless RDL::Globals.type_params['Array'] @@ -37,6 +41,12 @@ def setup # puts "Start #{@NAME}" end + # TODO: this will go away after config/reset + def teardown + RDL::Config.instance.number_mode = false + RDL::Config.instance.use_unknown_types = false # set in do_infer + end + # convert a string to a method type def tm(typ) RDL::Globals.parser.scan_str('#Q ' + typ) @@ -46,7 +56,6 @@ def infer_method_type(method, depends_on: []) depends_on.each { |m| RDL.infer self.class, m, time: :test } RDL.infer self.class, method, time: :test - RDL.do_infer :test, render_report: false types = RDL::Globals.info.get 'TestInfer', method, :type @@ -68,7 +77,7 @@ def assert_type_equal(meth, expected_type, depends_on: []) error_str += 'Got '.red + typ.solution.to_s end - assert expected_type == typ.solution, error_str + assert expected_type.match(typ.solution), error_str end def self.should_have_type(meth, typ, depends_on: []) @@ -92,7 +101,7 @@ def return_two_plus_two def plus_two(val) val + 2 end - should_have_type :plus_two, '([ +: (Number) -> aaa ]) -> aaa' + should_have_type :plus_two, '([ +: (Integer) -> a ]) -> b' def print_it(val) puts val @@ -112,7 +121,7 @@ def return_hash_1 def return_hash_val(val) { a: 1, b: 'b', c: val } end - should_have_type :return_hash_val, '(val) -> { a: Integer, b: String, c: val }' + should_have_type :return_hash_val, '(a) -> { a: Integer, b: String, c: a }' def concatenate 'Hello' + ' World!' @@ -137,11 +146,11 @@ def repeat_n(n) def note(reason, args, ast) Diagnostic.new :note, reason, args, ast.loc.expression end - should_have_type :note, '(reason, args, Parser::AST::Node or Parser::Source::Comment) -> Diagnostic' + should_have_type :note, '(a, b, Parser::AST::Node or Parser::Source::Comment) -> Diagnostic' def print_note(reason, args, ast) puts note(reason, args, ast).render end - should_have_type :print_note, '(reason, args, Parser::AST::Node or Parser::Source::Comment) -> nil', + should_have_type :print_note, '(a, b, Parser::AST::Node or Parser::Source::Comment) -> nil', depends_on: [:note] end From ae136f638055d9b741241232bf2ba5075b361dd8 Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Sun, 3 May 2020 17:11:32 -0400 Subject: [PATCH 046/124] fix reset (#97) * some tests are now passing * fix remainder range errors * remove dependency on comptypes further * remove hash type check to it's own file * remove comp types read --- lib/rdl/boot.rb | 1 + test/test_hash_types.rb | 9 ++++++++- test/test_infer.rb | 3 ++- test/test_typecheck.rb | 41 +++++++++++++++++------------------------ 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index fb3ad74a..d9eef93e 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -181,6 +181,7 @@ class << RDL::Globals module RDL def self.reset RDL::Globals.module_eval { + RDL::Config.reset @info = RDL::Info.new @wrapped_calls = Hash.new 0 @type_params = Hash.new diff --git a/test/test_hash_types.rb b/test/test_hash_types.rb index f808bfd3..5cf16a87 100644 --- a/test/test_hash_types.rb +++ b/test/test_hash_types.rb @@ -17,11 +17,18 @@ def setup def test_hash_methods self.class.class_eval { + type '(Integer) -> Integer', typecheck: :now + def def_inst_hash_fail(x) + hash = {} + hash["test"] = x + hash["test"] + end + type '({bar: Integer, baz: String}) -> String', typecheck: :now def access_test1(x) x[:baz] end - + type '({bar: Integer, baz: String}) -> {bar: Integer, baz: String}', typecheck: :now def access_test2(x) x[:baz] diff --git a/test/test_infer.rb b/test/test_infer.rb index 7b2c0acb..d5528a49 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -22,7 +22,8 @@ def setup RDL.readd_comp_types RDL.type_params :Hash, [:k, :v], :all? unless RDL::Globals.type_params['Hash'] RDL.type_params :Array, [:t], :all? unless RDL::Globals.type_params['Array'] - RDL.rdl_alias :Array, :size, :length + # RDL.rdl_alias :Array, :size, :length + RDL.nowrap :Range RDL.type_params 'RDL::Type::SingletonType', [:t], :satisfies? unless RDL::Globals.type_params['RDL::Type::SingletonType'] RDL.type_params(:Range, [:t], nil, variance: [:+]) { |t| t.member?(self.begin) && t.member?(self.end) } unless RDL::Globals.type_params['Range'] RDL.type :Range, :each, '() { (t) -> %any } -> self' diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 799eca11..88f3c731 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -1,7 +1,6 @@ require 'minitest/autorun' $LOAD_PATH << File.dirname(__FILE__) + "/../lib" require 'rdl' -require 'types/core' class N1 class N2 @@ -146,12 +145,10 @@ class TestTypecheck < Minitest::Test def setup RDL.reset RDL.type TestTypecheck, :_any_object, '() -> Object', wrap: false # a method that could return true or false - RDL.readd_comp_types - RDL.type_params :Hash, [:k, :v], :all? unless RDL::Globals.type_params["Hash"] - RDL.type_params :Array, [:t], :all? unless RDL::Globals.type_params["Array"] - RDL.rdl_alias :Array, :size, :length + RDL.type_params 'RDL::Type::SingletonType', [:t], :satisfies? unless RDL::Globals.type_params["RDL::Type::SingletonType"] -=begin + + RDL.nowrap :Array RDL.type_params :Array, [:t], :all? RDL.type :Array, :[]=, '(Integer, t) -> t', wrap: false RDL.type :Array, :[]=, '(Integer, Integer, t) -> t', wrap: false @@ -164,23 +161,28 @@ def setup RDL.type :Array, :index, '() -> Enumerator', wrap: false RDL.type :Array, :map, '() {(t) -> u} -> Array', wrap: false RDL.type :Array, :map, '() -> Enumerator', wrap: false + RDL.type :Array, :length, '() -> ``output_type(trec, targs, :length, "Integer")``' + RDL.rdl_alias :Array, :size, :length + + RDL.nowrap :Hash RDL.type_params :Hash, [:k, :v], :all? RDL.type :Hash, :length, '() -> Integer', wrap: false - RDL.rdl_alias :Array, :size, :length RDL.type :Hash, :[], '(k) -> v', wrap: false RDL.type :Hash, :[]=, '(k, v) -> v', wrap: false -=end + + RDL.nowrap :Range RDL.type_params(:Range, [:t], nil, variance: [:+]) { |t| t.member?(self.begin) && t.member?(self.end) } unless RDL::Globals.type_params["Range"] RDL.type :Range, :each, '() { (t) -> %any } -> self' RDL.type :Range, :each, '() -> Enumerator' -=begin + + RDL.nowrap :Integer + RDL.type :Integer, :>=, '(Integer) -> %bool', wrap: false RDL.type :Integer, :<, '(Integer) -> %bool', wrap: false RDL.type :Integer, :>, '(Integer) -> %bool', wrap: false - RDL.type :Integer, :>=, '(Integer) -> %bool', wrap: false RDL.type :Integer, :+, '(Integer) -> Integer', wrap: false RDL.type :Integer, :&, '(Integer) -> Integer', wrap: false RDL.type :Integer, :*, '(Integer) -> Integer', wrap: false -=end + RDL.type :Integer, :to_s, '() -> String', wrap: false RDL.type :Kernel, 'self.puts', '(*[to_s : () -> String]) -> nil', wrap: false RDL.type :Kernel, :raise, '() -> %bot', wrap: false @@ -189,11 +191,12 @@ def setup RDL.type :Kernel, :raise, '(Exception, ?String, ?Array) -> %bot', wrap: false RDL.type :Object, :===, '(%any other) -> %bool', wrap: false RDL.type :Object, :clone, '() -> self', wrap: false -# RDL.type :String, :*, '(Integer) -> String', wrap: false -# RDL.type :String, :+, '(String) -> String', wrap: false + RDL.type :String, :*, '(Integer) -> String', wrap: false + RDL.type :String, :+, '(String) -> String', wrap: false # RDL.type :String, :===, '(%any) -> %bool', wrap: false -# RDL.type :String, :length, '() -> Integer', wrap: false + RDL.type :String, :length, '() -> Integer', wrap: false RDL.type :NilClass, :&, '(%any obj) -> false', wrap: false + @t3 = RDL::Type::SingletonType.new 3 @t4 = RDL::Type::SingletonType.new 4 @t5 = RDL::Type::SingletonType.new 5 @@ -1227,16 +1230,6 @@ def def_inst_pass(x, y) a = Array.new(x,y); RDL.instantiate!(a, "Integer"); a; e } ) - # below works with computational types - #assert_raises(RDL::Typecheck::StaticTypeError) { - assert ( - self.class.class_eval { - type "(Integer) -> Integer", typecheck: :now - def def_inst_hash_fail(x) hash = {}; hash["test"] = x; hash["test"]; end - } - ) -# } - =begin # below works with computational types assert_raises(RDL::Typecheck::StaticTypeError) { From 5318987709423478181c2540ff4c0ed4018a7886 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Sun, 3 May 2020 19:43:51 -0400 Subject: [PATCH 047/124] record module includes/extends --- lib/rdl/boot.rb | 7 +++++++ lib/rdl/wrap.rb | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index fb3ad74a..e7c7c1cd 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -90,6 +90,11 @@ module RDL::Globals # If non-nil, should be a symbol. Added, untyped methods will be tagged # with that symbol @infer_added = nil + + # Hash + # Map from module names k to pairs [class/module v, :include/:extend] indicating the module + # k was included/extended in v + @module_mixees = Hash.new end class << RDL::Globals # add accessors and readers for module variables @@ -110,6 +115,7 @@ class << RDL::Globals # add accessors and readers for module variables attr_accessor :no_infer_meths attr_accessor :no_infer_files attr_accessor :infer_added + attr_accessor :module_mixees end # Create switches to control whether wrapping happens and whether @@ -200,6 +206,7 @@ def self.reset @no_infer_meths = [] @no_infer_files = [] @infer_added = nil + @module_mixees = Hash.new @parser = RDL::Type::Parser.new diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 7a27eee4..057d2d64 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -1120,6 +1120,16 @@ def method_added(meth) nil end + def included(other) + RDL::Globals.module_mixees[self] = [] unless RDL::Globals.module_mixees[self] + RDL::Globals.module_mixees[self] << [other, :include] + end + + def extended(other) + RDL::Globals.module_mixees[self] = [] unless RDL::Globals.module_mixees[self] + RDL::Globals.module_mixees[self] << [other, :extend] + end + } end From b79be68bbb0d9d91c3473701cd146b3233e90c46 Mon Sep 17 00:00:00 2001 From: Geoff Huston Date: Thu, 7 May 2020 10:42:49 -0400 Subject: [PATCH 048/124] Continue on Inference Errors (#98) * testing out different ways of unit testing * MethodTypes now have solutions * writing method tests * writing method tests * cleaned up some lint * cleaned up .gitignore * cleaned up some debugging code * more cleanup; fixed `method.comment` crash * matching up constraint * cleaned up wrap.rb * cleaned up boot, moved a require from `constraint` * Added more unit tests * re-adding needed library types * linting test_infer * working towards catching type errors * working towards catching type errors * added no_print_XXX! to tests, to show type variables in expected output * changed variable printing to print long names for type-variables * removed solution field from type * added pattern matcher for type variables * Moved VarType matching into types' match functions * removed leading underscores from some match parameters * not crashing on goodcheck example * not crashing on goodcheck example * moved some fixes to another branch * moved some fixes to another branch * intermediate checkin * Update hash.rb get rid of redundant `to_hash` type, add a type for `to_h` * cleaned up logging * more logging; no longer crashes on goodcheck * cleaned up some trace logging * Moved logging out of RDL::Util and into RDL::Logging * added option to disable log colors * changed log levels of some output * added warning for generating nested choice types * cleaned up some lint * cleaning more lint * fixed log error reporting * cleaned up some old code Co-authored-by: mckaz --- lib/rdl/boot.rb | 3 +- lib/rdl/class_indexer.rb | 4 +- lib/rdl/config.rb | 13 +++- lib/rdl/constraint.rb | 138 ++++++++++++++++++++--------------- lib/rdl/heuristics.rb | 10 +-- lib/rdl/logging.rb | 71 ++++++++++++++++++ lib/rdl/typecheck.rb | 79 ++++++++++++++++---- lib/rdl/types/choice.rb | 12 ++- lib/rdl/types/finite_hash.rb | 1 + lib/rdl/types/optional.rb | 1 + lib/rdl/types/type.rb | 7 ++ lib/rdl/types/var.rb | 13 +++- lib/rdl/types/vararg.rb | 1 + lib/rdl/util.rb | 17 +++++ lib/rdl/wrap.rb | 25 +++++-- lib/types/core/hash.rb | 4 +- rdl.gemspec | 2 +- test/test_infer.rb | 1 + 18 files changed, 302 insertions(+), 100 deletions(-) create mode 100644 lib/rdl/logging.rb diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index d9eef93e..8bcad52e 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -3,12 +3,13 @@ require 'set' require 'parser/current' #require 'method_source' - +require 'colorize' module RDL end require 'rdl/config.rb' +require 'rdl/logging.rb' def RDL.config yield(RDL::Config.instance) end diff --git a/lib/rdl/class_indexer.rb b/lib/rdl/class_indexer.rb index 88f2ea1e..91d964f1 100644 --- a/lib/rdl/class_indexer.rb +++ b/lib/rdl/class_indexer.rb @@ -22,7 +22,7 @@ def get_const_name(ast) raise "unexpected const ast #{ast}" end end - + def reset_class @current_class = "main" end @@ -72,7 +72,7 @@ def on_module(node) @current_class.sub!(entered_class, "") reset_class if @current_class.empty? -=begin +=begin if @current_class.include?("::") @current_class.sub!("::"+module_name, "") else diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index 72564978..acf7b3bb 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -8,7 +8,9 @@ class RDL::Config attr_accessor :report, :get_types, :guess_types attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types attr_accessor :type_defaults, :infer_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type - attr_accessor :use_precise_string, :number_mode, :use_unknown_types, :infer_empties, :infer_verbose + attr_accessor :use_precise_string, :number_mode, :use_unknown_types, :infer_empties + attr_accessor :continue_on_errors + attr_accessor :log_levels, :disable_log_colors def initialize RDL::Config.reset(self) @@ -23,7 +25,7 @@ def self.reset(c=RDL::Config.instance) c.at_exit_installed = false c.weak_update_promote = false c.promote_widen = false - c.type_defaults = { wrap: true, typecheck: false} + c.type_defaults = { wrap: true, typecheck: false } c.infer_defaults = { time: nil } c.pre_defaults = { wrap: true } c.post_defaults = { wrap: true } @@ -36,7 +38,12 @@ def self.reset(c=RDL::Config.instance) c.number_mode = false c.use_unknown_types = false c.infer_empties = true ## if [] and {} should be typed as Array and Hash - c.infer_verbose = false + c.continue_on_errors = false + c.disable_log_colors = false + c.log_levels = { + typecheck: :info, + inference: :info + } end def add_nowrap(*klasses) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 690f624a..032ccbc7 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -3,9 +3,9 @@ module RDL::Typecheck def self.resolve_constraints - puts "Starting constraint resolution..." if RDL::Config.instance.infer_verbose + RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." RDL::Globals.constrained_types.each { |klass, name| - puts "Resolving constraints from #{klass} and #{name}" if RDL::Config.instance.infer_verbose + RDL::Logging.log :inference, :debug, "Resolving constraints from #{RDL::Util.pp_klass_method(klass, name)}" typ = RDL::Globals.info.get(klass, name, :type) ## If typ is an Array, then it's an array of method types ## but for inference, we only use a single method type. @@ -17,26 +17,33 @@ def self.resolve_constraints end var_types.each { |var_type| - if var_type.var_type? || var_type.optional_var_type? || var_type.vararg_var_type? - var_type = var_type.type if var_type.optional_var_type? || var_type.vararg_var_type? - var_type.lbounds.each { |lower_t, ast| - var_type.add_and_propagate_lower_bound(lower_t, ast) - } - var_type.ubounds.each { |upper_t, ast| - var_type.add_and_propagate_upper_bound(upper_t, ast) - } - elsif var_type.fht_var_type? - var_type.elts.values.each { |v| - vt = v.optional_var_type? || v.vararg_var_type? ? v.type : v - vt.lbounds.each { |lower_t, ast| - vt.add_and_propagate_lower_bound(lower_t, ast) + begin + if var_type.var_type? || var_type.optional_var_type? || var_type.vararg_var_type? + var_type = var_type.type if var_type.optional_var_type? || var_type.vararg_var_type? + var_type.lbounds.each { |lower_t, ast| + RDL::Logging.log :typecheck, :trace, "#{lower_t} <= #{var_type}" + var_type.add_and_propagate_lower_bound(lower_t, ast) } - vt.ubounds.each { |upper_t, ast| - vt.add_and_propagate_upper_bound(upper_t, ast) + var_type.ubounds.each { |upper_t, ast| + var_type.add_and_propagate_upper_bound(upper_t, ast) } - } - else - raise "Got unexpected type #{var_type}." + elsif var_type.fht_var_type? + var_type.elts.values.each { |v| + vt = v.optional_var_type? || v.vararg_var_type? ? v.type : v + vt.lbounds.each { |lower_t, ast| + vt.add_and_propagate_lower_bound(lower_t, ast) + } + vt.ubounds.each { |upper_t, ast| + vt.add_and_propagate_upper_bound(upper_t, ast) + } + } + else + raise "Got unexpected type #{var_type}." + end + rescue => e + raise e unless RDL::Config.instance.continue_on_errors + + RDL::Logging.log :inference, :debug_error, "Caught error when resolving constraints for #{var_type}; skipping..." end } } @@ -99,8 +106,8 @@ def self.extract_var_sol(var, category) #sol = typ end rescue RDL::Typecheck::StaticTypeError => e - puts "Attempted to apply rule #{name} to var #{var}, but go the following error: " - puts e + RDL::Logging.log :typecheck, :debug_error, "Attempted to apply heuristic rule #{name} to var #{var}" + RDL::Logging.log :typecheck, :trace, "... but got the following error: #{e}" undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is end @@ -132,8 +139,9 @@ def self.extract_var_sol(var, category) sol = RDL::Type::TupleType.new(*new_params) end rescue RDL::Typecheck::StaticTypeError => e - puts "Attempted to apply solution #{sol} for var #{var} but got the following error: " - puts e + RDL::Logging.log :typecheck, :debug_error, "Attempted to apply solution #{sol} for var #{var}" + RDL::Logging.log :typecheck, :trace, "... but got the following error: #{e}" + undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is sol = var @@ -256,9 +264,9 @@ def self.make_extraction_report(typ_sols) total_potential += 1 end else - puts "Difference encountered for #{klass}##{meth}." - puts "Inferred: #{typ}" - puts "Original: #{orig_typ}" + RDL::Logging.log :inference, :debug, "Difference encountered for #{klass}##{meth}." + RDL::Logging.log :inference, :debug, "Inferred: #{typ}" + RDL::Logging.log :inference, :debug, "Original: #{orig_typ}" if orig_typ.is_a?(RDL::Type::MethodType) total_potential += orig_typ.args.size + 1 ## 1 for ret total_potential += orig_typ.block.args.size + 1 if !orig_typ.block.nil? @@ -294,52 +302,66 @@ def self.make_extraction_report(typ_sols) # incomplete_types.each { |row| csv << row } # } - puts "Total correct (that could be automatically inferred): #{correct_types}" - puts "Total # method types: #{meth_types}" - puts "Total # variable types: #{var_types}" - puts "Total # individual types: #{total_potential}" + RDL::Logging.log_header :inference, :info, "Extraction Complete" + RDL::Logging.log :inference, :info, "Total correct (that could be automatically inferred): #{correct_types}" + RDL::Logging.log :inference, :info, "Total # method types: #{meth_types}" + RDL::Logging.log :inference, :info, "Total # variable types: #{var_types}" + RDL::Logging.log :inference, :info, "Total # individual types: #{total_potential}" end def self.extract_solutions(render_report = true) ## Go through once to come up with solution for all var types. #until !@new_constraints + RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" + typ_sols = {} loop do @new_constraints = false typ_sols = {} - puts "\n\nRunning solution extraction..." if RDL::Config.instance.infer_verbose + + RDL::Logging.log :inference, :info, "Running solution extraction..." + RDL::Globals.constrained_types.each { |klass, name| - RDL::Type::VarType.no_print_XXX! - typ = RDL::Globals.info.get(klass, name, :type) - if typ.is_a?(Array) - raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 - tmeth = typ[0] + begin + RDL::Logging.log :inference, :debug, "Extracting #{RDL::Util.pp_klass_method(klass, name)}" - arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) + RDL::Type::VarType.no_print_XXX! + typ = RDL::Globals.info.get(klass, name, :type) + if typ.is_a?(Array) + raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 + tmeth = typ[0] - block_string = block_sol ? " { #{block_sol} }" : nil - puts "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" if RDL::Config.instance.infer_verbose + arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) - RDL::Type::VarType.print_XXX! - block_string = block_sol ? " { #{block_sol} }" : nil - typ_sols[[klass.to_s, name.to_sym]] = "(#{arg_sols.join(', ')})#{block_string} -> #{ret_sol}" - elsif name.to_s == "splat_param" - else - ## Instance/Class (also some times splat parameter) variables: - ## There is no clear answer as to what to do in this case. - ## Just need to pick something in between bounds (inclusive). - ## For now, plan is to just use lower bound when it's not empty/%bot, - ## otherwise use upper bound. - ## Can improve later if desired. - var_sol = extract_var_sol(typ, :var) - #typ.solution = var_sol - puts "Extracted solution for #{klass} variable #{name} is #{var_sol}." if RDL::Config.instance.infer_verbose - - RDL::Type::VarType.print_XXX! - typ_sols[[klass.to_s, name.to_sym]] = var_sol.to_s + block_string = block_sol ? " { #{block_sol} }" : nil + RDL::Logging.log :inference, :trace, "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" + + RDL::Type::VarType.print_XXX! + block_string = block_sol ? " { #{block_sol} }" : nil + typ_sols[[klass.to_s, name.to_sym]] = "(#{arg_sols.join(', ')})#{block_string} -> #{ret_sol}" + elsif name.to_s == "splat_param" + else + ## Instance/Class (also some times splat parameter) variables: + ## There is no clear answer as to what to do in this case. + ## Just need to pick something in between bounds (inclusive). + ## For now, plan is to just use lower bound when it's not empty/%bot, + ## otherwise use upper bound. + ## Can improve later if desired. + var_sol = extract_var_sol(typ, :var) + #typ.solution = var_sol + RDL::Logging.log :inference, :trace, "Extracted solution for #{klass} variable #{name} is #{var_sol}." + + RDL::Type::VarType.print_XXX! + typ_sols[[klass.to_s, name.to_sym]] = var_sol.to_s + end + rescue => e + raise e unless RDL::Config.instance.continue_on_errors + + RDL::Logging.log :inference, :debug_error, "Error while exctracting solution for #{RDL::Util.pp_klass_method(klass, name)}: #{e}; continuing..." + typ_sols[[klass.to_s, name.to_sym]] = "-- Extraction Error --" end } - break if !@new_constraints + break if !@new_constraints end make_extraction_report(typ_sols) if render_report diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 051b4460..4090db76 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -4,7 +4,7 @@ class RDL::Heuristic def self.add(name, &blk) raise RuntimeError, "Expected heuristic name to be Symbol, given #{name}." unless name.is_a? Symbol - raise RuntimeError, "Expected block to be provided for heuristic." if blk.nil? + raise RuntimeError, "Expected block to be provided for heuristic." if blk.nil? @rules[name] = blk end @@ -37,20 +37,20 @@ def self.struct_to_nominal(var_type) union = RDL::Type::UnionType.new(*nom_sing_types).canonical #struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds - + return union ## used to add and propagate here. Now that this is a heuristic, this should be done after running the rule. #var_type.add_and_propagate_upper_bound(union, nil) end - + end class << RDL::Heuristic attr_reader :rules end -class String +class String def is_rails_model? return false unless defined? Rails ActiveRecord::Base.descendants.map { |d| d.to_s }.include? self @@ -70,7 +70,7 @@ def is_pluralized_model? def model_set_type RDL::Globals.parser.scan_str "#T Array<#{singularize.camelize}> or ActiveRecord_Relation<#{singularize.camelize}>" end - + end if defined? Rails diff --git a/lib/rdl/logging.rb b/lib/rdl/logging.rb new file mode 100644 index 00000000..27272c0d --- /dev/null +++ b/lib/rdl/logging.rb @@ -0,0 +1,71 @@ + +class RDL::Logging + def self.log_level_colors(a) + colors = { + trace: :yellow, + debug: :green, + debug_error: :red, + info: :light_green, + warning: :light_yellow, + error: :light_red, + critical: { color: :light_red, mode: :bold } + } + colors[a] + end + + def self.log_level_leq(a, b) + levels = [:trace, :debug, :debug_error, :info, :warning, :error, :critical] + a_idx = levels.find_index(a) + b_idx = levels.find_index(b) + + raise "#{a} is not a valid log level; valid levels are #{levels}" unless a_idx + raise "#{b} is not a valid log level; valid levels are #{levels}" unless b_idx + + a_idx <= b_idx + end + + def self.log_str(area, level, message, message_color: nil) + tracing = RDL::Config.instance.log_levels[area] == :trace + no_color = RDL::Config.instance.disable_log_colors + + place = caller.find { |s| s.include?('lib/rdl') && !s.include?('in `log') } + file_line = place.match(/.*\/(.*?\.rb:[0-9]+)/)[1] + meth = place.match(/in `(block.*?in )?(.*?)'/)[2] + + lc = log_level_colors(level) + + meth = meth.to_s.colorize(lc) unless no_color + file_line = file_line.colorize(:light_black) unless no_color + message = message.colorize(message_color) if message_color && !no_color + + level_str = '' + level_str = "#{level.to_s.upcase} " if no_color + + depth_string = '' + depth_string = " #{caller.length - 1}" if tracing + leader = level_str + file_line + ' [' + meth + "#{depth_string}]" + + spacers = '' + spacers = ' ' * ((caller.length - 1) / 2) if tracing + + spacers + leader + ' ' + message + end + + def self.log_header(area, level, header) + return unless log_level_leq(RDL::Config.instance.log_levels[area], level) + no_color = RDL::Config.instance.disable_log_colors + if no_color + stars = "***************" + puts "\n" + stars + ' ' + log_str(area, level, header, message_color: { mode: :bold }) + ' ' + stars + else + puts "\n" + log_str(area, level, header, message_color: { mode: :bold }) + end + end + + def self.log(area, level, message) + return unless log_level_leq(RDL::Config.instance.log_levels[area], level) + + puts log_str(area, level, message, message_color: :white) + end + +end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 53b60985..24427248 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -188,7 +188,13 @@ def self.is_RDL(node) def self.get_ast(klass, meth) file, line = RDL::Globals.info.get(klass, meth, :source_location) - raise RuntimeError, "No file for #{RDL::Util.pp_klass_method(klass, meth)}" if file.nil? + + if file.nil? + return nil if RDL::Config.instance.continue_on_errors + + raise RuntimeError, "No file for #{RDL::Util.pp_klass_method(klass, meth)}" if file.nil? + end + raise RuntimeError, "static type checking in irb not supported" if file == "(irb)" if file == "(pry)" # no caching... @@ -205,25 +211,50 @@ def self.get_ast(klass, meth) digest = Digest::MD5.file file cache_hit = ((RDL::Globals.parser_cache.has_key? file) && (RDL::Globals.parser_cache[file][0] == digest)) + unless cache_hit - file_ast = Parser::CurrentRuby.parse_file file - mapper = ASTMapper.new(file) - mapper.process(file_ast) - cache = {ast: file_ast, line_defs: mapper.line_defs} - RDL::Globals.parser_cache[file] = [digest, cache] + begin + file_ast = Parser::CurrentRuby.parse_file file + mapper = ASTMapper.new(file) + mapper.process(file_ast) + cache = {ast: file_ast, line_defs: mapper.line_defs} + RDL::Globals.parser_cache[file] = [digest, cache] + rescue => e + RDL::Logging.log :typecheck, :error, "Failed to parse #{file}; #{e}" + return nil if RDL::Config.instance.continue_on_errors + + raise e + end end ast = RDL::Globals.parser_cache[file][1][:line_defs][line] return ast end def self.infer(klass, meth) - puts "*************** Infering method #{meth} from class #{klass} ***************" if RDL::Config.instance.infer_verbose + _infer(klass, meth) + rescue => exn + raise exn unless RDL::Config.instance.continue_on_errors + RDL::Logging.log :inference, :warning, "Error; Skipping inference for #{RDL::Util.pp_klass_method(klass, meth)}" + RDL::Logging.log :inference, :debug, "... got exception: #{exn}" + # RDL::Globals.info.set(klass, meth, :type, [RDL::Globals.types[:dyn]]) + end + + def self._infer(klass, meth) + RDL::Logging.log_header :inference, :debug, "Infering #{RDL::Util.pp_klass_method(klass, meth)}" + RDL::Config.instance.use_comp_types = true RDL::Config.instance.number_mode = true @var_cache = {} ast = get_ast(klass, meth) if ast.nil? - puts "Warning: Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}; skipping method" + RDL::Logging.log :inference, :warning, "Warning: Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}; skipping method" + + # if RDL::Config.instance.continue_on_errors + # puts "#{warning_text} recording %dyn" if RDL::Config.instance.convert_to_dyn_verbose + # RDL::Globals.info.set(klass, meth, :type, [RDL::Globals.types[:dyn]]) + # return + # end + return end types = RDL::Globals.info.get(klass, meth, :type) @@ -253,6 +284,7 @@ def self.infer(klass, meth) else raise RuntimeError, "Unexpected ast type #{ast.type}" end + raise RuntimeError, "Method #{name} defined where method #{meth} expected" if name.to_sym != meth context_types = RDL::Globals.info.get(klass, meth, :context_types) @@ -294,7 +326,7 @@ def self.infer(klass, meth) RDL::Globals.info.set(klass, meth, :typechecked, true) RDL::Globals.constrained_types << [klass, meth] - puts "Done with constraint generation." if RDL::Config.instance.infer_verbose + RDL::Logging.log :inference, :debug, "Done with constraint generation." end def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) @@ -555,13 +587,23 @@ def self.get_super_owner_from_class(cls, m) cls.superclass.instance_method(m).owner end + def self.tc(scope, env, e) + _tc(scope, env, e) + rescue => exn + raise exn unless RDL::Config.instance.continue_on_errors + + RDL::Logging.log :typecheck, :debug_error, "#{exn}; returning %dyn" + + [env, RDL::Globals.types[:dyn]] + end + # The actual type checking logic. # [+ scope +] tracks flow-insensitive information about the current scope, excluding local variables # [+ env +] is the (local variable) Env # [+ e +] is the expression to type check # Returns [env', t, eff], where env' is the type environment at the end of the expression # and t is the type of the expression. t is always canonical. - def self.tc(scope, env, e) + def self._tc(scope, env, e) case e.type when :nil [env, RDL::Globals.types[:nil], [:+, :+]] @@ -2470,21 +2512,26 @@ def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_un def self.make_unknown_method_type(klass, meth) raise "Tried to make unknown method type for class #{klass} method #{meth}, but no such method was found." unless (RDL::Util.to_class(klass).instance_methods + RDL::Util.to_class(klass).private_instance_methods).include?(meth) - params = RDL::Util.to_class(klass).instance_method(meth).parameters + + klass_obj = RDL::Util.to_class(klass) + + params = klass_obj.instance_method(meth).parameters arg_types = [] keyword_args = {} params.each { |param| + var_name = param[1] + case param[0] when :req - arg_types << RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1]) + arg_types << RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: var_name) when :opt - arg_types << RDL::Type::OptionalType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) + arg_types << RDL::Type::OptionalType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: var_name)) when :rest - arg_types << RDL::Type::VarargType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) + arg_types << RDL::Type::VarargType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: var_name)) when :key - keyword_args[param[1]] = RDL::Type::OptionalType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1])) + keyword_args[var_name] = RDL::Type::OptionalType.new(RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: var_name)) when :keyreq - keyword_args[param[1]] = RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: param[1]) + keyword_args[var_name] = RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: var_name) when :block ## all method types will be given a variable type for blocks anyway, so no need to add a new param here when :keyrest diff --git a/lib/rdl/types/choice.rb b/lib/rdl/types/choice.rb index 291e8d89..106def0f 100644 --- a/lib/rdl/types/choice.rb +++ b/lib/rdl/types/choice.rb @@ -4,7 +4,7 @@ class ChoiceType < Type class << self alias :__new__ :new end - + ## variational typing! attr_accessor :choices, :connecteds attr_accessor :ubounds # upper bounds this ChoiceType has been compared with using <= @@ -25,6 +25,10 @@ def initialize(choices, connecteds=[]) @lbounds = [] @activated = nil @hash = 101 + @choices.hash + + if choices.values.any? { |t| t.is_a? ChoiceType } + RDL::Logging.log :inference, :critical, "WARNING: Generating a ChoiceType with nested ChoiceTypes." + end end def add_connecteds(*connecteds) @@ -37,7 +41,7 @@ def add_connecteds(*connecteds) def <=(other) return Type.leq(self, other) end - + def to_s typs = [] @choices.each { |choice, typ| @@ -52,7 +56,7 @@ def ==(other) # :nodoc: end alias eql? == - + def remove!(choice, removed_hash: {}) raise "Expected integer, got #{choice}." unless choice.is_a? Integer removed = @choices.delete(choice) @@ -121,5 +125,5 @@ def hash @hash end - end + end end diff --git a/lib/rdl/types/finite_hash.rb b/lib/rdl/types/finite_hash.rb index 23e41fdb..9be96bb2 100644 --- a/lib/rdl/types/finite_hash.rb +++ b/lib/rdl/types/finite_hash.rb @@ -12,6 +12,7 @@ class FiniteHashType < Type attr_accessor :ubounds # upper bounds this tuple has been compared with using <= attr_accessor :lbounds # lower bounds... attr_accessor :default # For hashes created with Hash.new, gives the default type to return for non-existent keys + attr_accessor :solution # to store the solution from inference # [+ elts +] is a map from keys to types def initialize(elts, rest, default: nil) diff --git a/lib/rdl/types/optional.rb b/lib/rdl/types/optional.rb index 52e29ac0..9f763142 100644 --- a/lib/rdl/types/optional.rb +++ b/lib/rdl/types/optional.rb @@ -1,6 +1,7 @@ module RDL::Type class OptionalType < Type attr_reader :type + attr_accessor :solution # to store the solution from inference def initialize(type) raise RuntimeError, "Attempt to create optional type with non-type" unless type.is_a? Type diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 8a4781da..93b46432 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -104,10 +104,16 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end return true elsif right.is_a?(VarType) && right.to_infer + + RDL::Logging.log :typecheck, :trace, "#{right}.is_a VarType" + RDL::Logging.log :typecheck, :trace, "\t#{left} <= #{right}" if deferred_constraints.nil? + RDL::Logging.log :typecheck, :trace, 'no deferred_constraints' right.add_lbound(left, ast, new_cons, propagate: propagate) unless (right.lbounds.any? { |t, loc| t == left || t.hash == left.hash } || right.equal?(left)) else + RDL::Logging.log :typecheck, :trace, 'deferred_constraints:' deferred_constraints << [left, right] + deferred_constraints.each { |k, v| if v.is_a?(Array) then v.each { |v| RDL::Logging.log(:typecheck, :trace, "#{k} <= #{v[1] || v}") } else RDL::Logging.log(:typecheck, :trace, "#{k} <= #{v}") end } end return true end @@ -185,6 +191,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con } lb_var_choices.each { |vartype, choice_hash| + RDL::Logging.log :typecheck, :trace, vartype.to_s if choice_hash.values.uniq.size == 1 RDL::Type::Type.leq(choice_hash.values[0], vartype, nil, false, deferred_constraints, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) else diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index dd13096b..c779250c 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -76,26 +76,35 @@ def add_and_propagate_upper_bound(typ, ast, new_cons = {}) } end - ## Similar to above. def add_and_propagate_lower_bound(typ, ast, new_cons = {}) return if self.equal?(typ) + RDL::Logging.log :typecheck, :trace, "#{typ} <= #{self}" + if !@lbounds.any? { |t, a| t == typ } + RDL::Logging.log :typecheck, :trace, '@lbounds.any' @lbounds << [typ, ast] new_cons[self] = new_cons[self] ? new_cons[self] | [[:lower, typ, ast]] : [[:lower, typ, ast]] end + RDL::Logging.log :typecheck, :trace, 'ubounds.each' @ubounds.each { |upper_t, a| + RDL::Logging.log :typecheck, :trace, "ubound: #{upper_t}" if upper_t.is_a?(VarType) upper_t.add_and_propagate_lower_bound(typ, ast, new_cons) unless upper_t.lbounds.any? { |t, _| t == typ } else if typ.is_a?(VarType) && !typ.ubounds.any? { |t, _| t == upper_t } new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:upper, upper_t, ast]] : [[:upper, upper_t, ast]] end + RDL::Logging.log :typecheck, :trace, "about to check #{typ} <= #{upper_t} with".colorize(:green) + + RDL::Util.each_leq_constraints(new_cons) { |a, b| RDL::Logging.log(:typecheck, :trace, "#{a} <= #{b}") } + unless RDL::Type::Type.leq(typ, upper_t, {}, false, ast: ast, no_constraint: true, propagate: true, new_cons: new_cons) d1 = ast.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") d2 = a.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{typ} <= #{upper_t} generated during inference.\n #{d1}\n #{d2}") end + RDL::Logging.log :typecheck, :trace, "Checked #{typ} <= #{upper_t}".colorize(:green) end } end @@ -112,6 +121,8 @@ def add_ubound(typ, ast, new_cons = {}, propagate: false) def add_lbound(typ, ast, new_cons = {}, propagate: false) #raise "About to add lower bound #{typ} <= #{self}" if typ.is_a?(VarType) && !typ.to_infer + # raise "ChoiceType!!!!" if typ.is_a? ChoiceType + RDL::Logging.log :typecheck, :trace, "#{self}.add_lbound(#{typ}); " + 'propagate'.colorize(:yellow) + " = #{propagate}" if propagate add_and_propagate_lower_bound(typ, ast, new_cons) elsif !@lbounds.any? { |t, a| t == typ } diff --git a/lib/rdl/types/vararg.rb b/lib/rdl/types/vararg.rb index 1f19a14c..5bd6aaa7 100644 --- a/lib/rdl/types/vararg.rb +++ b/lib/rdl/types/vararg.rb @@ -1,6 +1,7 @@ module RDL::Type class VarargType < Type attr_reader :type + attr_accessor :solution # to store the solution from inference @@cache = {} diff --git a/lib/rdl/util.rb b/lib/rdl/util.rb index 9abe234a..7426f7c8 100644 --- a/lib/rdl/util.rb +++ b/lib/rdl/util.rb @@ -44,6 +44,23 @@ def self.remove_singleton_marker(klass) end end + def self.each_leq_constraints(cons) + cons.each_pair do |k, vs| + vs.each do |v| + if v[0] == :lower + yield v[1], k + else + yield k, v[1] + end + end + end + nil + end + + def self.puts_constraints(cons) + each_leq_constraints(cons) { |a, b| puts "#{a} <= #{b}" } + end + def self.add_singleton_marker(klass) return SINGLETON_MARKER + klass end diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 7a27eee4..12d875f7 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -246,6 +246,7 @@ def self.do_method_added(the_self, sing, klass, meth) loc = the_self.instance_method(meth).source_location end RDL::Globals.info.set(klass, meth, :source_location, loc) + # Apply any deferred contracts and reset list if RDL::Globals.deferred.size > 0 @@ -855,21 +856,31 @@ def self.do_infer(sym, render_report: true) $stn = 0 num_casts = 0 time = Time.now + RDL::Globals.to_infer[sym].each { |klass, meth| - RDL::Typecheck.infer klass, meth - num_casts += RDL::Typecheck.get_num_casts + begin + RDL::Typecheck.infer klass, meth + num_casts += RDL::Typecheck.get_num_casts + rescue Exception => e + if RDL::Config.instance.continue_on_errors + RDL::Logging.log :inference, :debug, "Error: #{e}; recording %dyn" + # RDL::Globals.info.set(klass, meth, :type, [RDL::Globals.types[:dyn]]) + else + raise e + end + end } + RDL::Globals.to_infer[sym] = Set.new RDL::Typecheck.resolve_constraints RDL::Typecheck.extract_solutions render_report time = Time.now - time - if RDL::Config.instance.infer_verbose - puts "Total time taken: #{time}." - puts "Total number of type casts used: #{num_casts}." - puts "Total amount of time spent on stn: #{$stn}." - end + + RDL::Logging.log :inference, :info, "Total time taken: #{time}." + RDL::Logging.log :inference, :info, "Total number of type casts used: #{num_casts}." + RDL::Logging.log :inference, :info, "Total amount of time spent on stn: #{$stn}." end def self.load_sequel_schema(db) diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 943e4799..44463619 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -421,7 +421,6 @@ def Hash.to_a_output_type(trec) end -RDL.type :Hash, :to_hash, '() -> self' #RDL.type :Hash, :values, '() -> ``output_type(trec, targs, :values, "Array")``' RDL.type :Hash, :values, '() -> ``values_output(trec)``' def Hash.values_output(trec) @@ -541,7 +540,8 @@ def Hash.values_at_output(trec, targs) RDL.type :Hash, :shift, '() -> Array' # RDL.type :Hash, :to_a, '() -> Array>' RDL.type :Hash, :to_a, '() -> Array>' -RDL.type :Hash, :to_hash, '() -> Hash' +RDL.type :Hash, :to_hash, '() -> self' +RDL.type :Hash, :to_h, '() -> self' RDL.type :Hash, :values, '() -> Array' RDL.type :Hash, :values_at, '(*k) -> Array', effect: [:+, :+] RDL.type :Hash, :with_indifferent_access, '() -> self' diff --git a/rdl.gemspec b/rdl.gemspec index 3707abde..21321f56 100644 --- a/rdl.gemspec +++ b/rdl.gemspec @@ -21,6 +21,6 @@ EOF s.add_runtime_dependency 'parser', '~>2.3', '>= 2.3.1.4' s.add_runtime_dependency 'sql-parser', '~>0.0.2' s.add_runtime_dependency 'method_source' - s.add_development_dependency 'colorize', '~>0.8', '>= 0.8.1' + s.add_runtime_dependency 'colorize', '~>0.8', '>= 0.8.1' s.add_development_dependency 'coderay', '~>1.2', '>= 1.1.2' end diff --git a/test/test_infer.rb b/test/test_infer.rb index d5528a49..5be5e2fa 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -18,6 +18,7 @@ def setup # TODO: this will go away after config/reset RDL::Config.instance.use_precise_string = false + RDL::Config.instance.log_levels[:inference] = :error RDL.readd_comp_types RDL.type_params :Hash, [:k, :v], :all? unless RDL::Globals.type_params['Hash'] From c0fec0b210252ae7e0bc013508a68605ce500eba Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Fri, 8 May 2020 17:27:31 -0400 Subject: [PATCH 049/124] support type checking with module mixins --- lib/rdl/boot.rb | 3 ++- lib/rdl/typecheck.rb | 15 +++++++---- test/test_typecheck.rb | 56 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 8ad6953d..5d60a837 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -207,7 +207,8 @@ def self.reset @no_infer_meths = [] @no_infer_files = [] @infer_added = nil - @module_mixees = Hash.new + # @module_mixees = Hash.new - resetting this breaks test cases that assume + # we know which modules are mixed into which other modules @parser = RDL::Type::Parser.new diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 53b60985..df3bc502 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -298,6 +298,7 @@ def self.infer(klass, meth) end def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) + # puts "Typechecking #{klass}##{meth}" ast = get_ast(klass, meth) unless ast raise RuntimeError, "Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}" if ast.nil? types = RDL::Globals.info.get(klass, meth, :type) unless types @@ -1774,13 +1775,17 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, unless ts klass = RDL::Util.to_class(scope[:klass]) if klass.class == Module - # defer type checking until module is included - - - + # Module mixin handle + # Here a module method is calling a non-existent method; check for it in all mixees + nts = RDL::Globals.module_mixees[klass].map { |k, kind| RDL::Type::NominalType.new(k) } + return [@types[:bot], :+] if nts.empty? # if module not mixed in, this call can't happen; so %bot plus pure + ut = RDL::Type::UnionType.new(*nts) + ts, es = tc_send(scope, env, ut, meth, tactuals, block, e, op_asgn) + return [[ts], [es]] end - error :no_instance_method_type, [trecv.name, meth], e unless ts + error :no_instance_method_type, [trecv.name, meth], e end + # error :no_instance_method_type, [trecv.name, meth], e unless ts inst = {self: trecv} self_klass = RDL::Util.to_class(trecv.name) when RDL::Type::GenericType diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 88f3c731..71c736fb 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -1927,6 +1927,62 @@ def foo(x); end assert_nil ChildWithoutType.new.foo(1) end + module ModuleMixin1 + def caller(x) + return in_mixee(x) + end + end + class ModuleMixee1a + include ModuleMixin1 + + def in_mixee(y) + return y + end + end + class ModuleMixee1b + include ModuleMixin1 + + def in_mixee(y) + return 3 + end + end + + def test_mixins_1 + RDL.type ModuleMixin1, :caller, '(Integer) -> Integer', typecheck: :mm1 + RDL.type ModuleMixee1a, :in_mixee, '(Integer) -> Integer', typecheck: :mm1 + RDL.type ModuleMixee1b, :in_mixee, '(Integer) -> Integer', typecheck: :mm1 + RDL.do_typecheck :mm1 + end + + module ModuleMixin2 + def caller(x) + return in_mixee(x) + end + end + class ModuleMixee2a + include ModuleMixin2 + + def in_mixee(y) + return "foo" + end + end + class ModuleMixee2b + include ModuleMixin2 + + def in_mixee(y) + return 3 + end + end + + def test_mixins_2 + RDL.type ModuleMixin2, :caller, '(Integer) -> Integer', typecheck: :mm2a + RDL.type ModuleMixee2a, :in_mixee, '(Integer) -> String', typecheck: :mm2b + RDL.type ModuleMixee2b, :in_mixee, '(Integer) -> Integer', typecheck: :mm2c + RDL.do_typecheck :mm2b + RDL.do_typecheck :mm2c + assert_raises(RDL::Typecheck::StaticTypeError) { RDL.do_typecheck :mm2a } + end + def test_object_sing_method assert_raises(RDL::Typecheck::StaticTypeError) { Object.class_eval do From 355e9b271d535af418b6578b6862e27cc6a4bf0d Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Fri, 8 May 2020 17:44:57 -0400 Subject: [PATCH 050/124] better error message for unsupported expression kinds --- lib/rdl/typecheck.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 10cbc344..2badaed7 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -1454,7 +1454,7 @@ def self._tc(scope, env, e) [envi, tres.canonical, effres] } else - raise RuntimeError, "Expression kind #{e.type} unsupported, for expression #{e}" + error :unsupported_expression, [e.type, e], e end end @@ -2706,7 +2706,7 @@ def message no_type_for_symbol: "can't find type for method corresponding to `%s.to_proc'", no_non_dep_types: "no non-dependent types for receiver %s in call to method %s", empty_env: "for some reason, environment is nil when type checking assignment to variable %s.", - + unsupported_expression: "Expression kind %s unsupported, for expression %s", infer_constraint_error: "%s constraint generated here." } From e2f306c244ce01c065cf5d78cece839805682eac Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Fri, 8 May 2020 17:58:07 -0400 Subject: [PATCH 051/124] Add test_match_with_lvasgn --- test/test_typecheck.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 71c736fb..2b7a4e63 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -197,6 +197,9 @@ def setup RDL.type :String, :length, '() -> Integer', wrap: false RDL.type :NilClass, :&, '(%any obj) -> false', wrap: false + RDL.nowrap :Regexp + RDL.type :Regexp, :=~, '(String str) -> Integer or nil', wrap: false # Can't wrap this or it will mess with $1, $2, etc + @t3 = RDL::Type::SingletonType.new 3 @t4 = RDL::Type::SingletonType.new 4 @t5 = RDL::Type::SingletonType.new 5 @@ -1994,6 +1997,10 @@ def self.add_one(x) end } end + + def test_match_with_lvasgn + assert do_tc("/foo/ =~ 'foo'") <= RDL::Globals.types[:any] + end def test_raise_typechecks self.class.class_eval "module RaiseTypechecks; end" @@ -2109,6 +2116,5 @@ def uses_baz3(x, y) end end } - end end From 93d3c990fad2aef8c126335bba567f5a3cbfdc40 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Fri, 8 May 2020 19:23:33 -0400 Subject: [PATCH 052/124] implement match_with_lvasgn --- lib/rdl/typecheck.rb | 9 +++++++++ test/test_typecheck.rb | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 2badaed7..ec20656b 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -958,6 +958,15 @@ def self._tc(scope, env, e) else tc_vasgn(scope, envi, e.children[0].type, x, trhs, e) + [effi] end + when :match_with_lvasgn # /regexp/ =~ rhs + env1, t1, eff1 = tc(scope, env, e.children[0]) # the regexp + env2, t2, eff2 = tc(scope, env, e.children[1]) # the rhs + ts, es = tc_send(scope, env2, RDL::Globals.types[:regexp], :=~, [t2], nil, e) + [env2, ts, es] + # (regexp + # (str "foo") + # (regopt)) + # (str "foo")) when :nth_ref, :back_ref [env, RDL::Globals.types[:string], [:+, :+]] when :const diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 2b7a4e63..65cac761 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -1997,9 +1997,10 @@ def self.add_one(x) end } end - + def test_match_with_lvasgn - assert do_tc("/foo/ =~ 'foo'") <= RDL::Globals.types[:any] + assert do_tc("/foo/ =~ 'foo'") <= RDL::Globals.types[:integer] + assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("/foo/ =~ 32") } end def test_raise_typechecks From 0846937b28e205e6df2e078047a9ae53a2048cf0 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Fri, 8 May 2020 21:16:46 -0400 Subject: [PATCH 053/124] Allow constants in default args --- lib/rdl/typecheck.rb | 19 ++++++++++++------- test/test_typecheck.rb | 9 ++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index ec20656b..7f21e142 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -299,9 +299,10 @@ def self._infer(klass, meth) meth_type = meth_type.instantiate inst - _, targs = args_hash({}, Env.new(inst), meth_type, args, ast, 'method') - targs[:self] = self_type scope = { task: :infer, klass: klass, meth: meth, tret: meth_type.ret, tblock: meth_type.block, captured: Hash.new, context_types: context_types } + # default args seem to be evaluated in the method body, so same scope + _, targs = args_hash(scope, Env.new(inst), meth_type, args, ast, 'method') + targs[:self] = self_type begin old_captured = scope[:captured].dup @@ -366,9 +367,10 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) raise RuntimeError, "Type checking of methods with computed types is not currently supported." unless (type.args + [type.ret]).all? { |t| !t.instance_of?(RDL::Type::ComputedType) } inst = {self: self_type} type = type.instantiate inst - _, targs = args_hash({}, Env.new(:self => self_type), type, args, ast, 'method') - targs[:self] = self_type scope = { task: :check, klass: klass, meth: meth, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types, eff: effect } + # default args seem to be evaluated in the method body, so same scope + _, targs = args_hash(scope, Env.new(:self => self_type), type, args, ast, 'method') + targs[:self] = self_type begin old_captured = scope[:captured].dup if body.nil? @@ -1828,7 +1830,8 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, if klass.class == Module # Module mixin handle # Here a module method is calling a non-existent method; check for it in all mixees - nts = RDL::Globals.module_mixees[klass].map { |k, kind| RDL::Type::NominalType.new(k) } + # TODO: Handle :extend + nts = RDL::Globals.module_mixees[klass].map { |k, kind| if kind == :include then RDL::Type::NominalType.new(k) end } return [@types[:bot], :+] if nts.empty? # if module not mixed in, this call can't happen; so %bot plus pure ut = RDL::Type::UnionType.new(*nts) ts, es = tc_send(scope, env, ut, meth, tactuals, block, e, op_asgn) @@ -2622,7 +2625,7 @@ def self.find_constant(env, scope, e) # https://cirw.in/blog/constant-lookup.html # First look in Module.nesting for a lexically scoped variable const_string = e.loc.expression.source - raise "Expected class and method in `scope`." unless scope[:klass] && scope[:meth] + raise "Expected class and method in `scope` #{scope[:klass]}." unless scope[:klass] && scope[:meth] if (RDL::Util.has_singleton_marker(scope[:klass])) klass = RDL::Util.to_class(RDL::Util.remove_singleton_marker(scope[:klass])) mod_inst = false @@ -2717,7 +2720,9 @@ def message empty_env: "for some reason, environment is nil when type checking assignment to variable %s.", unsupported_expression: "Expression kind %s unsupported, for expression %s", - infer_constraint_error: "%s constraint generated here." + infer_constraint_error: "%s constraint generated here.", + + empty: "", } end diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 65cac761..93873eb2 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -2034,7 +2034,6 @@ def self.baz end end - def test_sing_method_inheritence RDL.type SingletonInheritA, 'self.foo', '(Integer) -> Integer' self.class.class_eval do @@ -2045,6 +2044,14 @@ def calls_inherited_sing_meth(x) end end + def test_default_args + self.class.class_eval do + type '(?String) -> String', typecheck: :now + def with_default_arg(x=RUBY_VERSION) + x + end + end + end def test_comp_types self.class.class_eval "class CompTypes; end" From 5391674aaac4baa36c4d6ced469a610edec2c5b2 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 11 May 2020 16:44:28 -0400 Subject: [PATCH 054/124] add twin_network heuristic --- lib/rdl/heuristics.rb | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 4090db76..acb3a052 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -20,7 +20,7 @@ def self.matching_classes(meth_names) end def self.struct_to_nominal(var_type) - return unless (var_type.category == :arg) || (var_type.category == :var)#(var_type.category == :ivar) || (var_type.category == :cvar) || (var_type.category == :gvar) ## this rule only applies to args and (instance/class/global) variables + return unless (var_type.category == :arg) || (var_type.category == :var) ## this rule only applies to args and (instance/class/global) variables #return unless var_type.ubounds.all? { |t, loc| t.is_a?(RDL::Type::StructuralType) || t.is_a?(RDL::Type::VarType) } ## all upper bounds must be struct types or var types return unless var_type.ubounds.any? { |t, loc| t.is_a?(RDL::Type::StructuralType) } ## upper bounds must include struct type(s) struct_types = var_type.ubounds.select { |t, loc| t.is_a?(RDL::Type::StructuralType) } @@ -46,6 +46,38 @@ def self.struct_to_nominal(var_type) end + + +def self.twin_network_guess(var) + return unless (var_type.category == :arg) || (var_type.category == :var) ## this rule only applies to args and (instance/class/global) variables + name1 = var_type.name + name2 = "count" ## TODO: replace this with actual names from the current program + + uri = URI "http://127.0.0.1:5000/" + params = { in1: name1, in2: name2 } + uri.query = URI.encode_www_form(params) + + res = Net::HTTP.get_response(uri) + if res.msg != "OK" + puts "Failed to make request to twin network server. Received response #{res.body}." + return nil + end + + sim_score = res.body.to_f + if sim_score > 0.8 + puts "Twin network found #{name1} and #{name2} have similarity score of #{sim_score}." + puts "Attempting to apply Integer as solution." + ## TODO: once we replace "count" above, also have to replace Integer as solution. + return RDL::Globals.types[:integer] + else + puts "Twin network found insufficient similarity score of #{sim_score} between #{name1} and #{name2}." + return nil + end +end + + + + class << RDL::Heuristic attr_reader :rules end @@ -128,3 +160,6 @@ def model_set_type ### For rules involving :include?, :==, :!=, etc. we would need to track the exact receiver/args used in the method call, and somehow store these in the bounds created for a var type. + + +RDL::Heuristic.add(:twin_network) { |var| RDL::Heuristic.twin_network_guess(var) } From e3f6aa183abfa5a9a6c250d2e90354d8e6916088 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 11 May 2020 18:13:36 -0400 Subject: [PATCH 055/124] remove effects across RDL --- lib/rdl/typecheck.rb | 504 +++++++------------- lib/rdl/types/rdl_types.rb | 66 +-- lib/rdl/types/type.rb | 8 +- lib/rdl/wrap.rb | 10 +- lib/types/core/array.rb | 72 +-- lib/types/core/basic_object.rb | 6 +- lib/types/core/class.rb | 4 +- lib/types/core/enumerable.rb | 10 +- lib/types/core/hash.rb | 74 +-- lib/types/core/integer.rb | 52 +- lib/types/core/kernel.rb | 8 +- lib/types/core/module.rb | 2 +- lib/types/core/object.rb | 12 +- lib/types/core/string.rb | 74 +-- lib/types/core/symbol.rb | 6 +- lib/types/rails/active_record/comp_types.rb | 50 +- lib/types/sequel/comp_types.rb | 50 +- 17 files changed, 428 insertions(+), 580 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 7f21e142..4ff7aef0 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -330,18 +330,12 @@ def self._infer(klass, meth) RDL::Logging.log :inference, :debug, "Done with constraint generation." end - def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) + def self.typecheck(klass, meth, ast=nil, types = nil) # puts "Typechecking #{klass}##{meth}" ast = get_ast(klass, meth) unless ast raise RuntimeError, "Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}" if ast.nil? types = RDL::Globals.info.get(klass, meth, :type) unless types - effects = RDL::Globals.info.get(klass, meth, :effect) unless effects - if effects.empty? || effects[0] == nil - effect = nil - else - effect = [:+, :+] - effects.each { |e| effect = effect_union(effect, e) unless e.nil? } ## being very lazy about this right now, conservatively taking the union of all effects if there are multiple ones - end + raise RuntimeError, "Can't typecheck method with no types?!" if types.nil? or types == [] if ast.type == :def @@ -367,7 +361,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) raise RuntimeError, "Type checking of methods with computed types is not currently supported." unless (type.args + [type.ret]).all? { |t| !t.instance_of?(RDL::Type::ComputedType) } inst = {self: self_type} type = type.instantiate inst - scope = { task: :check, klass: klass, meth: meth, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types, eff: effect } + scope = { task: :check, klass: klass, meth: meth, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types } # default args seem to be evaluated in the method body, so same scope _, targs = args_hash(scope, Env.new(:self => self_type), type, args, ast, 'method') targs[:self] = self_type @@ -378,13 +372,13 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) else targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this @num_casts = 0 - _, body_type, body_effect = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) + _, body_type = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) end old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured]) end until old_captured == scope[:captured] error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || meth == :initialize ||RDL::Type::Type.leq(body_type, type.ret, ast: ast) - error :bad_effect, [body_effect, effect], body unless body.nil? || effect.nil? || effect_leq(body_effect, effect) } + if RDL::Config.instance.check_comp_types new_meth = WrapCall.rewrite(ast) # rewrite ast to insert dynamic checks RDL::Util.silent_warnings { RDL::Util.to_class(klass).class_eval(new_meth) } # redefine method in the same class @@ -396,45 +390,6 @@ def self.get_num_casts return @num_casts end - def self.effect_leq(e1, e2) - raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) } - p1, t1 = e1 - p2, t2 = e2 - case p1 #:+ always okay - when :~ - return false if p2 == :+ - when :- - return false if p2 == :+# || p2 == :~ going to treat this as okay, like a type cast - end - case t1 #:+ always okay - when :- - return false if t2 == :+ - end - return true - end - - def self.effect_union(e1, e2) - raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) }#{ |e| e.is_a?(Symbol) } - p1, t1 = e1 - p2, t2 = e2 - pret = tret = nil - case p1 - when :+ - pret = p2 - when :~ - if p2 == :- then pret = :- else pret = :~ end - else - pret = :- - end - case t1 - when :+ - tret = t2 - else - tret = :- - end - [pret, tret] - end - ### TODO: clean up below. Should probably incorporate it into `targs.merge` call in `self.typecheck`. def self.widen_scopes(h1, h2) h1new = h1.nil? ? nil : {} @@ -604,36 +559,34 @@ def self.tc(scope, env, e) # [+ scope +] tracks flow-insensitive information about the current scope, excluding local variables # [+ env +] is the (local variable) Env # [+ e +] is the expression to type check - # Returns [env', t, eff], where env' is the type environment at the end of the expression + # Returns [env', t], where env' is the type environment at the end of the expression # and t is the type of the expression. t is always canonical. def self._tc(scope, env, e) case e.type when :nil - [env, RDL::Globals.types[:nil], [:+, :+]] + [env, RDL::Globals.types[:nil]] when :true - [env, RDL::Globals.types[:true], [:+, :+]] + [env, RDL::Globals.types[:true]] when :false - [env, RDL::Globals.types[:false], [:+, :+]] + [env, RDL::Globals.types[:false]] when :str, :string t = RDL::Config.instance.use_precise_string ? RDL::Type::PreciseStringType.new(e.children[0]) : RDL::Globals.types[:string] - [env, t, [:+, :+]] + [env, t] when :complex, :rational # constants - [env, RDL::Type::NominalType.new(e.children[0].class), [:+, :+]] + [env, RDL::Type::NominalType.new(e.children[0].class)] when :int, :float - if RDL::Config.instance.number_mode #&& (e.type == :float) - [env, RDL::Type::NominalType.new(Integer), [:+, :+]] + if RDL::Config.instance.number_mode + [env, RDL::Type::NominalType.new(Integer)] else - [env, RDL::Type::SingletonType.new(e.children[0]), [:+, :+]] + [env, RDL::Type::SingletonType.new(e.children[0])] end when :sym # singleton - [env, RDL::Type::SingletonType.new(e.children[0]), [:+, :+]] + [env, RDL::Type::SingletonType.new(e.children[0])] when :dstr, :xstr # string (or execute-string) with interpolation - effi = [:+, :+] prec_str = [] envi = env e.children.each { |ei| - envi, ti, eff_new = tc(scope, envi, ei) - effi = effect_union(effi, eff_new) + envi, ti = tc(scope, envi, ei) if RDL::Config.instance.use_precise_string if ei.type == :str || ei.type == :string ## for strings, just append the string itself @@ -647,24 +600,23 @@ def self._tc(scope, env, e) t = RDL::Globals.types[:string] end } - [envi, t, effi] + [envi, t] when :dsym # symbol with interpolation envi = env e.children.each { |ei| envi, _ = tc(scope, envi, ei) } - [envi, RDL::Globals.types[:symbol], [:+, :+]] + [envi, RDL::Globals.types[:symbol]] when :regexp envi = env e.children.each { |ei| envi, _ = tc(scope, envi, ei) unless ei.type == :regopt } - [envi, RDL::Globals.types[:regexp], [:+, :+]] + [envi, RDL::Globals.types[:regexp]] when :array envi = env tis = [] is_array = false - effi = [:+, :+] + e.children.each { |ei| if ei.type == :splat - envi, ti, new_eff = tc(scope, envi, ei.children[0]); - effi = effect_union(effi, new_eff) + envi, ti = tc(scope, envi, ei.children[0]); if ti.is_a? RDL::Type::TupleType ti.cant_promote! # must remain a tuple tis.concat(ti.params) @@ -696,37 +648,34 @@ def self._tc(scope, env, e) tis << ti # splat does nothing end else - envi, ti, new_eff = tc(scope, envi, ei); - effi = effect_union(effi, new_eff) + envi, ti = tc(scope, envi, ei); tis << ti end } if is_array - [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(*tis).canonical), effi] + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(*tis).canonical)] elsif scope[:task] == :infer && RDL::Config.instance.infer_empties && tis.empty? - [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :array_param, name: "array_param_#{e.loc}")), effi] + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :array_param, name: "array_param_#{e.loc}"))] else - [envi, RDL::Type::TupleType.new(*tis), effi] + [envi, RDL::Type::TupleType.new(*tis)] end when :hash envi = env tlefts = [] trights = [] is_fh = true - effi = [:+, :+] + e.children.each { |p| # each child is a pair if p.type == :pair - envi, tleft, effl = tc(scope, envi, p.children[0]) + envi, tleft = tc(scope, envi, p.children[0]) tlefts << tleft - effi = effect_union(effi, effl) - envi, tright, effr = tc(scope, envi, p.children[1]) + envi, tright = tc(scope, envi, p.children[1]) trights << tright - effi = effect_union(effi, effr) is_fh = false unless tleft.is_a?(RDL::Type::SingletonType) elsif p.type == :kwsplat - envi, tkwsplat, new_eff = tc(scope, envi, p.children[0]) - effi = effect_union(effi, new_eff) + envi, tkwsplat = tc(scope, envi, p.children[0]) + if tkwsplat.is_a? RDL::Type::FiniteHashType tkwsplat.cant_promote! # must remain finite hash tlefts.concat(tkwsplat.elts.keys.map { |k| RDL::Type::SingletonType.new(k) }) @@ -747,49 +696,44 @@ def self._tc(scope, env, e) fh = tlefts.map { |t| t.val }.zip(trights).to_h if scope[:task] == :infer && RDL::Config.instance.infer_empties && fh.empty? [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_key, name: "hash_param_key_#{e.loc}"), - RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_val, name: "hash_param_val_#{e.loc}")), effi] + RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_val, name: "hash_param_val_#{e.loc}"))] else - [envi, RDL::Type::FiniteHashType.new(fh, nil), effi] + [envi, RDL::Type::FiniteHashType.new(fh, nil)] end else tleft = RDL::Type::UnionType.new(*tlefts) tright = RDL::Type::UnionType.new(*trights) - [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], tleft, tright), effi] + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], tleft, tright)] end #TODO test! # when :kwsplat # TODO! when :irange, :erange - env1, t1, eff1 = tc(scope, env, e.children[0]) - env2, t2, eff2 = tc(scope, env1, e.children[1]) + env1, t1 = tc(scope, env, e.children[0]) + env2, t2 = tc(scope, env1, e.children[1]) # promote singleton types to nominal types; safe since Ranges are immutable t1 = RDL::Type::NominalType.new(t1.val.class) if t1.is_a? RDL::Type::SingletonType t2 = RDL::Type::NominalType.new(t2.val.class) if t2.is_a? RDL::Type::SingletonType error :nonmatching_range_type, [t1, t2], e unless t1 <= t2 || t2 <= t1 - [env2, RDL::Type::GenericType.new(RDL::Globals.types[:range], t1), effect_union(eff1, eff2)] + [env2, RDL::Type::GenericType.new(RDL::Globals.types[:range], t1)] when :self - [env, env[:self], [:+, :+]] + [env, env[:self]] when :lvar, :ivar, :cvar, :gvar - if e.type == :lvar then eff = [:+, :+] else eff = [:-, :+] end - tc_var(scope, env, e.type, e.children[0], e) + [eff] + tc_var(scope, env, e.type, e.children[0], e) when :lvasgn, :ivasgn, :cvasgn, :gvasgn - if e.type == :lvasgn || scope[:meth] == :initialize then eff = [:+, :+] else eff = [:-, :+] end x = e.children[0] # if local var, lhs is bound to nil before assignment is executed! only matters in type checking for locals env = env.bind(x, RDL::Globals.types[:nil]) if ((e.type == :lvasgn) && (not (env.has_key? x))) - envright, tright, effright = tc(scope, env, e.children[1]) - tc_vasgn(scope, envright, e.type, x, tright, e)+[effect_union(eff, effright)] + envright, tright = tc(scope, env, e.children[1]) + tc_vasgn(scope, envright, e.type, x, tright, e) when :masgn # (masgn (mlhs (Xvasgn var-name) ... (Xvasgn var-name)) rhs) - effi = [:+, :+] e.children[0].children.each { |asgn| - effi = effect_union(effi, [:-, :+]) if asgn.type != :lvasgn && scope[:meth] != :initialize next unless asgn.type == :lvasgn x = e.children[0] env = env.bind(x, RDL::Globals.types[:nil]) if (not (env.has_key? x)) # see lvasgn # Note don't need to check outer_env here because will be checked by tc_vasgn below } - envi, tright, effright = tc(scope, env, e.children[1]) - effi = effect_union(effi, effright) + envi, tright = tc(scope, env, e.children[1]) lhs = e.children[0].children if tright.is_a? RDL::Type::TupleType tright.cant_promote! # must always remain a tuple because of the way type checking currently works @@ -810,13 +754,13 @@ def self._tc(scope, env, e) } splat = lhs[splat_ind] envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::TupleType.new(*rhs), splat) if splat.children.any? - [envi, tright, effi] + [envi, tright] else error :masgn_num, [rhs.length, lhs.length], e unless lhs.length == rhs.length lhs.zip(rhs).each { |left, right| envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left) } - [envi, tright, effi] + [envi, tright] end elsif (tright.is_a? RDL::Type::GenericType) && (tright.base == RDL::Globals.types[:array]) tasgn = tright.params[0] @@ -827,7 +771,7 @@ def self._tc(scope, env, e) envi, _ = tc_vasgn(scope, envi, asgn.type, asgn.children[0], tasgn, asgn) end } - [envi, tright, effi] + [envi, tright] elsif (tright.is_a? RDL::Type::DynamicType) tasgn = tright lhs.each { |asgn| @@ -837,7 +781,7 @@ def self._tc(scope, env, e) envi, _ = tc_vasgn(scope, envi, asgn.type, asgn.children[0], tasgn, asgn) end } - [env, tright, effi] + [env, tright] elsif tright.is_a?(RDL::Type::VarType) splat_ind = lhs.index { |lhs_elt| lhs_elt.type == :splat } #raise "not yet implemented" if splat_ind @@ -857,7 +801,7 @@ def self._tc(scope, env, e) } splat = lhs[splat_ind] envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :tuple_element, name: "array_param_#{count}")), splat) if splat.children.any? ## could be empty splat - [envi, tright, effi] + [envi, tright] else new_tuple = [] count = 0 @@ -867,75 +811,59 @@ def self._tc(scope, env, e) } #tuple_type = RDL::Type::TupleType.new(*new_tuple) #RDL::Type::Type.leq(tright, tuple_type, ast: e) # don't think this is necessary - [envi, tright, effi] + [envi, tright] end else error :masgn_bad_rhs, [tright], e.children[1] end when :op_asgn - effi = [:+, :+] if e.children[0].type == :send # (op-asgn (send recv meth) :op operand) meth = e.children[0].children[1] - envleft, trecv, effleft = tc(scope, env, e.children[0].children[0]) # recv - effi = effect_union(effi, effleft) + envleft, trecv = tc(scope, env, e.children[0].children[0]) # recv elargs = e.children[0].children[2] if elargs - envleft, elargs, effleft = tc(scope, envleft, elargs) - effi = effect_union(effi, effleft) + envleft, elargs = tc(scope, envleft, elargs) largs = [elargs] else largs = [] end - tloperand, lopeff = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() - effi = effect_union(effi, lopeff) - envoperand, troperand, effoperand = tc(scope, envleft, e.children[2]) # operand - effi = effect_union(effi, effoperand) - tright, effright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand) - effi = effect_union(effi, effright) + tloperand = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() + envoperand, troperand = tc(scope, envleft, e.children[2]) # operand + tright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand) tright = largs.push(tright) if largs mutation_meth = (meth.to_s + '=').to_sym - tres, effres = tc_send(scope, envoperand, trecv, mutation_meth, tright, nil, e, true) # call recv.meth=(recvt.meth().op(operand)) - effi = effect_union(effi, effres) - [envoperand, tres, effi] + tres = tc_send(scope, envoperand, trecv, mutation_meth, tright, nil, e, true) # call recv.meth=(recvt.meth().op(operand)) + [envoperand, tres] else # (op-asgn (Xvasgn var-name) :op operand) x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_vasgn below env = env.bind(x, RDL::Globals.types[:nil]) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn - effi = effect_union(effi, [:-, :+]) if e.children[0].type != :lvasgn envi, trecv = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to - envright, tright, effright = tc(scope, envi, e.children[2]) # operand - effi = effect_union(effi, effright) - trhs, effrhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e) - effi = effect_union(effrhs, effi) - tc_vasgn(scope, envright, e.children[0].type, x, trhs, e) + [effi] + envright, tright = tc(scope, envi, e.children[2]) # operand + trhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e) + tc_vasgn(scope, envright, e.children[0].type, x, trhs, e) end when :and_asgn, :or_asgn # very similar logic to op_asgn - effi = [:+, :+] if e.children[0].type == :send meth = e.children[0].children[1] - envleft, trecv, effleft = tc(scope, env, e.children[0].children[0]) # recv - effi = effect_union(effi, effleft) + envleft, trecv = tc(scope, env, e.children[0].children[0]) # recv elargs = e.children[0].children[2] if elargs - envleft, elargs, eleff = tc(scope, envleft, elargs) - effi = effect_union(effi, eleff) + envleft, elargs = tc(scope, envleft, elargs) largs = [elargs] else largs = [] end - tleft, effleft = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() - effi = effect_union(effi, effleft) - envright, tright, effright = tc(scope, envleft, e.children[1]) # operand - effi = effect_union(effi, effright) + tleft = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() + envright, tright = tc(scope, envleft, e.children[1]) # operand else x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_var below env = env.bind(x, RDL::Globals.types[:nil]) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn envleft, tleft = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to - envright, tright, effright = tc(scope, envleft, e.children[1]) - effi = effect_union(effi, effright) + envright, tright = tc(scope, envleft, e.children[1]) end envi, trhs = (if tleft.is_a? RDL::Type::SingletonType if e.type == :and_asgn @@ -954,23 +882,22 @@ def self._tc(scope, env, e) if e.children[0].type == :send mutation_meth = (meth.to_s + '=').to_sym rhs_array = [*largs, trhs] - tres, effres = tc_send(scope, envi, trecv, mutation_meth, rhs_array, nil, e) - effi = effect_union(effi, effres) - [envi, tres, effi] + tres = tc_send(scope, envi, trecv, mutation_meth, rhs_array, nil, e) + [envi, tres] else - tc_vasgn(scope, envi, e.children[0].type, x, trhs, e) + [effi] + tc_vasgn(scope, envi, e.children[0].type, x, trhs, e) end when :match_with_lvasgn # /regexp/ =~ rhs - env1, t1, eff1 = tc(scope, env, e.children[0]) # the regexp - env2, t2, eff2 = tc(scope, env, e.children[1]) # the rhs - ts, es = tc_send(scope, env2, RDL::Globals.types[:regexp], :=~, [t2], nil, e) - [env2, ts, es] + env1, t1 = tc(scope, env, e.children[0]) # the regexp + env2, t2 = tc(scope, env, e.children[1]) # the rhs + ts = tc_send(scope, env2, RDL::Globals.types[:regexp], :=~, [t2], nil, e) + [env2, ts] # (regexp # (str "foo") # (regopt)) # (str "foo")) when :nth_ref, :back_ref - [env, RDL::Globals.types[:string], [:+, :+]] + [env, RDL::Globals.types[:string]] when :const if e.children[0].nil? && e.children[1] == :ENV ## ENV @@ -980,21 +907,20 @@ def self._tc(scope, env, e) else c = to_type(find_constant(env, scope, e)) end - [env, c, [:+, :+]] + [env, c] when :defined? # do not type check subexpression, since it may not be type correct, e.g., undefined variable - [env, RDL::Globals.types[:string], [:+, :+]] + [env, RDL::Globals.types[:string]] when :send, :csend # children[0] = receiver; if nil, receiver is self # children[1] = method name, a symbol # children [2..] = actual args - return tc_var_type(scope, env, e) + [[:+, :+]] if (e.children[0].nil? || is_RDL(e.children[0])) && e.children[1] == :var_type - return tc_type_cast(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil? ## TODO: could be more precise with effects here, punting for now - return tc_note_type(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :note_type - return tc_instantiate!(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :instantiate! + return tc_var_type(scope, env, e) if (e.children[0].nil? || is_RDL(e.children[0])) && e.children[1] == :var_type + return tc_type_cast(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil? + return tc_note_type(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :note_type + return tc_instantiate!(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :instantiate! envi = env tactuals = [] - eff = [:+, :+] block = scope[:block] map_case = false e_map_case = ti_map_case = nil @@ -1037,45 +963,39 @@ def self._tc(scope, env, e) e_map_case = ei ti_map_case = ti else - ti, effi = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType - eff = effect_union(eff, effi) + ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType block = [ti, ei] end else - envi, ti, effi = tc(sscope, envi, ei) - eff = effect_union(eff, effi) + envi, ti = tc(sscope, envi, ei) tactuals << ti end } - envi, trecv, effrec = if e.children[0].nil? then [envi, envi[:self], [:+, :+]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver - eff = effect_union(effrec, eff) + envi, trecv = if e.children[0].nil? then [envi, envi[:self]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver if map_case && trecv.is_a?(RDL::Type::GenericType) #raise "Expected GenericType, got #{trecv}." unless trecv.is_a?(RDL::Type::GenericType) trecv.is_a?(RDL::Type::GenericType) - ti_map_case, effi = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) + ti_map_case = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) map_block_type = RDL::Type::MethodType.new([trecv.params[0]], nil, ti_map_case.canonical.ret) - eff = effect_union(eff, effi) block = [map_block_type, e_map_case] end - tres, effres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e) - [envi, tres.canonical, effect_union(effres, eff) ] + tres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e) + [envi, tres.canonical] } when :yield - ## TODO: effects # very similar to send except the callee is the method's block error :no_block, [], e unless scope[:tblock] error :block_block, [], e if scope[:tblock].is_a?(RDL::Type::MethodType) && scope[:tblock].block scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception envi = env tactuals = [] - eff = [:+, :+] - e.children[0..-1].each { |ei| envi, ti, effi = tc(scope, envi, ei); tactuals << ti ; eff = effect_union(effi, eff)} + e.children[0..-1].each { |ei| envi, ti = tc(scope, envi, ei); tactuals << ti } if scope[:tblock].is_a?(RDL::Type::VarType) block_ret_type = RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :block_ret, name: "block_return") block_type = RDL::Type::MethodType.new(tactuals, nil, block_ret_type) RDL::Type::Type.leq(scope[:tblock], block_type, ast: e) - return [envi, block_ret_type, eff] + return [envi, block_ret_type] else unless tc_arg_types(scope[:tblock], tactuals) msg = < 1 @@ -1461,8 +1351,8 @@ def self._tc(scope, env, e) scope_merge(scope, block: nil, break: env, next: env) { |sscope| trecv = get_super_owner(envi[:self], scope[:meth]) - tres, effres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) - [envi, tres.canonical, effres] + tres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) + [envi, tres.canonical] } else error :unsupported_expression, [e.type, e], e @@ -1705,7 +1595,6 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) trecvs = if trecvs.is_a? RDL::Type::UnionType then union = true; trecvs.types else union = false; [trecvs] end trets = [] - eff = [:+, :+] trecvs.each { |trecv| if trecv.is_a?(RDL::Type::ChoiceType) choice_hash = { } @@ -1716,7 +1605,7 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) t = t.canonical tarms = t.is_a?(RDL::Type::UnionType) ? t.types : [t] tarms.each { |t| - ts, _ = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) + ts = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) choice_hash[num] = RDL::Type::UnionType.new(*ts).canonical } rescue StaticTypeError => err @@ -1733,21 +1622,13 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) ts = [RDL::Type::ChoiceType.new(choice_hash, [trecv] + trecv.connecteds)] end else - ts, es = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) + ts = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) end - if es.nil? || (es.all? { |effect| effect.nil? }) ## could be multiple, because every time e is called, nil is added to effects - ## should probably change default effect to be [:-, :-], but for now I want it like this, - ## so I can easily see when a method has been used and its effect set to the default. - #puts "Going to assume method #{meth} for receiver #{trecv} has effect [:-, :-]." - eff = [:-, :-] - else - es.each { |effect| eff = effect_union(eff, effect) unless effect.nil? } - end trets.concat(ts) } trets.map! {|t| (t.is_a?(RDL::Type::AnnotatedArgType) || t.is_a?(RDL::Type::BoundArgType)) ? t.type : t} - return [RDL::Type::UnionType.new(*trets), eff] + return RDL::Type::UnionType.new(*trets) end # Like tc_send but trecv should never be a union type @@ -1767,7 +1648,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, elsif trecv.is_a?(RDL::Type::AnnotatedArgType) || trecv.is_a?(RDL::Type::DependentArgType) trecv = trecv.type end - return [tc_send_class(trecv, e), [[:+, :+]]] if (meth == :class) && (tactuals.empty?) + return tc_send_class(trecv, e) if (meth == :class) && (tactuals.empty?) ts = [] # Array, i.e., an intersection types case trecv when RDL::Type::SingletonType @@ -1787,7 +1668,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, trecv_lookup = RDL::Util.add_singleton_marker(trecv.val.to_s) self_inst = trecv end - ts, es = lookup(scope, trecv_lookup, meth_lookup, e) + ts = lookup(scope, trecv_lookup, meth_lookup, e) ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if init && (ts.nil?) # there's always a nullary new if initialize is undefined error :no_singleton_method_type, [trecv.val, meth], e unless ts inst = {self: self_inst} @@ -1802,13 +1683,13 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, else raise "unsupported self type for to_proc #{env[:self]}" end - ts, es = lookup(scope, klass.to_s, trecv.val, e) + ts = lookup(scope, klass.to_s, trecv.val, e) ts = filter_comp_types(ts, false) ## no comp types in this case error :no_type_for_symbol, [trecv.val.inspect], e if ts.nil? - return [ts, nil] ## TODO: not sure what to do hear about effect + return ts else klass = trecv.val.class.to_s - ts, es = lookup(scope, klass, meth, e) + ts = lookup(scope, klass, meth, e) error :no_instance_method_type, [klass, meth], e unless ts inst = {self: trecv} self_klass = trecv.val.class @@ -1817,14 +1698,14 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, meth_lookup = meth trecv_lookup = RDL::Util.add_singleton_marker(trecv.val.to_s) self_inst = trecv - ts, es = lookup(scope, trecv_lookup, meth_lookup, e) + ts = lookup(scope, trecv_lookup, meth_lookup, e) ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if (meth == :new) && (ts.nil?) # there's always a nullary new if initialize is undefined error :no_singleton_method_type, [trecv.val, meth], e unless ts inst = {self: self_inst} self_klass = trecv.val ts = ts.map { |t| t.instantiate(inst) } when RDL::Type::NominalType - ts, es = lookup(scope, trecv.name, meth, e) + ts = lookup(scope, trecv.name, meth, e) unless ts klass = RDL::Util.to_class(scope[:klass]) if klass.class == Module @@ -1832,10 +1713,10 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, # Here a module method is calling a non-existent method; check for it in all mixees # TODO: Handle :extend nts = RDL::Globals.module_mixees[klass].map { |k, kind| if kind == :include then RDL::Type::NominalType.new(k) end } - return [@types[:bot], :+] if nts.empty? # if module not mixed in, this call can't happen; so %bot plus pure + return [RDL::Globals.types[:bot]] if nts.empty? # if module not mixed in, this call can't happen; so %bot ut = RDL::Type::UnionType.new(*nts) - ts, es = tc_send(scope, env, ut, meth, tactuals, block, e, op_asgn) - return [[ts], [es]] + t = tc_send(scope, env, ut, meth, tactuals, block, e, op_asgn) + return [t] end error :no_instance_method_type, [trecv.name, meth], e end @@ -1843,11 +1724,11 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, inst = {self: trecv} self_klass = RDL::Util.to_class(trecv.name) when RDL::Type::GenericType - ts, es = lookup(scope, trecv.base.name, meth, e) + ts = lookup(scope, trecv.base.name, meth, e) if ts.nil? && (trecv.base.name == "ActiveRecord_Relation") && defined? DBType ## ActiveRecord_Relation methods that don't exist are delegated to underlying scoped class. ## Maybe there's a way not to bake this in to typechecker, but I can't think of one. - ts, es = lookup(scope, "[s]"+DBType.rec_to_nominal(trecv).name, meth, e) + ts = lookup(scope, "[s]"+DBType.rec_to_nominal(trecv).name, meth, e) error :no_instance_method_type, [trecv.base.name, meth], e unless ts ts = ts.map { |t| RDL::Type::MethodType.new(t.args, t.block, trecv) } end @@ -1856,7 +1737,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, self_klass = RDL::Util.to_class(trecv.base.name) when RDL::Type::TupleType if RDL::Config.instance.use_comp_types - ts, es = lookup(scope, "Array", meth, e) + ts = lookup(scope, "Array", meth, e) error :no_instance_method_type, ["Array", meth], e unless ts #inst = trecv.to_inst.merge(self: trecv) t_bind = trecv.promote.params[0].to_s == "t" ? RDL::Globals.types[:bot] : trecv.promote.params[0] @@ -1866,14 +1747,14 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ## need to promote in this case error :tuple_finite_hash_promote, ['tuple', 'Array'], e unless trecv.promote! trecv = trecv.canonical - ts, es = lookup(scope, trecv.base.name, meth, e) + ts = lookup(scope, trecv.base.name, meth, e) error :no_instance_method_type, [trecv.base.name, meth], e unless ts inst = trecv.to_inst.merge(self: trecv) self_klass = RDL::Util.to_class(trecv.base.name) end when RDL::Type::FiniteHashType if RDL::Config.instance.use_comp_types - ts, es = lookup(scope, "Hash", meth, e) + ts = lookup(scope, "Hash", meth, e) error :no_instance_method_type, ["Hash", meth], e unless ts #inst = trecv.to_inst.merge(self: trecv) k_bind = trecv.promote.params[0].to_s == "k" ? RDL::Globals.types[:bot] : trecv.promote.params[0] @@ -1884,14 +1765,14 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ## need to promote in this case error :tuple_finite_hash_promote, ['finite hash', 'Hash'], e unless trecv.promote! trecv = trecv.canonical - ts, es = lookup(scope, trecv.base.name, meth, e) + ts = lookup(scope, trecv.base.name, meth, e) error :no_instance_method_type, [trecv.base.name, meth], e unless ts inst = trecv.to_inst.merge(self: trecv) self_klass = RDL::Util.to_class(trecv.base.name) end when RDL::Type::PreciseStringType if RDL::Config.instance.use_comp_types - ts, es = lookup(scope, "String", meth, e) + ts = lookup(scope, "String", meth, e) error :no_instance_method_type, ["String", meth], e unless ts inst = { self: trecv } self_klass = String @@ -1899,7 +1780,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ## need to promote in this case error :tuple_finite_hash_promote, ['precise string type', 'String'], e unless trecv.promote! trecv = trecv.canonical - ts, es = lookup(scope, trecv.name, meth, e) + ts = lookup(scope, trecv.name, meth, e) error :no_instance_method_type, [trecv.name, meth], e unless ts inst = { self: trecv } self_klass = RDL::Util.to_class(trecv.name) @@ -1954,7 +1835,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, #self_klass = nil #error :recv_var_type, [trecv], e - return [[ret_type]] + return [ret_type] when RDL::Type::MethodType if meth == :call # Special case - invokes the Proc @@ -1964,7 +1845,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, tc_send_one_recv(scope, env, RDL::Globals.types[:proc], meth, tactuals, block, e, op_asgn, union) end when RDL::Type::DynamicType - return [[trecv]] + return [trecv] else raise RuntimeError, "receiver type #{trecv} of kind #{trecv.class} not supported yet, meth=#{meth}" end @@ -2019,34 +1900,11 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, #apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? if tmeth_inst begin - effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block + tc_block(scope, env, tmeth.block, block, tmeth_inst) if block rescue BlockTypeError => _ block_mismatch = true end - if es - es = es.map { |es_effect| if es_effect.nil? then es_effect else es_effect.clone end } - es.each { |es_effect| ## expecting just one effect per method right now. can clean this up later. - if !es_effect.nil? && (es_effect[1] == :blockdep || es_effect[0] == :blockdep) - #raise "Got block-dependent effect for method #{meth}, but no block." unless block && effblock - if !(block && effblock) - ## In this case we called a block-dependent method, - ## but with no block. It could, e.g., return an enumerator. - ## Could have more intricate handling, but for now will just - ## be conseervative and return [:-, :-] - es_effect[0] = :- - es_effect[1] = :- - elsif effblock[0] == :+ or effblock[0] == :~ - es_effect[1] = :+ - es_effect[0] = :+ - elsif effblock[0] == :- - es_effect[1] = :- - es_effect[0] = :- - else - raise "unexpected effect #{effblock[0]}" - end - end - } - end + if init#trecv.is_a?(RDL::Type::SingletonType) && meth == :new init_typ = RDL::Type::NominalType.new(trecv.val) if (tmeth.ret.instance_of?(RDL::Type::GenericType)) @@ -2159,7 +2017,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, error :arg_type_single_receiver_error, [name, meth, msg], e end # TODO: issue warning if trets.size > 1 ? - return [trets, es] + return trets end def self.apply_deferred_constraints(deferred_constraints, e) @@ -2411,10 +2269,9 @@ def self.tc_block(scope, env, tblock, block, inst) args_hash[a] = v v } - _, ret_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], nil] else tc(scope, env.merge(Env.new(args_hash)), body) end + _, ret_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(scope, env.merge(Env.new(args_hash)), body) end block_type = RDL::Type::MethodType.new(arg_vartypes, nil, ret_type) RDL::Type::Type.leq(block_type, tblock, inst, false, ast: body) - eff else # must be [block-args, block-body] args, body = block env, targs = args_hash(scope, env, tblock, args, block[1], 'block') @@ -2422,10 +2279,9 @@ def self.tc_block(scope, env, tblock, block, inst) # note: okay if outer_env shadows, since nested scope will include outer scope by next line targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this env = env.merge(Env.new(targs_dup)) - _, body_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], [:+, :+]] else tc(bscope, env.merge(Env.new(targs)), body) end + _, body_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(bscope, env.merge(Env.new(targs)), body) end error :bad_return_type, [body_type, tblock.ret], body, block: true unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false, ast: body) # - eff } end end @@ -2445,19 +2301,18 @@ def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_un # return array of all matching types from context_types, if any ts = [] scope[:context_types].each { |ctk, ctm, ctt| ts << ctt if ctk.to_s == klass && ctm == name } - return [ts, [[:-, :-]]] unless ts.empty? ## not sure what to do about effects here, so just going to be super conservative + return ts unless ts.empty? end if scope[:context_types] scope[:context_types].each { |k, m, t| - return [t, [[:-, :-]]] if k == klass && m = name ## not sure what to do about effects here, so just going to be super conservative + return t if k == klass && m = name } end t = RDL::Globals.info.get_with_aliases(klass, name, :type) - e = RDL::Globals.info.get_with_aliases(klass, name, :effect) - return [t, e] if t # simplest case, no need to walk inheritance hierarchy - return [[make_unknown_method_type(klass, name)]] if RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } + return t if t # simplest case, no need to walk inheritance hierarchy + return [make_unknown_method_type(klass, name)] if RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } klass_name = RDL::Util.has_singleton_marker(klass) ? RDL::Util.remove_singleton_marker(klass) : klass - return [[make_unknown_method_type(klass, name)]] if make_unknown && (name == :initialize) && (RDL::Util.to_class(klass_name).instance_method(:initialize).owner == RDL::Util.to_class(klass_name)) # don't want to walk up hierarchy in initialize case where it is specifically defined for this class + return [make_unknown_method_type(klass, name)] if make_unknown && (name == :initialize) && (RDL::Util.to_class(klass_name).instance_method(:initialize).owner == RDL::Util.to_class(klass_name)) # don't want to walk up hierarchy in initialize case where it is specifically defined for this class the_klass = RDL::Util.to_class(klass) is_singleton = RDL::Util.has_singleton_marker(klass) included = RDL::Util.to_class(klass.gsub("[s]", "")).included_modules @@ -2475,14 +2330,12 @@ def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_un anc_lookup = ancestor.to_s end tancestor = RDL::Globals.info.get_with_aliases(anc_lookup, name, :type) - eancestor = RDL::Globals.info.get_with_aliases(anc_lookup, name, :effect) - return [tancestor, eancestor] if tancestor - return [[make_unknown_method_type(anc_lookup, name)]] if RDL::Globals.to_infer.values.any? { |set| set.include?([anc_lookup, name]) } + return tancestor if tancestor + return [make_unknown_method_type(anc_lookup, name)] if RDL::Globals.to_infer.values.any? { |set| set.include?([anc_lookup, name]) } # special caes: Kernel's singleton methods are *also* added when included?! if ancestor == Kernel tancestor = RDL::Globals.info.get_with_aliases(RDL::Util.add_singleton_marker('Kernel'), name, :type) - eancestor = RDL::Globals.info.get_with_aliases(RDL::Util.add_singleton_marker('Kernel'), name, :effect) - return [tancestor, eancestor] if tancestor + return tancestor if tancestor end if ancestor.instance_methods(false).member?(name) ## Milod: Not sure what the purpose of the below lines is. @@ -2515,12 +2368,12 @@ def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_un ret = RDL::Globals.types[:dyn] ret = RDL::Type::NominalType.new(the_klass) if name == :initialize - return [[RDL::Type::MethodType.new(args, nil, ret)]] + return [RDL::Type::MethodType.new(args, nil, ret)] else #return nil ## Trying new approach: create unknown method type for any methods without types. if make_unknown - return [[make_unknown_method_type(klass, name)]] if (RDL::Util.to_class(klass).instance_methods + RDL::Util.to_class(klass).private_instance_methods).include?(name) + return [make_unknown_method_type(klass, name)] if (RDL::Util.to_class(klass).instance_methods + RDL::Util.to_class(klass).private_instance_methods).include?(name) else return nil end @@ -2663,7 +2516,6 @@ def message RDL_MESSAGES = { bad_return_type: "got type `%s' where return type `%s' expected", - bad_effect: "got effect `%s' where effect `%s' expected", bad_inst_type: "instantiate! called on object of type `%s' where Generic Type was expected", inst_not_param: "instantiate! receiver is of class `%s' which is not parameterized", inst_num_args: "instantiate! expecting `%s' type parameters, got `%s' parameters", diff --git a/lib/rdl/types/rdl_types.rb b/lib/rdl/types/rdl_types.rb index 6fe8d413..2beb8d9e 100644 --- a/lib/rdl/types/rdl_types.rb +++ b/lib/rdl/types/rdl_types.rb @@ -1,45 +1,45 @@ RDL.type_params 'RDL::Type::SingletonType', [:t], :satisfies? -RDL.type 'RDL::Type::SingletonType', :initialize, "(x) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::SingletonType', :val, "() -> t", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::SingletonType', :nominal, "() -> RDL::Type::NominalType", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::SingletonType', :initialize, "(x) -> self", wrap: false +RDL.type 'RDL::Type::SingletonType', :val, "() -> t", wrap: false +RDL.type 'RDL::Type::SingletonType', :nominal, "() -> RDL::Type::NominalType", wrap: false -RDL.type 'RDL::Type::NominalType', :initialize, "(Class or String) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::NominalType', :klass, "() -> Class", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::NominalType', :name, "() -> String", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::NominalType', :initialize, "(Class or String) -> self", wrap: false +RDL.type 'RDL::Type::NominalType', :klass, "() -> Class", wrap: false +RDL.type 'RDL::Type::NominalType', :name, "() -> String", wrap: false -RDL.type 'RDL::Type::GenericType', :initialize, "(RDL::Type::Type, *RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::GenericType', :params, "() -> Array", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::GenericType', :base, "() -> RDL::Type::NominalType", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::GenericType', :initialize, "(RDL::Type::Type, *RDL::Type::Type) -> self", wrap: false +RDL.type 'RDL::Type::GenericType', :params, "() -> Array", wrap: false +RDL.type 'RDL::Type::GenericType', :base, "() -> RDL::Type::NominalType", wrap: false -RDL.type 'RDL::Type::UnionType', :initialize, "(*RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::UnionType', :canonical, "() -> RDL::Type::Type", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::UnionType', :types, "() -> Array", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::UnionType', :initialize, "(*RDL::Type::Type) -> self", wrap: false +RDL.type 'RDL::Type::UnionType', :canonical, "() -> RDL::Type::Type", wrap: false +RDL.type 'RDL::Type::UnionType', :types, "() -> Array", wrap: false -RDL.type 'RDL::Type::TupleType', :initialize, "(*RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::TupleType', :params, "() -> Array", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::TupleType', :promote, "(?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::TupleType', :promote!, "(?RDL::Type::Type) -> %bool", wrap: false, effect: [:-, :+] -RDL.type 'RDL::Type::TupleType', :check_bounds, "(?%bool) -> %bool", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::TupleType', :initialize, "(*RDL::Type::Type) -> self", wrap: false +RDL.type 'RDL::Type::TupleType', :params, "() -> Array", wrap: false +RDL.type 'RDL::Type::TupleType', :promote, "(?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false +RDL.type 'RDL::Type::TupleType', :promote!, "(?RDL::Type::Type) -> %bool", wrap: false +RDL.type 'RDL::Type::TupleType', :check_bounds, "(?%bool) -> %bool", wrap: false -RDL.type 'RDL::Type::FiniteHashType', :elts, "() -> Hash<%any, RDL::Type::Type>", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::FiniteHashType', :elts=, "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", wrap: false, effect: [:-, :+] -RDL.type 'RDL::Type::FiniteHashType', :promote, "(?%any, ?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::FiniteHashType', :promote!, "(?%any, ?RDL::Type::Type) -> %bool", wrap: false, effect: [:-, :+] -RDL.type 'RDL::Type::FiniteHashType', :initialize, "(Hash<%any, RDL::Type::Type> or {}, ?RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::FiniteHashType', :check_bounds, "(?%bool) -> %bool", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::FiniteHashType', :elts, "() -> Hash<%any, RDL::Type::Type>", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :elts=, "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :promote, "(?%any, ?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :promote!, "(?%any, ?RDL::Type::Type) -> %bool", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :initialize, "(Hash<%any, RDL::Type::Type> or {}, ?RDL::Type::Type) -> self", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :check_bounds, "(?%bool) -> %bool", wrap: false -RDL.type 'RDL::Type::OptionalType', :initialize, "(RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::OptionalType', :initialize, "(RDL::Type::Type) -> self", wrap: false -RDL.type 'RDL::Type::VarargType', :initialize, '(RDL::Type::Type) -> self', wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::VarargType', :initialize, '(RDL::Type::Type) -> self', wrap: false -RDL.type 'RDL::Type::VarType', :initialize, "(String) -> self", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::VarType', :initialize, "(String) -> self", wrap: false -RDL.type "RDL::Type::Type", 'self.leq', "(RDL::Type::Type, RDL::Type::Type) -> %bool", wrap: false, effect: [:+, :+] +RDL.type "RDL::Type::Type", 'self.leq', "(RDL::Type::Type, RDL::Type::Type) -> %bool", wrap: false -RDL.type 'RDL::Globals', 'self.parser', "() -> RDL::Type::Parser", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Globals', 'self.types', "() -> Hash", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::Parser', :scan_str, "(String) -> RDL::Type::Type", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Config', 'self.instance', "() -> RDL::Config", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Config', 'weak_update_promote', "() -> %bool", wrap: false, effect: [:+, :+] -RDL.type "Object", '__getobj__', "() -> self", wrap: false, effect: [:+, :+] ## needed due to type casting +RDL.type 'RDL::Globals', 'self.parser', "() -> RDL::Type::Parser", wrap: false +RDL.type 'RDL::Globals', 'self.types', "() -> Hash", wrap: false +RDL.type 'RDL::Type::Parser', :scan_str, "(String) -> RDL::Type::Type", wrap: false +RDL.type 'RDL::Config', 'self.instance', "() -> RDL::Config", wrap: false +RDL.type 'RDL::Config', 'weak_update_promote', "() -> %bool", wrap: false +RDL.type "Object", '__getobj__', "() -> self", wrap: false ## needed due to type casting diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 93b46432..de3eb415 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -279,7 +279,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con right.methods.each_pair { |m, t| return false unless lklass.method_defined?(m) || RDL::Typecheck.lookup({}, klass_lookup, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) ## Added the second condition because Rails lazily defines some methods. - types, _ = RDL::Typecheck.lookup({}, lklass.to_s, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) + types = RDL::Typecheck.lookup({}, lklass.to_s, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) if RDL::Config.instance.use_comp_types types = RDL::Typecheck.filter_comp_types(types, true) else @@ -378,16 +378,16 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con klass = left.base.klass right.methods.each_pair { |meth, t| if (klass.to_s == "ActiveRecord_Relation") && !klass.method_defined?(meth) && defined? DBType - types, _ = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false) + types = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false) if !types - base_types, _ = RDL::Typecheck.lookup({}, "[s]"+DBType.rec_to_nominal(left).name, meth, {}, make_unknown: false) + base_types = RDL::Typecheck.lookup({}, "[s]"+DBType.rec_to_nominal(left).name, meth, {}, make_unknown: false) return false unless base_types types = base_types.map { |t| RDL::Type::MethodType.new(t.args, t.block, left) } end return false unless types else return false unless klass.method_defined?(meth) || RDL::Typecheck.lookup({}, klass.to_s, meth, nil, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) ## Added the second condition because Rails lazily defines some methods. - types, _ = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) + types = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) end if RDL::Config.instance.use_comp_types diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 4f3d3c2f..bbfd57e7 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -270,7 +270,6 @@ def self.do_method_added(the_self, sing, klass, meth) raise RuntimeError, "Deferred #{kind} contract from class #{prev_klass} being applied in class #{tmp_klass} to #{meth}" end RDL::Globals.info.add(klass, meth, kind, contract) - RDL::Globals.info.add(klass, meth, :effect, h[:effect]) if h.has_key?(:effect) RDL::Wrap.wrap(klass, meth) if h[:wrap] unless !h.has_key?(:typecheck) || RDL::Globals.info.set(klass, meth, :typecheck, h[:typecheck]) raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}" @@ -398,7 +397,7 @@ def post(*args, wrap: RDL::Config.instance.post_defaults[:wrap], version: nil, & # type(klass, meth, type) # type(meth, type) # type(type) - def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil, effect: nil) + def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil) return if version && !(Gem::Requirement.new(version).satisfied_by? Gem.ruby_version) klass, meth, type = begin RDL::Wrap.process_type_args(self, *args) @@ -412,7 +411,6 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: err.set_backtrace bt raise err end - effect[0] = :- if effect && effect[0] == :~ ## For now, treating pure-ish :~ as :-, since we realized it doesn't actually affect termination checking. typs = type.args + [type.ret] if type.block block_type = type.block.is_a?(RDL::Type::OptionalType) ? type.block.type : type.block @@ -425,7 +423,6 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: # warn "#{RDL::Util.pp_klass_method(klass, meth)}: methods that end in ? should have return type %bool" # end RDL::Globals.info.add(klass, meth, :type, type) - RDL::Globals.info.add(klass, meth, :effect, effect) unless RDL::Globals.info.set(klass, meth, :typecheck, typecheck) raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}" end @@ -449,7 +446,7 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: end else RDL::Globals.deferred << [klass, :type, type, {wrap: wrap, - typecheck: typecheck, effect: effect}] + typecheck: typecheck}] end nil end @@ -971,7 +968,6 @@ def self.load_rails_schema def self.check_type_code RDL.config { |config| config.use_comp_types = false } count = 1 - #code_type = RDL::Globals.parser.scan_str "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL::Globals.dep_types.each { |klass, meth, typ| klass = RDL::Util.has_singleton_marker(klass) ? RDL::Util.remove_singleton_marker(klass) : klass arg_list = "(trec, targs" @@ -997,7 +993,7 @@ def self.check_type_code end eval tmp_eval ast = Parser::CurrentRuby.parse tmp_meth - RDL::Typecheck.typecheck("[s]#{klass}", "tc_#{meth}#{count}".to_sym, ast, [code_type], [[:-, :+]]) + RDL::Typecheck.typecheck("[s]#{klass}", "tc_#{meth}#{count}".to_sym, ast, [code_type]) count += 1 end } diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 2484830a..b3f6fe07 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -12,7 +12,7 @@ def Array.to_type(t) RDL::Globals.parser.scan_str "#T #{t}" end end -RDL.type Array, 'self.to_type', "(Object) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.to_type', "(Object) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.output_type(trec, targs, meth_name, default1, default2=default1, use_sing_val: true, nil_false_default: false) @@ -45,7 +45,7 @@ def Array.output_type(trec, targs, meth_name, default1, default2=default1, use_s RDL::Globals.parser.scan_str "#T #{default2}" end end -RDL.type Array, 'self.output_type', "(RDL::Type::Type, Array, Symbol, String or Symbol, ?(String or Symbol), { use_sing_val: ?%bool, nil_false_default: ?%bool }) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.output_type', "(RDL::Type::Type, Array, Symbol, String or Symbol, ?(String or Symbol), { use_sing_val: ?%bool, nil_false_default: ?%bool }) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.any_or_t(trec, vararg=false) @@ -58,7 +58,7 @@ def Array.any_or_t(trec, vararg=false) if vararg then RDL::Type::VarargType.new(ret) else ret end end end -RDL.type Array, 'self.any_or_t', "(RDL::Type::Type, ?%bool) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.any_or_t', "(RDL::Type::Type, ?%bool) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.promoted_or_t(trec, vararg=false) @@ -71,7 +71,7 @@ def Array.promoted_or_t(trec, vararg=false) if vararg then RDL::Type::VarargType.new(ret) else ret end end end -RDL.type Array, 'self.promoted_or_t', "(RDL::Type::Type, ?%bool) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.promoted_or_t', "(RDL::Type::Type, ?%bool) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.promote_tuple(trec) @@ -82,7 +82,7 @@ def Array.promote_tuple(trec) trec end end -RDL.type Array, 'self.promote_tuple', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.promote_tuple', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.promote_tuple!(trec) @@ -94,7 +94,7 @@ def Array.promote_tuple!(trec) trec end end -RDL.type Array, 'self.promote_tuple!', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:~, :+] +RDL.type Array, 'self.promote_tuple!', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code RDL.type :Array, :<<, '(``any_or_t(trec)``) -> ``append_push_output(trec, targs, :<<)``' @@ -109,7 +109,7 @@ def Array.append_push_output(trec, targs, meth) RDL::Globals.parser.scan_str "#T Array" end end -RDL.type Array, 'self.append_push_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.append_push_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :[], '(Range) -> ``output_type(trec, targs, :[], :promoted_array, "Array")``' RDL.type :Array, :[], '(Integer or Float) -> ``output_type(trec, targs, :[], :promoted_param, "t")``' @@ -132,7 +132,7 @@ def Array.plus_input(targs) RDL::Globals.types[:array] end end -RDL.type Array, 'self.plus_input', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Array, 'self.plus_input', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Array.plus_output(trec, targs) @@ -165,7 +165,7 @@ def Array.plus_output(trec, targs) end end end -RDL.type Array, 'self.plus_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Array, 'self.plus_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :-, '(Array) -> ``output_type(trec, targs, :-, :promoted_array, "Array")``' RDL.type :Array, :slice, '(Range) -> ``output_type(trec, targs, :slice, :promoted_array, "Array")``' @@ -198,7 +198,7 @@ def Array.assign_output(trec, targs) RDL::Globals.parser.scan_str "#T t" end end -RDL.type Array, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :[]=, '(Integer, Integer, ``any_or_t(trec)``) -> t' RDL.type :Array, :[]=, '(Integer, Integer, ``any_or_t(trec)``) -> t' @@ -220,7 +220,7 @@ def Array.multi_assign_output(trec, targs) end =end end -RDL.type Array, 'self.multi_assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.multi_assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :[]=, '(Range, ``any_or_t(trec)``) -> ``multi_assign_output(trec, targs)``' RDL.type :Array, :assoc, '(t) -> Array' @@ -240,7 +240,7 @@ def Array.map_output(trec) RDL::Globals.parser.scan_str "#T Array" end end -RDL.type Array, 'self.map_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.map_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :map!, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :collect, '() {(``promoted_or_t(trec)``) -> u} -> Array' @@ -310,7 +310,7 @@ def Array.each_arg2(trec) RDL.type :Array, :each_index, '() { (Integer) -> %any } -> self' RDL.type :Array, :each_index, '() -> Enumerator' -RDL.type :Array, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``', effect: [:+, :+] +RDL.type :Array, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``' RDL.type :Array, :fetch, '(Integer) -> ``output_type(trec, targs, :[], :promoted_param, "t")``' RDL.type :Array, :fetch, '(Integer, %any) -> ``RDL::Type::UnionType.new(targs[1], output_type(trec, targs, :[], :promoted_param, "t"))``' RDL.type :Array, :fetch, '(Integer) { (Integer) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :[], :promoted_param, "t"))``' @@ -333,7 +333,7 @@ def Array.fill_output(trec, targs) RDL::Globals.parser.scan_str "#T Array" end end -RDL.type Array, 'self.fill_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.fill_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :fill, '(``promoted_or_t(trec)``, Integer, ?Integer) -> ``promote_tuple!(trec)``' ## can be more precise for this one, but would require many cases RDL.type :Array, :fill, '(``promoted_or_t(trec)``, Range) -> ``promote_tuple!(trec)``' @@ -367,7 +367,7 @@ def Array.include_output(trec, targs) RDL::Globals.types[:bool] end end -RDL.type Array, 'self.include_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Array, 'self.include_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :insert, '(Integer, ``promoted_or_t(trec)``) -> ``promote_tuple!(trec)``' @@ -417,7 +417,7 @@ def Array.reverse_output(trec) RDL::Globals.parser.scan_str "#T Array" end end -RDL.type Array, 'self.reverse_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.reverse_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :reverse_each, '() { (``promoted_or_t(trec)``) -> %any } -> self' RDL.type :Array, :reverse_each, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' @@ -470,31 +470,31 @@ def Array.reverse_output(trec) ######### Non-dependet types below ######### -RDL.type :Array, :<<, '(t) -> Array', effect: [:-, :+] -RDL.type :Array, :[], '(Range) -> Array', effect: [:+, :+] -RDL.type :Array, :[], '(Integer or Float) -> t', effect: [:+, :+] -RDL.type :Array, :[], '(Integer, Integer) -> Array', effect: [:+, :+] +RDL.type :Array, :<<, '(t) -> Array' +RDL.type :Array, :[], '(Range) -> Array' +RDL.type :Array, :[], '(Integer or Float) -> t' +RDL.type :Array, :[], '(Integer, Integer) -> Array' RDL.type :Array, :&, '(Array) -> Array' RDL.type :Array, :*, '(Integer) -> Array' RDL.type :Array, :*, '(String) -> String' -RDL.type :Array, :+, '(Enumerable) -> Array', effect: [:+, :+] -RDL.type :Array, :+, '(Array) -> Array', effect: [:+, :+] +RDL.type :Array, :+, '(Enumerable) -> Array' +RDL.type :Array, :+, '(Array) -> Array' RDL.type :Array, :-, '(Array) -> Array' RDL.type :Array, :slice, '(Range) -> Array' RDL.type :Array, :slice, '(Integer) -> t' RDL.type :Array, :slice, '(Integer, Integer) -> Array' -RDL.type :Array, :[]=, '(Integer, t) -> t', effect: [:-, :+] -RDL.type :Array, :[]=, '(Integer, Integer, t) -> t', effect: [:-, :+] +RDL.type :Array, :[]=, '(Integer, t) -> t' +RDL.type :Array, :[]=, '(Integer, Integer, t) -> t' # RDL.type :Array, :[]=, '(Integer, Integer, Array) -> Array' # RDL.type :Array, :[]=, '(Range, Array) -> Array' -RDL.type :Array, :[]=, '(Range, t) -> t', effect: [:-, :+] +RDL.type :Array, :[]=, '(Range, t) -> t' RDL.type :Array, :assoc, '(t) -> Array' RDL.type :Array, :at, '(Integer) -> t' RDL.type :Array, :clear, '() -> Array' -RDL.type :Array, :map, '() {(t) -> u} -> Array', effect: [:blockdep, :blockdep] -RDL.type :Array, :map, '() -> Enumerator', effect: [:blockdep, :blockdep] -RDL.type :Array, :map!, '() {(t) -> u} -> Array', effect: [:-, :blockdep] -RDL.type :Array, :map!, '() -> Enumerator', effect: [:-, :blockdep] +RDL.type :Array, :map, '() {(t) -> u} -> Array' +RDL.type :Array, :map, '() -> Enumerator' +RDL.type :Array, :map!, '() {(t) -> u} -> Array' +RDL.type :Array, :map!, '() -> Enumerator' RDL.type :Array, :collect, '() { (t) -> u } -> Array' RDL.type :Array, :collect, '() -> Enumerator' RDL.type :Array, :combination, '(Integer) { (Array) -> %any } -> Array' @@ -516,8 +516,8 @@ def Array.reverse_output(trec) RDL.type :Array, :drop, '(Integer) -> Array' RDL.type :Array, :drop_while, '() { (t) -> %bool } -> Array' RDL.type :Array, :drop_while, '() -> Enumerator' -RDL.type :Array, :each, '() -> Enumerator', effect: [:-, :blockdep] -RDL.type :Array, :each, '() { (t) -> %any } -> Array', effect: [:-, :blockdep] +RDL.type :Array, :each, '() -> Enumerator' +RDL.type :Array, :each, '() { (t) -> %any } -> Array' RDL.type :Array, :each_index, '() { (Integer) -> %any } -> Array' RDL.type :Array, :each_index, '() -> Enumerator' RDL.type :Array, :empty?, '() -> %bool' @@ -536,7 +536,7 @@ def Array.reverse_output(trec) RDL.type :Array, :index, '() -> Enumerator' RDL.type :Array, :first, '() -> t' RDL.type :Array, :first, '(Integer) -> Array' -RDL.type :Array, :include?, '(u) -> %bool', effect: [:+, :+] +RDL.type :Array, :include?, '(u) -> %bool' RDL.type :Array, :initialize, '() -> self' RDL.type :Array, :initialize, '(Integer) -> self' RDL.type :Array, :initialize, '(Integer, t) -> self' @@ -547,8 +547,8 @@ def Array.reverse_output(trec) RDL.type :Array, :keep_if, '() { (t) -> %bool } -> Array' RDL.type :Array, :last, '() -> t' RDL.type :Array, :last, '(Integer) -> Array' -RDL.type :Array, :member?, '(u) -> %bool', effect: [:+, :+] -RDL.type :Array, :length, '() -> Integer', effect: [:+, :+] +RDL.type :Array, :member?, '(u) -> %bool' +RDL.type :Array, :length, '() -> Integer' RDL.type :Array, :pack, "(String) -> String" RDL.type :Array, :permutation, '(?Integer) -> Enumerator' RDL.type :Array, :permutation, '(?Integer) { (Array) -> %any } -> Array' @@ -556,7 +556,7 @@ def Array.reverse_output(trec) RDL.type :Array, :pop, '() -> t' RDL.type :Array, :product, '(*Array) -> Array>' RDL.type :Array, :rassoc, '(u) -> t' -RDL.type :Array, :reject, '() { (t) -> %bool } -> Array', effect: [:+, :blockdep] +RDL.type :Array, :reject, '() { (t) -> %bool } -> Array' RDL.type :Array, :reject, '() -> Enumerator' RDL.type :Array, :reject!, '() { (t) -> %bool } -> Array' RDL.type :Array, :reject!, '() -> Enumerator' @@ -564,7 +564,7 @@ def Array.reverse_output(trec) RDL.type :Array, :repeated_combination, '(Integer) -> Enumerator' RDL.type :Array, :repeated_permutation, '(Integer) { (Array) -> %any } -> Array' RDL.type :Array, :repeated_permutation, '(Integer) -> Enumerator' -RDL.type :Array, :reverse, '() -> Array', effect: [:+, :+] +RDL.type :Array, :reverse, '() -> Array' RDL.type :Array, :reverse!, '() -> Array' RDL.type :Array, :reverse_each, '() { (t) -> %any } -> Array' RDL.type :Array, :reverse_each, '() -> Enumerator' diff --git a/lib/types/core/basic_object.rb b/lib/types/core/basic_object.rb index 2fbd79e9..9e906246 100644 --- a/lib/types/core/basic_object.rb +++ b/lib/types/core/basic_object.rb @@ -1,9 +1,9 @@ RDL.nowrap :BasicObject -RDL.type :BasicObject, :==, '(%any other) -> %bool', effect: [:+, :+] +RDL.type :BasicObject, :==, '(%any other) -> %bool' RDL.type :BasicObject, :equal?, '(%any other) -> %bool' -RDL.type :BasicObject, :!, '() -> %bool', effect: [:+, :+] -RDL.type :BasicObject, :!=, '(%any other) -> %bool', effect: [:+, :+] +RDL.type :BasicObject, :!, '() -> %bool' +RDL.type :BasicObject, :!=, '(%any other) -> %bool' RDL.type :BasicObject, :instance_eval, '(String, ?String filename, ?Integer lineno) -> %any' RDL.type :BasicObject, :instance_eval, '() { () -> %any } -> %any' RDL.type :BasicObject, :instance_exec, '(*%any args) { (*%any) -> %any } -> %any' diff --git a/lib/types/core/class.rb b/lib/types/core/class.rb index b3eab955..8eb396bb 100644 --- a/lib/types/core/class.rb +++ b/lib/types/core/class.rb @@ -13,5 +13,5 @@ RDL.type :Class, :class, '() -> Class' RDL.type :Class, :superclass, '() -> Class' RDL.type :Class, :name, '() -> String' -RDL.type :Class, :==, '(%any) -> %bool', effect: [:+, :+] -RDL.type :Class, :===, '(%any) -> %bool', effect: [:+, :+] +RDL.type :Class, :==, '(%any) -> %bool' +RDL.type :Class, :===, '(%any) -> %bool' diff --git a/lib/types/core/enumerable.rb b/lib/types/core/enumerable.rb index 3b7cc3cf..d45b4020 100644 --- a/lib/types/core/enumerable.rb +++ b/lib/types/core/enumerable.rb @@ -2,9 +2,9 @@ RDL.type_params :Enumerable, [:t], :all? -RDL.type :Enumerable, :all?, '() -> %bool', effect: [:blockdep, :blockdep] -RDL.type :Enumerable, :all?, '() { (t) -> %bool } -> %bool', effect: [:blockdep, :blockdep] -RDL.type :Enumerable, :all?, '() { (k, v) -> %bool } -> %bool', effect: [:blockdep, :blockdep] +RDL.type :Enumerable, :all?, '() -> %bool' +RDL.type :Enumerable, :all?, '() { (t) -> %bool } -> %bool' +RDL.type :Enumerable, :all?, '() { (k, v) -> %bool } -> %bool' RDL.type :Enumerable, :any?, '() -> %bool' RDL.type :Enumerable, :any?, '() { (t) -> %any } -> %bool' # RDL.type :Enumerable, :chunk, '(XXXX : *XXXX)' # TODO @@ -28,8 +28,8 @@ RDL.type :Enumerable, :each_cons, '(Integer n) -> Enumerator' # RDL.type :Enumerable, :each_entry, '(XXXX : *XXXX)' # TODO RDL.rdl_alias :Enumerable, :each_slice, :each_cons -RDL.type :Enumerable, :each_with_index, '() { (t, Integer) -> %any } -> Enumerable', effect: [:blockdep, :blockdep] # args! note may not return self -RDL.type :Enumerable, :each_with_index, '() -> Enumerable', effect: [:blockdep, :blockdep] # args! note may not return self +RDL.type :Enumerable, :each_with_index, '() { (t, Integer) -> %any } -> Enumerable' +RDL.type :Enumerable, :each_with_index, '() -> Enumerable' # args! note may not return self # RDL.type :Enumerable, :each_with_object, '(XXXX : XXXX)' #TODO RDL.type :Enumerable, :entries, '() -> Array' # TODO args? RDL.rdl_alias :Enumerable, :find, :detect diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 44463619..01314211 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -54,7 +54,7 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_de end end end -RDL.type Hash, 'self.output_type', "(RDL::Type::Type, Array, Symbol, Symbol or String, ?(Symbol or String), { nil_default: ?%bool, use_sing_val: ?%bool } ) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.output_type', "(RDL::Type::Type, Array, Symbol, Symbol or String, ?(Symbol or String), { nil_default: ?%bool, use_sing_val: ?%bool } ) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.to_type(t) @@ -74,7 +74,7 @@ def Hash.to_type(t) RDL::Type::NominalType.new(t.class) end end -RDL.type Hash, 'self.to_type', "(%any) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.to_type', "(%any) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.any_or_k(trec) case trec @@ -93,7 +93,7 @@ def Hash.any_or_k(trec) raise "unexpected, got #{trec}" end end -RDL.type Hash, 'self.any_or_k', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.any_or_k', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.any_or_v(trec) case trec @@ -106,7 +106,7 @@ def Hash.any_or_v(trec) raise "unexpected" end end -RDL.type Hash, 'self.any_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.any_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.promoted_or_v(trec) case trec @@ -119,7 +119,7 @@ def Hash.promoted_or_v(trec) raise "unexpected" end end -RDL.type Hash, 'self.promoted_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.promoted_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.promoted_or_k(trec) case trec @@ -151,7 +151,7 @@ def Hash.weak_promote(val) val end end -RDL.type Hash, 'self.weak_promote', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.weak_promote', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false #RDL.type :Hash, 'self.[]', '(*%any) -> ``hash_create_output_from_list(targs)``' RDL.type :Hash, 'self.[]', '(*%any) -> ``hash_create_output(targs)``' @@ -182,9 +182,9 @@ def Hash.hash_create_output(targs) args = targs.map { |a| i = i+1 ; if i.even? && a.is_a?(RDL::Type::SingletonType) then RDL.type_cast(a, "RDL::Type::SingletonType", force: true).val else a end } RDL::Type::FiniteHashType.new(RDL.type_cast(Hash[*args], "Hash<%any, RDL::Type::Type>", force: true), nil) end -RDL.type Hash, 'self.hash_create_output', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.hash_create_output', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false -RDL.type :Hash, :[], '(``any_or_k(trec)``) -> ``output_type(trec, targs, :[], :default_or_promoted_val, "v", nil_default: true)``', effect: [:+, :+] +RDL.type :Hash, :[], '(``any_or_k(trec)``) -> ``output_type(trec, targs, :[], :default_or_promoted_val, "v", nil_default: true)``' RDL.type :Hash, :[]=, '(``any_or_k(trec)``, ``any_or_v(trec)``) -> ``assign_output(trec, targs)``' @@ -208,7 +208,7 @@ def Hash.assign_output(trec, targs) trec.params[1] end end -RDL.type Hash, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type Hash, :initialize, "(*%any) -> ``RDL::Type::FiniteHashType.new({}, nil, default: targs[0])``" RDL.type Hash, :initialize, "() { (Hash, x) -> y } -> ``RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:top], RDL::Globals.types[:top])``" @@ -249,7 +249,7 @@ def Hash.delete_output(trec, targs, block) RDL::Globals.parser.scan_str "#T #{t}" end end -RDL.type Hash, 'self.delete_output', "(RDL::Type::Type, Array, %bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.delete_output', "(RDL::Type::Type, Array, %bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Hash, :delete_if, '() { (``promoted_or_k(trec)``, ``promoted_or_v(trec)``) -> %any } -> self' RDL.type :Hash, :delete_if, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. @@ -267,9 +267,9 @@ def Hash.delete_output(trec, targs, block) RDL.type :Hash, :fetch, '(``any_or_k(trec)``, ``targs[1] ? targs[1] : RDL::Globals.types[:top]``) -> ``RDL::Type::UnionType.new(targs[1] ? targs[1] : RDL::Globals.types[:top], output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``any_or_k(trec)``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { () -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' -RDL.type :Hash, :first, '() -> ``output_type(trec, targs, :first, :default_or_promoted_val, "v", nil_default: true)``', effect: [:+, :+] +RDL.type :Hash, :first, '() -> ``output_type(trec, targs, :first, :default_or_promoted_val, "v", nil_default: true)``' RDL.type :Hash, :member?, '(%any) -> ``output_type(trec, targs, :member?, "%bool")``' -RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``', effect: [:+, :+] +RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``' RDL.type :Hash, :key?, '(%any) -> ``output_type(trec, targs, :key?, "%bool")``' RDL.type :Hash, :has_value?, '(%any) -> ``output_type(trec, targs, :has_value?, "%bool")``' RDL.type :Hash, :value?, '(%any) -> ``output_type(trec, targs, :value?, "%bool")``' @@ -289,7 +289,7 @@ def Hash.invert_output(trec) #RDL::Globals.parser.scan_str "#T Hash" end end -RDL.type Hash, 'self.invert_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.invert_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Hash, :keep_if, '() { (``any_or_k(trec)``,``any_or_v(trec)``) -> %bool } -> self' RDL.type :Hash, :keep_if, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. @@ -321,7 +321,7 @@ def Hash.merge_input(trec, targs, mutate=false) RDL::Globals.types[:hash] end end -RDL.type Hash, 'self.merge_input', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.merge_input', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.merge_output(trec, targs, mutate=false) @@ -383,7 +383,7 @@ def Hash.merge_output(trec, targs, mutate=false) end end -RDL.type Hash, 'self.merge_output', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, 'self.merge_output', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Hash, :merge, '(Hash) { (k,v,b) -> v or b } -> Hash' RDL.type :Hash, :rassoc, '(``any_or_v(trec)``) -> ``RDL::Type::TupleType.new(output_type(trec, targs, :key, :promoted_key, "k", nil_default: true, use_sing_val: false),targs[0])``' @@ -406,7 +406,7 @@ def Hash.shift_output(trec) RDL::Type::TupleType.new(trec.params[0], trec.params[1]) end end -RDL.type Hash, 'self.shift_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.shift_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false #RDL.type :Hash, :to_a, '() -> ``output_type(trec, targs, :to_a, "Array<[k, v]>")``' RDL.type :Hash, :to_a, '() -> ``to_a_output_type(trec)")``' @@ -443,7 +443,7 @@ def Hash.values_at_input(trec) RDL::Type::VarargType.new(trec.params[0]) end end -RDL.type Hash, 'self.values_at_input', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, 'self.values_at_input', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.values_at_output(trec, targs) @@ -463,7 +463,7 @@ def Hash.values_at_output(trec, targs) RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), trec.params[1]) end end -RDL.type Hash, 'self.values_at_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, 'self.values_at_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false @@ -471,15 +471,15 @@ def Hash.values_at_output(trec, targs) ######### Non-dependent types below ######### -RDL.type :Hash, 'self.[]', '(*u) -> Hash', effect: [:+, :+] # example: Hash[1,2,3,4] -RDL.type :Hash, 'self.[]', '(Array<[a,b]>) -> Hash', effect: [:+, :+] -RDL.type :Hash, 'self.[]', '([to_hash: () -> Hash]) -> Hash', effect: [:+, :+] +RDL.type :Hash, 'self.[]', '(*u) -> Hash' # example: Hash[1,2,3,4] +RDL.type :Hash, 'self.[]', '(Array<[a,b]>) -> Hash' +RDL.type :Hash, 'self.[]', '([to_hash: () -> Hash]) -> Hash' RDL.type :Hash, :[], '(k) -> v' -RDL.type :Hash, :[]=, '(k, v) -> v', effect: [:-, :+] +RDL.type :Hash, :[]=, '(k, v) -> v' RDL.type :Hash, :store, '(k,v) -> v' -RDL.type :Hash, :any?, "() { (k, v) -> %any } -> %bool", effect: [:blockdep, :blockdep] +RDL.type :Hash, :any?, "() { (k, v) -> %any } -> %bool" # RDL.type :Hash, :assoc, '(k) -> [k, v]' # TODO RDL.type :Hash, :assoc, '(k) -> Array' RDL.type :Hash, :clear, '() -> Hash' @@ -497,44 +497,44 @@ def Hash.values_at_output(trec, targs) RDL.type :Hash, :delete, '(k) { (k) -> u } -> u or v' RDL.type :Hash, :delete_if, '() { (k,v) -> %bool } -> Hash' RDL.type :Hash, :delete_if, '() -> Enumerator<[k, v]>' -RDL.type :Hash, :each, '() { (k,v) -> %any } -> Hash', effect: [:blockdep, :blockdep] -RDL.type :Hash, :each, '() -> Enumerator<[k, v]>', effect: [:blockdep, :blockdep] +RDL.type :Hash, :each, '() { (k,v) -> %any } -> Hash' +RDL.type :Hash, :each, '() -> Enumerator<[k, v]>' RDL.type :Hash, :each_pair, '() { (k,v) -> %any } -> Hash' RDL.type :Hash, :each_pair, '() -> Enumerator<[k, v]>' -RDL.type :Hash, :each_key, '() { (k) -> %any } -> Hash', effect: [:blockdep, :blockdep] -RDL.type :Hash, :each_key, '() -> Enumerator<[k, v]>', effect: [:blockdep, :blockdep] +RDL.type :Hash, :each_key, '() { (k) -> %any } -> Hash' +RDL.type :Hash, :each_key, '() -> Enumerator<[k, v]>' RDL.type :Hash, :each_value, '() { (v) -> %any } -> Hash' RDL.type :Hash, :each_value, '() -> Enumerator<[k, v]>' RDL.type :Hash, :empty?, '() -> %bool' -RDL.type :Hash, :except, '(%any) -> self', effect: [:+, :+] +RDL.type :Hash, :except, '(%any) -> self' RDL.type :Hash, :fetch, '(k) -> v' RDL.type :Hash, :fetch, '(k,u) -> u or v' RDL.type :Hash, :fetch, '(k) { (k) -> u } -> u or v' -RDL.type :Hash, :map, "() { (k, v) -> x } -> Array", effect: [:+, :blockdep] +RDL.type :Hash, :map, "() { (k, v) -> x } -> Array" RDL.type :Hash, :member?, '(t) -> %bool' RDL.type :Hash, :has_key?, '(t) -> %bool' RDL.type :Hash, :key?, '(t) -> %bool' RDL.type :Hash, :has_value?, '(t) -> %bool' RDL.type :Hash, :value?, '(t) -> %bool' RDL.type :Hash, :to_s, '() -> String' -RDL.type :Hash, :include?, '(%any) -> %bool', effect: [:+, :+] +RDL.type :Hash, :include?, '(%any) -> %bool' RDL.type :Hash, :inspect, '() -> String' -RDL.type :Hash, :invert, '() -> Hash', effect: [:+, :+] +RDL.type :Hash, :invert, '() -> Hash' RDL.type :Hash, :keep_if, '() { (k,v) -> %bool } -> Hash' RDL.type :Hash, :keep_if, '() -> Enumerator<[k, v]>' RDL.type :Hash, :key, '(t) -> k' -RDL.type :Hash, :keys, '() -> Array', effect: [:+, :+] +RDL.type :Hash, :keys, '() -> Array' RDL.type :Hash, :length, '() -> Integer' -RDL.type :Hash, :size, '() -> Integer', effect: [:+, :+] -RDL.type :Hash, :merge, '(Hash) -> Hash', effect: [:+, :+] -RDL.type :Hash, :merge, '(Hash) { (k,v,b) -> v or b } -> Hash', effect: [:+, :+] +RDL.type :Hash, :size, '() -> Integer' +RDL.type :Hash, :merge, '(Hash) -> Hash' +RDL.type :Hash, :merge, '(Hash) { (k,v,b) -> v or b } -> Hash' # RDL.type :Hash, :rassoc, '(k) -> Tuple' RDL.type :Hash, :rassoc, '(k) -> Array' RDL.type :Hash, :rehash, '() -> Hash' RDL.type :Hash, :reject, '() -> Enumerator<[k, v]>' RDL.type :Hash, :reject, '() {(k,v) -> %bool} -> Hash' RDL.type :Hash, :reject!, '() {(k,v) -> %bool} -> Hash' -RDL.type :Hash, :select, '() {(k,v) -> %bool} -> Hash', effect: [:+, :blockdep] +RDL.type :Hash, :select, '() {(k,v) -> %bool} -> Hash' RDL.type :Hash, :select!, '() {(k,v) -> %bool} -> Hash' # RDL.type :Hash, :shift, '() -> Tuple' RDL.type :Hash, :shift, '() -> Array' @@ -543,5 +543,5 @@ def Hash.values_at_output(trec, targs) RDL.type :Hash, :to_hash, '() -> self' RDL.type :Hash, :to_h, '() -> self' RDL.type :Hash, :values, '() -> Array' -RDL.type :Hash, :values_at, '(*k) -> Array', effect: [:+, :+] +RDL.type :Hash, :values_at, '(*k) -> Array' RDL.type :Hash, :with_indifferent_access, '() -> self' diff --git a/lib/types/core/integer.rb b/lib/types/core/integer.rb index cf1e537a..1c6d2564 100644 --- a/lib/types/core/integer.rb +++ b/lib/types/core/integer.rb @@ -13,7 +13,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL::Globals.parser.scan_str "#T #{type}" end end -RDL.type Numeric, 'self.sing_or_type', "(RDL::Type::Type, Array, Symbol, String) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Numeric, 'self.sing_or_type', "(RDL::Type::Type, Array, Symbol, String) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Integer, :%, '(Integer x {{ x!=0 }}) -> ``sing_or_type(trec, targs, :%, "Integer")``' @@ -62,17 +62,17 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :/, '(Complex x {{ x!=0 }}) -> ``sing_or_type(trec, targs, :/, "Complex")``' RDL.pre(:Integer, :/) { |x| x!=0 && if (x.real.is_a?(BigDecimal)||x.imaginary.is_a?(BigDecimal)) then (if x.real.is_a?(Float) then (x.real!=Float::INFINITY && !(x.real.nan?)) elsif(x.imaginary.is_a?(Float)) then x.imaginary!=Float::INFINITY && !(x.imaginary.nan?) else true end) else true end && if (x.real.is_a?(Rational) && x.imaginary.is_a?(Float)) then !x.imaginary.nan? else true end} -RDL.type :Integer, :<, '(Integer) -> ``sing_or_type(trec, targs, :<, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<, '(Float) -> ``sing_or_type(trec, targs, :<, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<, '(Rational) -> ``sing_or_type(trec, targs, :<, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<, '(BigDecimal) -> ``sing_or_type(trec, targs, :<, "%bool")``', effect: [:+, :+] +RDL.type :Integer, :<, '(Integer) -> ``sing_or_type(trec, targs, :<, "%bool")``' +RDL.type :Integer, :<, '(Float) -> ``sing_or_type(trec, targs, :<, "%bool")``' +RDL.type :Integer, :<, '(Rational) -> ``sing_or_type(trec, targs, :<, "%bool")``' +RDL.type :Integer, :<, '(BigDecimal) -> ``sing_or_type(trec, targs, :<, "%bool")``' RDL.type :Integer, :<<, '(Integer) -> ``sing_or_type(trec, targs, :<<, "Integer")``' -RDL.type :Integer, :<=, '(Integer) -> ``sing_or_type(trec, targs, :<=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<=, '(Float) -> ``sing_or_type(trec, targs, :<=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<=, '(Rational) -> ``sing_or_type(trec, targs, :<=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<=, '(BigDecimal) -> ``sing_or_type(trec, targs, :<=, "%bool")``', effect: [:+, :+] +RDL.type :Integer, :<=, '(Integer) -> ``sing_or_type(trec, targs, :<=, "%bool")``' +RDL.type :Integer, :<=, '(Float) -> ``sing_or_type(trec, targs, :<=, "%bool")``' +RDL.type :Integer, :<=, '(Rational) -> ``sing_or_type(trec, targs, :<=, "%bool")``' +RDL.type :Integer, :<=, '(BigDecimal) -> ``sing_or_type(trec, targs, :<=, "%bool")``' RDL.type :Integer, :<=>, '(Integer) -> ``sing_or_type(trec, targs, :<=>, "Integer")``' RDL.post(:Integer, :<=>) { |r,x| r == -1 || r == 0 || r == 1 } @@ -89,15 +89,15 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :===, '(Object) -> ``sing_or_type(trec, targs, :===, "%bool")``' -RDL.type :Integer, :>, '(Integer) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>, '(Float) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>, '(Rational) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>, '(BigDecimal) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] +RDL.type :Integer, :>, '(Integer) -> ``sing_or_type(trec, targs, :>, "%bool")``' +RDL.type :Integer, :>, '(Float) -> ``sing_or_type(trec, targs, :>, "%bool")``' +RDL.type :Integer, :>, '(Rational) -> ``sing_or_type(trec, targs, :>, "%bool")``' +RDL.type :Integer, :>, '(BigDecimal) -> ``sing_or_type(trec, targs, :>, "%bool")``' -RDL.type :Integer, :>=, '(Integer) -> ``sing_or_type(trec, targs, :>=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>=, '(Float) -> ``sing_or_type(trec, targs, :>=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>=, '(Rational) -> ``sing_or_type(trec, targs, :>=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>=, '(BigDecimal) -> ``sing_or_type(trec, targs, :>=, "%bool")``', effect: [:+, :+] +RDL.type :Integer, :>=, '(Integer) -> ``sing_or_type(trec, targs, :>=, "%bool")``' +RDL.type :Integer, :>=, '(Float) -> ``sing_or_type(trec, targs, :>=, "%bool")``' +RDL.type :Integer, :>=, '(Rational) -> ``sing_or_type(trec, targs, :>=, "%bool")``' +RDL.type :Integer, :>=, '(BigDecimal) -> ``sing_or_type(trec, targs, :>=, "%bool")``' RDL.type :Integer, :>>, '(Integer) -> Integer r {{ r >= 0 }}' ## TODO @@ -239,11 +239,11 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :**, '(Complex) -> Complex' RDL.pre(:Integer, :**) { |x| x!=0 && if (x.real.is_a?(BigDecimal)||x.imaginary.is_a?(BigDecimal)) then (if x.real.is_a?(Float) then (x.real!=Float::INFINITY && !(x.real.nan?)) elsif(x.imaginary.is_a?(Float)) then x.imaginary!=Float::INFINITY && !(x.imaginary.nan?) else true end) else true end} -RDL.type :Integer, :+, '(Integer) -> Integer', effect: [:+, :+] -RDL.type :Integer, :+, '(Float) -> Float', effect: [:+, :+] -RDL.type :Integer, :+, '(Rational) -> Rational', effect: [:+, :+] -RDL.type :Integer, :+, '(BigDecimal) -> BigDecimal', effect: [:+, :+] -RDL.type :Integer, :+, '(Complex) -> Complex', effect: [:+, :+] +RDL.type :Integer, :+, '(Integer) -> Integer' +RDL.type :Integer, :+, '(Float) -> Float' +RDL.type :Integer, :+, '(Rational) -> Rational' +RDL.type :Integer, :+, '(BigDecimal) -> BigDecimal' +RDL.type :Integer, :+, '(Complex) -> Complex' RDL.type :Integer, :-, '(Integer) -> Integer' RDL.type :Integer, :-, '(Float) -> Float' @@ -283,9 +283,9 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :<=>, '(BigDecimal) -> Object' RDL.post(:Integer, :<=>) { |r,x| r == -1 || r == 0 || r == 1 } -RDL.type :Integer, :==, '(Object) -> %bool', effect: [:+, :+] +RDL.type :Integer, :==, '(Object) -> %bool' -RDL.type :Integer, :===, '(Object) -> %bool', effect: [:+, :+] +RDL.type :Integer, :===, '(Object) -> %bool' RDL.type :Integer, :>, '(Integer) -> %bool' RDL.type :Integer, :>, '(Float) -> %bool' @@ -370,7 +370,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.post(:Integer, :denominator) { |r,x| r == 1 } RDL.type :Integer, :downto, '(Integer) { (Integer) -> %any } -> Integer' RDL.type :Integer, :downto, '(Integer limit) -> Enumerator' -RDL.type :Integer, :even?, '() -> %bool', effect: [:+ ,:+] +RDL.type :Integer, :even?, '() -> %bool' RDL.type :Integer, :gcd, '(Integer) -> Integer' RDL.type :Integer, :gcdlcm, '(Integer) -> [Integer, Integer]' RDL.type :Integer, :floor, '() -> Integer' @@ -380,7 +380,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :lcm, '(Integer) -> Integer' RDL.type :Integer, :next, '() -> Integer' RDL.type :Integer, :numerator, '() -> Integer' -RDL.type :Integer, :odd?, '() -> %bool', effect: [:+, :+] +RDL.type :Integer, :odd?, '() -> %bool' RDL.type :Integer, :ord, '() -> Integer' RDL.type :Integer, :phase, '() -> %numeric' RDL.type :Integer, :pred, '() -> Integer' diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index 6ddd304a..0f9f37b9 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -62,9 +62,9 @@ RDL.type :Kernel, 'self.printf', '(?IO, ?String, *%any) -> nil' RDL.type :Kernel, :proc, '() {(*%any) -> %any} -> Proc' # TODO more precise RDL.type :Kernel, 'self.putc', '(Integer) -> Integer' -RDL.type :Kernel, 'self.puts', '(*[to_s : () -> String]) -> nil', effect: [:-,:+] -RDL.type :Kernel, 'self.raise', '() -> %bot', effect: [:+, :+] -RDL.type :Kernel, 'raise', '() -> %bot', effect: [:+, :+] +RDL.type :Kernel, 'self.puts', '(*[to_s : () -> String]) -> nil' +RDL.type :Kernel, 'self.raise', '() -> %bot' +RDL.type :Kernel, 'raise', '() -> %bot' # RDL.type :Kernel, 'self.raise', '(String or [exception : () -> String], ?String, ?Array) -> %any' # TODO: above same as fail? RDL.type :Kernel, 'self.rand', '(Integer or Range max) -> Integer' @@ -91,7 +91,7 @@ # RDL.type :Kernel, 'self.trap' # TODO # RDL.type :Kernel, 'self.untrace_var' # TODO RDL.type :Kernel, 'self.warn', '(*String msg) -> nil' -RDL.type :Kernel, :clone, '() -> self', effect: [:~, :+] +RDL.type :Kernel, :clone, '() -> self' RDL.type :Kernel, :raise, '() -> %bot' RDL.type :Kernel, :raise, '(String) -> %bot' RDL.type :Kernel, :raise, '(Class, ?String, ?Array) -> %bot' diff --git a/lib/types/core/module.rb b/lib/types/core/module.rb index 87d0ce5f..c480b0b7 100644 --- a/lib/types/core/module.rb +++ b/lib/types/core/module.rb @@ -54,7 +54,7 @@ RDL.type :Module, :public_method_defined?, '(Symbol or String) -> %bool' RDL.type :Module, :remove_class_variable, '(Symbol) -> %any' RDL.type :Module, :singleton_class?, '() -> %bool' -RDL.type :Module, :to_s, '() -> String', effect: [:+, :+] +RDL.type :Module, :to_s, '() -> String' # private methods below here RDL.type :Module, :alias_method, '(Symbol or String new_name, Symbol or String old_name) -> self' RDL.type :Module, :append_features, '(Module) -> self' diff --git a/lib/types/core/object.rb b/lib/types/core/object.rb index 0085374b..38117c91 100644 --- a/lib/types/core/object.rb +++ b/lib/types/core/object.rb @@ -24,11 +24,11 @@ RDL.type :Object, :!~, '(%any other) -> %bool', wrap: false RDL.type :Object, :<=>, '(%any other) -> Integer or nil', wrap: false -RDL.type :Object, :===, '(%any other) -> %bool', wrap: false, effect: [:+, :+] -RDL.type :Object, :==, '(%any other) -> %bool', wrap: false, effect: [:+, :+] +RDL.type :Object, :===, '(%any other) -> %bool', wrap: false +RDL.type :Object, :==, '(%any other) -> %bool', wrap: false RDL.type :Object, :=~, '(%any other) -> nil', wrap: false RDL.type :Object, :class, '() -> Class', wrap: false -RDL.type :Object, :clone, '() -> self', wrap: false, effect: [:+, :+] +RDL.type :Object, :clone, '() -> self', wrap: false # RDL.type :Object, :define_singleton_method, '(XXXX : *XXXX)') # TODO RDL.type :Object, :display, '(IO port) -> nil', wrap: false RDL.type :Object, :dup, '() -> self an_object', wrap: false @@ -45,11 +45,11 @@ RDL.type :Object, :instance_variable_get, '(Symbol or String) -> %any', wrap: false RDL.type :Object, :instance_variable_set, '(Symbol or String, %any) -> %any', wrap: false # returns 2nd argument RDL.type :Object, :instance_variables, '() -> Array', wrap: false -RDL.type :Object, :is_a?, '(Class or Module) -> %bool', wrap: false, effect: [:+, :+] +RDL.type :Object, :is_a?, '(Class or Module) -> %bool', wrap: false RDL.type :Object, :kind_of?, '(Class) -> %bool', wrap: false RDL.type :Object, :method, '(Symbol) -> Method', wrap: false RDL.type :Object, :methods, '(?%bool regular) -> Array', wrap: false -RDL.type :Object, :nil?, '() -> %bool', wrap: false, effect: [:+, :+] +RDL.type :Object, :nil?, '() -> %bool', wrap: false RDL.type :Object, :private_methods, '(?%bool all) -> Array', wrap: false RDL.type :Object, :protected_methods, '(?%bool all) -> Array', wrap: false RDL.type :Object, :public_method, '(Symbol) -> Method', wrap: false @@ -57,7 +57,7 @@ RDL.type :Object, :public_send, '(Symbol or String, *%any args) -> %any', wrap: false RDL.type :Object, :remove_instance_variable, '(Symbol) -> %any', wrap: false # RDL.type :Object, :respond_to?, '(Symbol or String, ?%bool include_all) -> %bool' -RDL.type :Object, :send, '(Symbol or String, *%any) -> Object', wrap: false, effect: [:-, :+] # Can't wrap this, used outside wrap switch +RDL.type :Object, :send, '(Symbol or String, *%any) -> Object', wrap: false RDL.type :Object, :singleton_class, '() -> Class', wrap: false RDL.type :Object, :singleton_method, '(Symbol) -> Method', wrap: false RDL.type :Object, :singleton_methods, '(?%bool all) -> Array', wrap: false diff --git a/lib/types/core/string.rb b/lib/types/core/string.rb index 3ca446f6..548eac60 100644 --- a/lib/types/core/string.rb +++ b/lib/types/core/string.rb @@ -28,7 +28,7 @@ def String.output_type(trec, targs, meth, type) end end -RDL.type String, 'self.output_type', "(RDL::Type::Type, Array, Symbol, String) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.output_type', "(RDL::Type::Type, Array, Symbol, String) -> RDL::Type::Type" def String.to_type(v) case v @@ -45,7 +45,7 @@ def String.to_type(v) end end -RDL.type String, 'self.to_type', "(%any) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.to_type', "(%any) -> RDL::Type::Type" def String.any_string(a) case a @@ -56,7 +56,7 @@ def String.any_string(a) end end -RDL.type String, 'self.any_string', "(%any) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.any_string', "(%any) -> RDL::Type::Type" def String.string_promote!(trec) case trec @@ -68,7 +68,7 @@ def String.string_promote!(trec) end end -RDL.type String, 'self.string_promote!', "(%any) -> RDL::Type::Type", effect: [:~, :+] +RDL.type String, 'self.string_promote!', "(%any) -> RDL::Type::Type" RDL.type :String, :initialize, '(?String str) -> self new_str' @@ -83,7 +83,7 @@ def String.plus_output(trec, targs) end end -RDL.type String, 'self.plus_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.plus_output', "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL.type :String, :+, '(``any_string(targs[0])``) -> ``plus_output(trec, targs)``' @@ -108,17 +108,17 @@ def String.append_output(trec, targs) end end -RDL.type String, 'self.append_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.append_output', "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL.type :String, :<=>, '(String) -> ``output_type(trec, targs, :<=>, "Integer")``' -RDL.type :String, :==, '(%any) -> ``output_type(trec, targs, :==, "%bool")``', effect: [:+, :+] +RDL.type :String, :==, '(%any) -> ``output_type(trec, targs, :==, "%bool")``' RDL.type :String, :===, '(%any) -> ``output_type(trec, targs, :===, "%bool")``' RDL.type :String, :=~, '(Object) -> ``output_type(trec, targs, :=~, "Integer")``', wrap: false # Wrapping this messes up $1 etc -RDL.type :String, :[], '(Integer, ?Integer) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] -RDL.type :String, :[], '(Range or Regexp) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] -RDL.type :String, :[], '(Regexp, Integer) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] -RDL.type :String, :[], '(Regexp, String) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] -RDL.type :String, :[], '(String) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] +RDL.type :String, :[], '(Integer, ?Integer) -> ``output_type(trec, targs, :[], "String")``' +RDL.type :String, :[], '(Range or Regexp) -> ``output_type(trec, targs, :[], "String")``' +RDL.type :String, :[], '(Regexp, Integer) -> ``output_type(trec, targs, :[], "String")``' +RDL.type :String, :[], '(Regexp, String) -> ``output_type(trec, targs, :[], "String")``' +RDL.type :String, :[], '(String) -> ``output_type(trec, targs, :[], "String")``' RDL.type :String, :ascii_only?, '() -> ``output_type(trec, targs, :ascii_only?, "%bool")``' RDL.type :String, :b, '() -> ``output_type(trec, targs, :b, "String")``' RDL.type :String, :bytes, '() -> ``output_type(trec, targs, :bytes, "Array")``' @@ -138,7 +138,7 @@ def String.cap_down_output(trec, meth) end end -RDL.type String, 'self.cap_down_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.cap_down_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type" RDL.type :String, :casecmp, '(String) -> ``output_type(trec, targs, :casecmp, "Integer")``' RDL.type :String, :center, '(Integer, ?String) -> ``output_type(trec, targs, :center, "String")``' @@ -164,7 +164,7 @@ def String.chop_output(trec) end end -RDL.type String, 'self.chop_output', "(RDL::Type::Type) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.chop_output', "(RDL::Type::Type) -> RDL::Type::Type" RDL.type :String, :chr, '() -> ``output_type(trec, targs, :chr, "String")``' RDL.type :String, :clear, '() -> ``clear_output(trec)``' @@ -180,7 +180,7 @@ def String.clear_output(trec) end end -RDL.type String, 'self.clear_output', "(RDL::Type::Type) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.clear_output', "(RDL::Type::Type) -> RDL::Type::Type" RDL.type :String, :codepoints, '() -> ``output_type(trec, targs, :codepoints, "Array")``' RDL.type :String, :concat, '(Integer or Object) -> ``append_output(trec, targs)``' @@ -221,7 +221,7 @@ def String.clear_output(trec) RDL.type :String, :gsub!, '(Regexp or String) -> ``string_promote!(trec); RDL::Type::NominalType.new(Enumerator)``', wrap: false RDL.type :String, :hash, '() -> Integer' RDL.type :String, :hex, '() -> ``output_type(trec, targs, :getbyte, "Integer")``' -RDL.type :String, :include?, '(String) -> ``output_type(trec, targs, :include?, "%bool")``', effect: [:+, :+] +RDL.type :String, :include?, '(String) -> ``output_type(trec, targs, :include?, "%bool")``' RDL.type :String, :index, '(Regexp or String, ?Integer) -> ``output_type(trec, targs, :index, "Integer")``' RDL.type :String, :replace, '(String) -> ``replace_output(trec, targs)``' @@ -242,7 +242,7 @@ def String.replace_output(trec, targs) end end -RDL.type String, 'self.replace_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.replace_output', "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL.type :String, :insert, '(Integer, String) -> String' ## TODO @@ -265,7 +265,7 @@ def String.insert_output(trec, targs) end end -RDL.type String, 'self.insert_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.insert_output', "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL.type :String, :inspect, '() -> ``output_type(trec, targs, :inspect, "String")``' RDL.type :String, :intern, '() -> ``output_type(trec, targs, :intern, "Symbol")``' @@ -296,7 +296,7 @@ def String.lrstrip_output(trec, meth) end end -RDL.type String, 'self.lrstrip_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.lrstrip_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type" RDL.type :String, :match, '(Regexp or String) -> MatchData' RDL.type :String, :match, '(Regexp or String, Integer) -> MatchData' @@ -319,7 +319,7 @@ def String.mutate_output(trec, meth) end end -RDL.type String, 'self.mutate_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.mutate_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type" RDL.type :String, :oct, '() -> ``output_type(trec, targs, :oct, "Integer")``' @@ -348,11 +348,11 @@ def String.mutate_output(trec, meth) RDL.type :String, :slice!, '(Regexp, Integer) -> ``string_promote!(trec)``' RDL.type :String, :slice!, '(Regexp, String) -> ``string_promote!(trec)``' RDL.type :String, :slice!, '(String) -> ``string_promote!(trec)``' -RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> ``output_type(trec, targs, :split, "Array")``', effect: [:+, :+] -RDL.type :String, :split, '(?Integer) -> ``output_type(trec, targs, :split, "Array")``', effect: [:+, :+] +RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> ``output_type(trec, targs, :split, "Array")``' +RDL.type :String, :split, '(?Integer) -> ``output_type(trec, targs, :split, "Array")``' RDL.type :String, :squeeze, '() -> ``output_type(trec, targs, :squeeze, "String")``' RDL.type :String, :squeeze!, '() -> ``mutate_output(trec, :squeeze!)``' -RDL.type :String, :start_with?, '(* String) -> ``output_type(trec, targs, :start_with?, "%bool")``', effect: [:+, :+] +RDL.type :String, :start_with?, '(* String) -> ``output_type(trec, targs, :start_with?, "%bool")``' RDL.type :String, :strip, '() -> ``output_type(trec, targs, :strip, "String")``' RDL.type :String, :strip!, '() -> ``mutate_output(trec, :strip!)``' RDL.type :String, :sub, '(Regexp or String, String or Hash) -> ``output_type(trec, targs, :sub, "String")``', wrap: false # Can't wrap these, since they mess with $1 etc @@ -367,9 +367,9 @@ def String.mutate_output(trec, meth) RDL.type :String, :to_f, '() -> ``output_type(trec, targs, :to_f, "Float")``' RDL.type :String, :to_i, '(?Integer) -> ``output_type(trec, targs, :to_i, "Integer")``' RDL.type :String, :to_r, '() -> Rational' -RDL.type :String, :to_s, '() -> self', effect: [:+, :+] +RDL.type :String, :to_s, '() -> self' RDL.type :String, :to_str, '() -> self' -RDL.type :String, :to_sym, '() -> ``output_type(trec, targs, :to_sym, "Symbol")``', effect: [:+, :+] +RDL.type :String, :to_sym, '() -> ``output_type(trec, targs, :to_sym, "Symbol")``' RDL.type :String, :tr, '(String, String) -> ``output_type(trec, targs, :tr, "String")``' RDL.type :String, :tr!, '(String, String) -> ``string_promote!(trec)``' RDL.type :String, :tr_s, '(String, String) -> ``output_type(trec, targs, :tr_s, "String")``' @@ -405,14 +405,14 @@ def String.mutate_output(trec, meth) RDL.type :String, :+, '(String) -> String' RDL.type :String, :<<, '(Object) -> String' RDL.type :String, :<=>, '(String other) -> Integer or nil ret' -RDL.type :String, :==, '(%any) -> %bool', effect: [:+, :+] +RDL.type :String, :==, '(%any) -> %bool' RDL.type :String, :===, '(%any) -> %bool' RDL.type :String, :=~, '(Object) -> Integer or nil', wrap: false # Wrapping this messes up $1 etc -RDL.type :String, :[], '(Integer, ?Integer) -> String or nil', effect: [:+, :+] -RDL.type :String, :[], '(Range or Regexp) -> String or nil', effect: [:+, :+] -RDL.type :String, :[], '(Regexp, Integer) -> String or nil', effect: [:+, :+] -RDL.type :String, :[], '(Regexp, String) -> String or nil', effect: [:+, :+] -RDL.type :String, :[], '(String) -> String or nil', effect: [:+, :+] +RDL.type :String, :[], '(Integer, ?Integer) -> String or nil' +RDL.type :String, :[], '(Range or Regexp) -> String or nil' +RDL.type :String, :[], '(Regexp, Integer) -> String or nil' +RDL.type :String, :[], '(Regexp, String) -> String or nil' +RDL.type :String, :[], '(String) -> String or nil' RDL.type :String, :ascii_only?, '() -> %bool' RDL.type :String, :b, '() -> String' RDL.type :String, :bytes, '() -> Array' # TODO: bindings to parameterized (vars) @@ -466,7 +466,7 @@ def String.mutate_output(trec, meth) RDL.type :String, :gsub!, '(Regexp or String) -> Enumerator', wrap: false RDL.type :String, :hash, '() -> Integer' RDL.type :String, :hex, '() -> Integer' -RDL.type :String, :include?, '(String) -> %bool', effect: [:+, :+] +RDL.type :String, :include?, '(String) -> %bool' RDL.type :String, :index, '(Regexp or String, ?Integer) -> Integer or nil' RDL.type :String, :replace, '(String) -> String' RDL.type :String, :insert, '(Integer, String) -> String' @@ -504,11 +504,11 @@ def String.mutate_output(trec, meth) RDL.type :String, :slice!, '(Regexp, Integer) -> String or nil' RDL.type :String, :slice!, '(Regexp, String) -> String or nil' RDL.type :String, :slice!, '(String) -> String or nil' -RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> Array', effect: [:+, :+] -RDL.type :String, :split, '(?Integer) -> Array', effect: [:+, :+] +RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> Array' +RDL.type :String, :split, '(?Integer) -> Array' RDL.type :String, :squeeze, '(?String) -> String' RDL.type :String, :squeeze!, '(?String) -> String' -RDL.type :String, :start_with?, '(* String) -> %bool', effect: [:+, :+] +RDL.type :String, :start_with?, '(* String) -> %bool' RDL.type :String, :strip, '() -> String' RDL.type :String, :strip!, '() -> String' RDL.type :String, :sub, '(Regexp or String, String or Hash) -> String', wrap: false # Can't wrap these, since they mess with $1 etc @@ -523,9 +523,9 @@ def String.mutate_output(trec, meth) RDL.type :String, :to_f, '() -> Float' RDL.type :String, :to_i, '(?Integer) -> Integer' RDL.type :String, :to_r, '() -> Rational' -RDL.type :String, :to_s, '() -> String', effect: [:+, :+] +RDL.type :String, :to_s, '() -> String' RDL.type :String, :to_str, '() -> self' -RDL.type :String, :to_sym, '() -> Symbol', effect: [:+, :+] +RDL.type :String, :to_sym, '() -> Symbol' RDL.type :String, :tr, '(String, String) -> String' RDL.type :String, :tr!, '(String, String) -> String or nil' RDL.type :String, :tr_s, '(String, String) -> String' diff --git a/lib/types/core/symbol.rb b/lib/types/core/symbol.rb index 6cbf3b09..461c4296 100644 --- a/lib/types/core/symbol.rb +++ b/lib/types/core/symbol.rb @@ -2,7 +2,7 @@ RDL.type :Symbol, 'self.all_symbols', '() -> Array' RDL.type :Symbol, :<=>, '(Symbol other) -> Integer or nil' -RDL.type :Symbol, :==, '(Object) -> %bool', effect: [:+, :+] +RDL.type :Symbol, :==, '(Object) -> %bool' RDL.type :Symbol, :=~, '(Object) -> Integer or nil' RDL.type :Symbol, :[], '(Integer idx) -> String' RDL.type :Symbol, :[], '(Integer b, Integer n) -> String' @@ -22,6 +22,6 @@ RDL.rdl_alias :Symbol, :slice, :[] RDL.type :Symbol, :swapcase, '() -> Symbol' RDL.type :Symbol, :to_proc, '() -> Proc' # TODO proc -RDL.type :Symbol, :to_s, "() -> String", effect: [:+, :+] -RDL.type :Symbol, :to_sym, "() -> self", effect: [:+, :+] +RDL.type :Symbol, :to_s, "() -> String" +RDL.type :Symbol, :to_sym, "() -> self" RDL.type :Symbol, :upcase, '() -> Symbol' diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index 67143ea1..a325613d 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -17,11 +17,11 @@ class ActiveRecord::Base type :save!, '(?{ validate: %bool }) -> %bool', wrap: false type 'self.transaction', '() {() -> u} -> u', wrap: false - type RDL::Globals, 'self.ar_db_schema', "() -> Hash<%any, RDL::Type::GenericType>", wrap: false, effect: [:+, :+] - type String, :singularize, "() -> String", wrap: false, effect: [:+, :+] - type String, :camelize, "() -> String", wrap: false, effect: [:+, :+] - type String, :pluralize, "() -> String", wrap: false, effect: [:+, :+] - type String, :underscore, "() -> String", wrap: false, effect: [:+, :+] + type RDL::Globals, 'self.ar_db_schema', "() -> Hash<%any, RDL::Type::GenericType>", wrap: false + type String, :singularize, "() -> String", wrap: false + type String, :camelize, "() -> String", wrap: false + type String, :pluralize, "() -> String", wrap: false + type String, :underscore, "() -> String", wrap: false type Object, :try, "(Symbol) -> ``try_output(trec, targs)``", wrap: false @@ -71,7 +71,7 @@ def self.access_output(trec, targs) raise 'unexpected type' end end - RDL.type ActiveRecord::Base, 'self.access_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type ActiveRecord::Base, 'self.access_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.uc_first_arg(trec) case trec @@ -85,7 +85,7 @@ def self.uc_first_arg(trec) raise "unexpected type" end end - RDL.type ActiveRecord::Base, 'self.uc_first_arg', "(RDL::Type::Type) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type ActiveRecord::Base, 'self.uc_first_arg', "(RDL::Type::Type) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code def self.uc_second_arg(trec, targs) case trec @@ -99,7 +99,7 @@ def self.uc_second_arg(trec, targs) raise "unexpected type" end end - RDL.type ActiveRecord::Base, 'self.uc_second_arg', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type ActiveRecord::Base, 'self.uc_second_arg', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code end @@ -344,12 +344,12 @@ def self.rec_to_nominal(t) end end end - RDL.type DBType, 'self.rec_to_nominal', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.rec_to_nominal', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.rec_to_array(trec) RDL::Type::GenericType.new(RDL::Globals.types[:array], rec_to_nominal(trec)) end - RDL.type DBType, 'self.rec_to_array', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.rec_to_array', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code ## given a receiver type in various kinds of query calls, returns the accepted finite hash type input, ## or a union of types if the receiver represents joined tables. @@ -405,7 +405,7 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false, include_assocs: raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}." end end - RDL.type DBType, 'self.rec_to_schema_type', "(RDL::Type::Type, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.rec_to_schema_type', "(RDL::Type::Type, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code ## turns a given table name into the appropriate finite hash type based on table schema, with optional or top-type values ## [+ tname +] is the table name as a symbol @@ -445,15 +445,15 @@ def self.table_name_to_schema_type(tname, check_col, takes_array=false, include_ end RDL::Type::FiniteHashType.new(RDL.type_cast(h, "Hash<%any, RDL::Type::Type>", force: true), nil) end - RDL.type DBType, 'self.table_name_to_schema_type', "(Symbol, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.table_name_to_schema_type', "(Symbol, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code def self.where_input_type(trec, targs) handle_sql_strings(trec, targs) if targs[0].is_a?(RDL::Type::PreciseStringType) && !targs[1].nil? && !targs[1].kind_of_var_input? tschema = rec_to_schema_type(trec, true, true) return RDL::Type::UnionType.new(tschema, RDL::Globals.types[:string], RDL::Globals.types[:array]) ## no indepth checking for string or array cases end - RDL.type Object, 'self.handle_sql_strings', "(RDL::Type::Type, Array) -> %any", wrap: false, effect: [:+, :+] - RDL.type DBType, 'self.where_input_type', "(RDL::Type::Type, Array) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type Object, 'self.handle_sql_strings', "(RDL::Type::Type, Array) -> %any", wrap: false + RDL.type DBType, 'self.where_input_type', "(RDL::Type::Type, Array) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code def self.find_input_type(trec, targs) handle_sql_strings(trec, targs) if targs[0].is_a? RDL::Type::PreciseStringType @@ -480,13 +480,13 @@ def self.where_noarg_output_type(trec) raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}." end end - RDL.type DBType, 'self.where_noarg_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.where_noarg_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code def self.not_input_type(trec, targs) tschema = rec_to_schema_type(trec, true) return RDL::Type::UnionType.new(tschema, RDL::Globals.types[:string], RDL::Globals.types[:array]) ## no indepth checking for string or array cases end - RDL.type DBType, 'self.not_input_type', "(RDL::Type::Type, Array) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.not_input_type', "(RDL::Type::Type, Array) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code def self.exists_input_type(trec, targs) raise "Unexpected number of arguments to ActiveRecord::Base#exists?." unless targs.size <= 1 @@ -500,7 +500,7 @@ def self.exists_input_type(trec, targs) end return RDL::Type::OptionalType.new(RDL::Type::UnionType.new(RDL::Globals.types[:integer], RDL::Globals.types[:string], typ)) end - RDL.type DBType, 'self.exists_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.exists_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.find_output_type(trec, targs) @@ -536,7 +536,7 @@ def self.find_output_type(trec, targs) DBType.rec_to_nominal(trec) end end - RDL.type DBType, 'self.find_output_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.find_output_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.joins_one_input_type(trec, targs) return RDL::Globals.types[:top] unless targs.size == 1 ## trivial case, won't be matched @@ -578,7 +578,7 @@ def self.joins_one_input_type(trec, targs) raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to joins." end end - RDL.type DBType, 'self.joins_one_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.joins_one_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.joins_multi_input_type(trec, targs) return RDL::Globals.types[:top] unless targs.size > 1 ## trivial case, won't be matched @@ -587,7 +587,7 @@ def self.joins_multi_input_type(trec, targs) } return targs[0] ## since this method is called as first argument in type end - RDL.type DBType, 'self.joins_multi_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.joins_multi_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.associated_with?(rec, sym) tschema = RDL::Globals.ar_db_schema[rec.to_s.to_sym] @@ -611,7 +611,7 @@ def self.associated_with?(rec, sym) } return false end - RDL.type DBType, 'self.associated_with?', "(Class or Symbol or String, Symbol) -> %bool", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.associated_with?', "(Class or Symbol or String, Symbol) -> %bool", wrap: false, typecheck: :type_code def self.get_joined_args(targs) arg_types = RDL.type_cast([], "Array", force: true) @@ -640,7 +640,7 @@ def self.get_joined_args(targs) raise "oops, didn't expect to get here." end end - RDL.type DBType, 'self.get_joined_args', "(Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.get_joined_args', "(Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.joins_output(trec, targs) arg_type = get_joined_args(targs) @@ -666,7 +666,7 @@ def self.joins_output(trec, targs) ret = RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), jt) return ret end - RDL.type DBType, 'self.joins_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.joins_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.plus_output_type(trec, targs) typs = RDL.type_cast([], "Array", force: true) @@ -693,7 +693,7 @@ def self.plus_output_type(trec, targs) } RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), RDL::Type::UnionType.new(*typs)) end - RDL.type DBType, 'self.plus_output_type', "(RDL::Type::Type, Array) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.plus_output_type', "(RDL::Type::Type, Array) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code def self.count_input(trec, targs) hash_type = rec_to_schema_type(trec, true)## Bug found here. orginally had: rec_to_schema_type(trec, targs).elts @@ -710,7 +710,7 @@ def self.count_input(trec, targs) } return RDL::Type::OptionalType.new(RDL::Type::UnionType.new(*typs)) end - RDL.type DBType, 'self.count_input', "(RDL::Type::Type, Array) -> RDL::Type::OptionalType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.count_input', "(RDL::Type::Type, Array) -> RDL::Type::OptionalType", wrap: false, typecheck: :type_code def self.each_block_arg(trec) diff --git a/lib/types/sequel/comp_types.rb b/lib/types/sequel/comp_types.rb index 9d683e43..6c84a98e 100644 --- a/lib/types/sequel/comp_types.rb +++ b/lib/types/sequel/comp_types.rb @@ -6,7 +6,7 @@ class SequelDB type :transaction, "() { () -> %any } -> self", wrap: false - type RDL::Globals, 'self.seq_db_schema', "()-> Hash", wrap: false, effect: [:+, :+] + type RDL::Globals, 'self.seq_db_schema', "()-> Hash", wrap: false def self.gen_output_type(targ) case targ @@ -21,7 +21,7 @@ def self.gen_output_type(targ) end end - RDL.type SequelDB, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type SequelDB, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false end module Sequel::Mysql2; end @@ -36,7 +36,7 @@ class Sequel::Mysql2::Database type :transaction, "() { () -> %any } -> self", wrap: false - type RDL::Globals, 'self.seq_db_schema', "()-> Hash", wrap: false, effect: [:+, :+] + type RDL::Globals, 'self.seq_db_schema', "()-> Hash", wrap: false def self.gen_output_type(targ) case targ @@ -50,7 +50,7 @@ def self.gen_output_type(targ) raise "unexpected type #{targ.class}" end end - RDL.type Sequel::Mysql2::Database, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Sequel::Mysql2::Database, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false end @@ -71,13 +71,13 @@ def self.gen_output_type(targ) raise "unexpected type" end end - RDL.type Sequel, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Sequel, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false def self.qualify_output_type(targs) raise "unexpected types" unless targs.all? { |a| a.is_a?(RDL::Type::SingletonType) } RDL::Type::GenericType.new(RDL::Type::NominalType.new(SeqQualIdent), targs[0], targs[1]) end - RDL.type Sequel, 'self.qualify_output_type', "(Array) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Sequel, 'self.qualify_output_type', "(Array) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false end class SeqIdent extend RDL::Annotate @@ -98,7 +98,7 @@ def self.gen_output_type(trec, targ) raise "unexpected trec type" end end - RDL.type SeqIdent, 'self.gen_output_type', "(RDL::Type::Type, RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type SeqIdent, 'self.gen_output_type', "(RDL::Type::Type, RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false end @@ -122,7 +122,7 @@ class Table def self.get_schema(hash) hash.select { |key, val| ![:__last_joined, :__all_joined, :__selected, :__orm].member?(key) } end - RDL.type Table, 'self.get_schema', "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_schema', "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", typecheck: :type_code, wrap: false def self.get_all_joined(t) case t @@ -142,7 +142,7 @@ def self.get_all_joined(t) raise "unexpected type #{t} in __all_joined clause" end end - RDL.type Table, 'self.get_all_joined', "(RDL::Type::Type) -> Array", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_all_joined', "(RDL::Type::Type) -> Array", typecheck: :type_code, wrap: false def self.join_ret_type(trec, targs) raise RDL::Typecheck::StaticTypeError, "Unexpected number of arguments to `join`." unless targs.size == 2 @@ -202,7 +202,7 @@ def self.join_ret_type(trec, targs) raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type in call to `join`." end end - RDL.type Table, 'self.join_ret_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.join_ret_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false type :insert, "(``insert_arg_type(trec, targs)``) -> Integer", wrap: false @@ -254,13 +254,13 @@ def self.order_input(trec, targs) raise "unexpected type #{trec}" end end - RDL.type Table, 'self.order_input', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.order_input', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.map_block_input(trec) schema = get_schema(RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts) RDL::Type::FiniteHashType.new(schema, nil) end - RDL.type Table, 'self.map_block_input', "(RDL::Type::GenericType) -> RDL::Type::FiniteHashType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.map_block_input', "(RDL::Type::GenericType) -> RDL::Type::FiniteHashType", typecheck: :type_code, wrap: false def self.all_output(trec) f = first_output(trec) @@ -291,7 +291,7 @@ def self.all_output(trec) return RDL::Type::GenericType.new(RDL::Globals.types[:array], f) end end - RDL.type Table, 'self.all_output', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.all_output', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false def self.select_map_output(trec, targs, meth) raise "VarargType not supported for select_map." if meth == :select_map && targs[0].is_a?(RDL::Type::VarargType) @@ -357,7 +357,7 @@ def self.select_map_output(trec, targs, meth) raise 'unexpected type #{trec}' end end - RDL.type Table, 'self.select_map_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.select_map_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false def self.get_input(trec) case trec @@ -368,12 +368,12 @@ def self.get_input(trec) raise 'unexpected type #{trec}' end end - RDL.type Table, 'self.get_input', "(RDL::Type::Type) -> RDL::Type::UnionType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_input', "(RDL::Type::Type) -> RDL::Type::UnionType", typecheck: :type_code, wrap: false def self.get_output(trec, targs) RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[RDL.type_cast(targs[0], "RDL::Type::SingletonType", force: true).val] end - RDL.type Table, 'self.get_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.first_output(trec) case trec @@ -389,7 +389,7 @@ def self.first_output(trec) raise 'unexpected type #{trec}' end end - RDL.type Table, 'self.first_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.first_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.insert_arg_type(trec, targs, tuple=false) raise "Cannot insert/update for joined table." if RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[:__all_joined].is_a?(RDL::Type::UnionType) @@ -399,7 +399,7 @@ def self.insert_arg_type(trec, targs, tuple=false) schema_arg_type(trec, targs, :insert) end end - RDL.type Table, 'self.insert_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.insert_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.import_arg_type(trec, targs) raise "Cannot import for joined table." if RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[:__all_joined].is_a?(RDL::Type::UnionType) @@ -419,7 +419,7 @@ def self.import_arg_type(trec, targs) end return targs[0] end - RDL.type Table, 'self.import_arg_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.import_arg_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.get_nominal_where_type(type) ## `where` can accept arrays/tuples and tables with a single column selected @@ -449,7 +449,7 @@ def self.get_nominal_where_type(type) end end - RDL.type Table, 'self.get_nominal_where_type', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_nominal_where_type', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.schema_arg_type(trec, targs, meth) return RDL::Type::NominalType.new(Hash) if targs.size != 1 @@ -495,7 +495,7 @@ def self.schema_arg_type(trec, targs, meth) end end - RDL.type Table, 'self.schema_arg_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.schema_arg_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false ## [+ column_name +] is a symbol or SeqQualIdent Generic type of the qualified column name, e.g. :person__age ## [+ all_joined +] is an array of symbols of joined tables (must check if qualifying table is a member) @@ -526,7 +526,7 @@ def self.check_qual_column(column_name, all_joined, type=nil) end return qual_table_schema[qual_column] end - RDL.type Table, 'self.check_qual_column', "(Symbol or RDL::Type::GenericType, Array, ?RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.check_qual_column', "(Symbol or RDL::Type::GenericType, Array, ?RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.schema_arg_tuple_type(trec, targs, meth) return RDL::Type::NominalType.new(Array) if targs.size != 2 @@ -584,7 +584,7 @@ def self.schema_arg_tuple_type(trec, targs, meth) raise 'not yet implemented' end end - RDL.type Table, 'self.schema_arg_tuple_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.schema_arg_tuple_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.where_arg_type(trec, targs, tuple=false) return targs[0] if targs[0] == RDL::Globals.types[:string] @@ -616,7 +616,7 @@ def self.where_arg_type(trec, targs, tuple=false) return ret end end - RDL.type Table, 'self.where_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.where_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.unique_ids?(ids, joined) j = get_all_joined(joined) @@ -634,6 +634,6 @@ def self.unique_ids?(ids, joined) } return true end - RDL.type Table, 'self.unique_ids?', "(Array, RDL::Type::Type) -> %bool", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.unique_ids?', "(Array, RDL::Type::Type) -> %bool", typecheck: :type_code, wrap: false end From d516022099e53cd77dca0ac4c91de391619732e8 Mon Sep 17 00:00:00 2001 From: mckaz Date: Mon, 11 May 2020 22:20:52 -0400 Subject: [PATCH 056/124] remove effects across RDL (#100) --- lib/rdl/typecheck.rb | 504 +++++++------------- lib/rdl/types/rdl_types.rb | 66 +-- lib/rdl/types/type.rb | 8 +- lib/rdl/wrap.rb | 10 +- lib/types/core/array.rb | 72 +-- lib/types/core/basic_object.rb | 6 +- lib/types/core/class.rb | 4 +- lib/types/core/enumerable.rb | 10 +- lib/types/core/hash.rb | 74 +-- lib/types/core/integer.rb | 52 +- lib/types/core/kernel.rb | 8 +- lib/types/core/module.rb | 2 +- lib/types/core/object.rb | 12 +- lib/types/core/string.rb | 74 +-- lib/types/core/symbol.rb | 6 +- lib/types/rails/active_record/comp_types.rb | 50 +- lib/types/sequel/comp_types.rb | 50 +- 17 files changed, 428 insertions(+), 580 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 7f21e142..4ff7aef0 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -330,18 +330,12 @@ def self._infer(klass, meth) RDL::Logging.log :inference, :debug, "Done with constraint generation." end - def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) + def self.typecheck(klass, meth, ast=nil, types = nil) # puts "Typechecking #{klass}##{meth}" ast = get_ast(klass, meth) unless ast raise RuntimeError, "Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}" if ast.nil? types = RDL::Globals.info.get(klass, meth, :type) unless types - effects = RDL::Globals.info.get(klass, meth, :effect) unless effects - if effects.empty? || effects[0] == nil - effect = nil - else - effect = [:+, :+] - effects.each { |e| effect = effect_union(effect, e) unless e.nil? } ## being very lazy about this right now, conservatively taking the union of all effects if there are multiple ones - end + raise RuntimeError, "Can't typecheck method with no types?!" if types.nil? or types == [] if ast.type == :def @@ -367,7 +361,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) raise RuntimeError, "Type checking of methods with computed types is not currently supported." unless (type.args + [type.ret]).all? { |t| !t.instance_of?(RDL::Type::ComputedType) } inst = {self: self_type} type = type.instantiate inst - scope = { task: :check, klass: klass, meth: meth, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types, eff: effect } + scope = { task: :check, klass: klass, meth: meth, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types } # default args seem to be evaluated in the method body, so same scope _, targs = args_hash(scope, Env.new(:self => self_type), type, args, ast, 'method') targs[:self] = self_type @@ -378,13 +372,13 @@ def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil) else targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this @num_casts = 0 - _, body_type, body_effect = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) + _, body_type = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) end old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured]) end until old_captured == scope[:captured] error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || meth == :initialize ||RDL::Type::Type.leq(body_type, type.ret, ast: ast) - error :bad_effect, [body_effect, effect], body unless body.nil? || effect.nil? || effect_leq(body_effect, effect) } + if RDL::Config.instance.check_comp_types new_meth = WrapCall.rewrite(ast) # rewrite ast to insert dynamic checks RDL::Util.silent_warnings { RDL::Util.to_class(klass).class_eval(new_meth) } # redefine method in the same class @@ -396,45 +390,6 @@ def self.get_num_casts return @num_casts end - def self.effect_leq(e1, e2) - raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) } - p1, t1 = e1 - p2, t2 = e2 - case p1 #:+ always okay - when :~ - return false if p2 == :+ - when :- - return false if p2 == :+# || p2 == :~ going to treat this as okay, like a type cast - end - case t1 #:+ always okay - when :- - return false if t2 == :+ - end - return true - end - - def self.effect_union(e1, e2) - raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) }#{ |e| e.is_a?(Symbol) } - p1, t1 = e1 - p2, t2 = e2 - pret = tret = nil - case p1 - when :+ - pret = p2 - when :~ - if p2 == :- then pret = :- else pret = :~ end - else - pret = :- - end - case t1 - when :+ - tret = t2 - else - tret = :- - end - [pret, tret] - end - ### TODO: clean up below. Should probably incorporate it into `targs.merge` call in `self.typecheck`. def self.widen_scopes(h1, h2) h1new = h1.nil? ? nil : {} @@ -604,36 +559,34 @@ def self.tc(scope, env, e) # [+ scope +] tracks flow-insensitive information about the current scope, excluding local variables # [+ env +] is the (local variable) Env # [+ e +] is the expression to type check - # Returns [env', t, eff], where env' is the type environment at the end of the expression + # Returns [env', t], where env' is the type environment at the end of the expression # and t is the type of the expression. t is always canonical. def self._tc(scope, env, e) case e.type when :nil - [env, RDL::Globals.types[:nil], [:+, :+]] + [env, RDL::Globals.types[:nil]] when :true - [env, RDL::Globals.types[:true], [:+, :+]] + [env, RDL::Globals.types[:true]] when :false - [env, RDL::Globals.types[:false], [:+, :+]] + [env, RDL::Globals.types[:false]] when :str, :string t = RDL::Config.instance.use_precise_string ? RDL::Type::PreciseStringType.new(e.children[0]) : RDL::Globals.types[:string] - [env, t, [:+, :+]] + [env, t] when :complex, :rational # constants - [env, RDL::Type::NominalType.new(e.children[0].class), [:+, :+]] + [env, RDL::Type::NominalType.new(e.children[0].class)] when :int, :float - if RDL::Config.instance.number_mode #&& (e.type == :float) - [env, RDL::Type::NominalType.new(Integer), [:+, :+]] + if RDL::Config.instance.number_mode + [env, RDL::Type::NominalType.new(Integer)] else - [env, RDL::Type::SingletonType.new(e.children[0]), [:+, :+]] + [env, RDL::Type::SingletonType.new(e.children[0])] end when :sym # singleton - [env, RDL::Type::SingletonType.new(e.children[0]), [:+, :+]] + [env, RDL::Type::SingletonType.new(e.children[0])] when :dstr, :xstr # string (or execute-string) with interpolation - effi = [:+, :+] prec_str = [] envi = env e.children.each { |ei| - envi, ti, eff_new = tc(scope, envi, ei) - effi = effect_union(effi, eff_new) + envi, ti = tc(scope, envi, ei) if RDL::Config.instance.use_precise_string if ei.type == :str || ei.type == :string ## for strings, just append the string itself @@ -647,24 +600,23 @@ def self._tc(scope, env, e) t = RDL::Globals.types[:string] end } - [envi, t, effi] + [envi, t] when :dsym # symbol with interpolation envi = env e.children.each { |ei| envi, _ = tc(scope, envi, ei) } - [envi, RDL::Globals.types[:symbol], [:+, :+]] + [envi, RDL::Globals.types[:symbol]] when :regexp envi = env e.children.each { |ei| envi, _ = tc(scope, envi, ei) unless ei.type == :regopt } - [envi, RDL::Globals.types[:regexp], [:+, :+]] + [envi, RDL::Globals.types[:regexp]] when :array envi = env tis = [] is_array = false - effi = [:+, :+] + e.children.each { |ei| if ei.type == :splat - envi, ti, new_eff = tc(scope, envi, ei.children[0]); - effi = effect_union(effi, new_eff) + envi, ti = tc(scope, envi, ei.children[0]); if ti.is_a? RDL::Type::TupleType ti.cant_promote! # must remain a tuple tis.concat(ti.params) @@ -696,37 +648,34 @@ def self._tc(scope, env, e) tis << ti # splat does nothing end else - envi, ti, new_eff = tc(scope, envi, ei); - effi = effect_union(effi, new_eff) + envi, ti = tc(scope, envi, ei); tis << ti end } if is_array - [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(*tis).canonical), effi] + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(*tis).canonical)] elsif scope[:task] == :infer && RDL::Config.instance.infer_empties && tis.empty? - [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :array_param, name: "array_param_#{e.loc}")), effi] + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :array_param, name: "array_param_#{e.loc}"))] else - [envi, RDL::Type::TupleType.new(*tis), effi] + [envi, RDL::Type::TupleType.new(*tis)] end when :hash envi = env tlefts = [] trights = [] is_fh = true - effi = [:+, :+] + e.children.each { |p| # each child is a pair if p.type == :pair - envi, tleft, effl = tc(scope, envi, p.children[0]) + envi, tleft = tc(scope, envi, p.children[0]) tlefts << tleft - effi = effect_union(effi, effl) - envi, tright, effr = tc(scope, envi, p.children[1]) + envi, tright = tc(scope, envi, p.children[1]) trights << tright - effi = effect_union(effi, effr) is_fh = false unless tleft.is_a?(RDL::Type::SingletonType) elsif p.type == :kwsplat - envi, tkwsplat, new_eff = tc(scope, envi, p.children[0]) - effi = effect_union(effi, new_eff) + envi, tkwsplat = tc(scope, envi, p.children[0]) + if tkwsplat.is_a? RDL::Type::FiniteHashType tkwsplat.cant_promote! # must remain finite hash tlefts.concat(tkwsplat.elts.keys.map { |k| RDL::Type::SingletonType.new(k) }) @@ -747,49 +696,44 @@ def self._tc(scope, env, e) fh = tlefts.map { |t| t.val }.zip(trights).to_h if scope[:task] == :infer && RDL::Config.instance.infer_empties && fh.empty? [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_key, name: "hash_param_key_#{e.loc}"), - RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_val, name: "hash_param_val_#{e.loc}")), effi] + RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :hash_param_val, name: "hash_param_val_#{e.loc}"))] else - [envi, RDL::Type::FiniteHashType.new(fh, nil), effi] + [envi, RDL::Type::FiniteHashType.new(fh, nil)] end else tleft = RDL::Type::UnionType.new(*tlefts) tright = RDL::Type::UnionType.new(*trights) - [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], tleft, tright), effi] + [envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], tleft, tright)] end #TODO test! # when :kwsplat # TODO! when :irange, :erange - env1, t1, eff1 = tc(scope, env, e.children[0]) - env2, t2, eff2 = tc(scope, env1, e.children[1]) + env1, t1 = tc(scope, env, e.children[0]) + env2, t2 = tc(scope, env1, e.children[1]) # promote singleton types to nominal types; safe since Ranges are immutable t1 = RDL::Type::NominalType.new(t1.val.class) if t1.is_a? RDL::Type::SingletonType t2 = RDL::Type::NominalType.new(t2.val.class) if t2.is_a? RDL::Type::SingletonType error :nonmatching_range_type, [t1, t2], e unless t1 <= t2 || t2 <= t1 - [env2, RDL::Type::GenericType.new(RDL::Globals.types[:range], t1), effect_union(eff1, eff2)] + [env2, RDL::Type::GenericType.new(RDL::Globals.types[:range], t1)] when :self - [env, env[:self], [:+, :+]] + [env, env[:self]] when :lvar, :ivar, :cvar, :gvar - if e.type == :lvar then eff = [:+, :+] else eff = [:-, :+] end - tc_var(scope, env, e.type, e.children[0], e) + [eff] + tc_var(scope, env, e.type, e.children[0], e) when :lvasgn, :ivasgn, :cvasgn, :gvasgn - if e.type == :lvasgn || scope[:meth] == :initialize then eff = [:+, :+] else eff = [:-, :+] end x = e.children[0] # if local var, lhs is bound to nil before assignment is executed! only matters in type checking for locals env = env.bind(x, RDL::Globals.types[:nil]) if ((e.type == :lvasgn) && (not (env.has_key? x))) - envright, tright, effright = tc(scope, env, e.children[1]) - tc_vasgn(scope, envright, e.type, x, tright, e)+[effect_union(eff, effright)] + envright, tright = tc(scope, env, e.children[1]) + tc_vasgn(scope, envright, e.type, x, tright, e) when :masgn # (masgn (mlhs (Xvasgn var-name) ... (Xvasgn var-name)) rhs) - effi = [:+, :+] e.children[0].children.each { |asgn| - effi = effect_union(effi, [:-, :+]) if asgn.type != :lvasgn && scope[:meth] != :initialize next unless asgn.type == :lvasgn x = e.children[0] env = env.bind(x, RDL::Globals.types[:nil]) if (not (env.has_key? x)) # see lvasgn # Note don't need to check outer_env here because will be checked by tc_vasgn below } - envi, tright, effright = tc(scope, env, e.children[1]) - effi = effect_union(effi, effright) + envi, tright = tc(scope, env, e.children[1]) lhs = e.children[0].children if tright.is_a? RDL::Type::TupleType tright.cant_promote! # must always remain a tuple because of the way type checking currently works @@ -810,13 +754,13 @@ def self._tc(scope, env, e) } splat = lhs[splat_ind] envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::TupleType.new(*rhs), splat) if splat.children.any? - [envi, tright, effi] + [envi, tright] else error :masgn_num, [rhs.length, lhs.length], e unless lhs.length == rhs.length lhs.zip(rhs).each { |left, right| envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left) } - [envi, tright, effi] + [envi, tright] end elsif (tright.is_a? RDL::Type::GenericType) && (tright.base == RDL::Globals.types[:array]) tasgn = tright.params[0] @@ -827,7 +771,7 @@ def self._tc(scope, env, e) envi, _ = tc_vasgn(scope, envi, asgn.type, asgn.children[0], tasgn, asgn) end } - [envi, tright, effi] + [envi, tright] elsif (tright.is_a? RDL::Type::DynamicType) tasgn = tright lhs.each { |asgn| @@ -837,7 +781,7 @@ def self._tc(scope, env, e) envi, _ = tc_vasgn(scope, envi, asgn.type, asgn.children[0], tasgn, asgn) end } - [env, tright, effi] + [env, tright] elsif tright.is_a?(RDL::Type::VarType) splat_ind = lhs.index { |lhs_elt| lhs_elt.type == :splat } #raise "not yet implemented" if splat_ind @@ -857,7 +801,7 @@ def self._tc(scope, env, e) } splat = lhs[splat_ind] envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :tuple_element, name: "array_param_#{count}")), splat) if splat.children.any? ## could be empty splat - [envi, tright, effi] + [envi, tright] else new_tuple = [] count = 0 @@ -867,75 +811,59 @@ def self._tc(scope, env, e) } #tuple_type = RDL::Type::TupleType.new(*new_tuple) #RDL::Type::Type.leq(tright, tuple_type, ast: e) # don't think this is necessary - [envi, tright, effi] + [envi, tright] end else error :masgn_bad_rhs, [tright], e.children[1] end when :op_asgn - effi = [:+, :+] if e.children[0].type == :send # (op-asgn (send recv meth) :op operand) meth = e.children[0].children[1] - envleft, trecv, effleft = tc(scope, env, e.children[0].children[0]) # recv - effi = effect_union(effi, effleft) + envleft, trecv = tc(scope, env, e.children[0].children[0]) # recv elargs = e.children[0].children[2] if elargs - envleft, elargs, effleft = tc(scope, envleft, elargs) - effi = effect_union(effi, effleft) + envleft, elargs = tc(scope, envleft, elargs) largs = [elargs] else largs = [] end - tloperand, lopeff = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() - effi = effect_union(effi, lopeff) - envoperand, troperand, effoperand = tc(scope, envleft, e.children[2]) # operand - effi = effect_union(effi, effoperand) - tright, effright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand) - effi = effect_union(effi, effright) + tloperand = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() + envoperand, troperand = tc(scope, envleft, e.children[2]) # operand + tright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand) tright = largs.push(tright) if largs mutation_meth = (meth.to_s + '=').to_sym - tres, effres = tc_send(scope, envoperand, trecv, mutation_meth, tright, nil, e, true) # call recv.meth=(recvt.meth().op(operand)) - effi = effect_union(effi, effres) - [envoperand, tres, effi] + tres = tc_send(scope, envoperand, trecv, mutation_meth, tright, nil, e, true) # call recv.meth=(recvt.meth().op(operand)) + [envoperand, tres] else # (op-asgn (Xvasgn var-name) :op operand) x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_vasgn below env = env.bind(x, RDL::Globals.types[:nil]) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn - effi = effect_union(effi, [:-, :+]) if e.children[0].type != :lvasgn envi, trecv = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to - envright, tright, effright = tc(scope, envi, e.children[2]) # operand - effi = effect_union(effi, effright) - trhs, effrhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e) - effi = effect_union(effrhs, effi) - tc_vasgn(scope, envright, e.children[0].type, x, trhs, e) + [effi] + envright, tright = tc(scope, envi, e.children[2]) # operand + trhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e) + tc_vasgn(scope, envright, e.children[0].type, x, trhs, e) end when :and_asgn, :or_asgn # very similar logic to op_asgn - effi = [:+, :+] if e.children[0].type == :send meth = e.children[0].children[1] - envleft, trecv, effleft = tc(scope, env, e.children[0].children[0]) # recv - effi = effect_union(effi, effleft) + envleft, trecv = tc(scope, env, e.children[0].children[0]) # recv elargs = e.children[0].children[2] if elargs - envleft, elargs, eleff = tc(scope, envleft, elargs) - effi = effect_union(effi, eleff) + envleft, elargs = tc(scope, envleft, elargs) largs = [elargs] else largs = [] end - tleft, effleft = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() - effi = effect_union(effi, effleft) - envright, tright, effright = tc(scope, envleft, e.children[1]) # operand - effi = effect_union(effi, effright) + tleft = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() + envright, tright = tc(scope, envleft, e.children[1]) # operand else x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_var below env = env.bind(x, RDL::Globals.types[:nil]) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn envleft, tleft = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to - envright, tright, effright = tc(scope, envleft, e.children[1]) - effi = effect_union(effi, effright) + envright, tright = tc(scope, envleft, e.children[1]) end envi, trhs = (if tleft.is_a? RDL::Type::SingletonType if e.type == :and_asgn @@ -954,23 +882,22 @@ def self._tc(scope, env, e) if e.children[0].type == :send mutation_meth = (meth.to_s + '=').to_sym rhs_array = [*largs, trhs] - tres, effres = tc_send(scope, envi, trecv, mutation_meth, rhs_array, nil, e) - effi = effect_union(effi, effres) - [envi, tres, effi] + tres = tc_send(scope, envi, trecv, mutation_meth, rhs_array, nil, e) + [envi, tres] else - tc_vasgn(scope, envi, e.children[0].type, x, trhs, e) + [effi] + tc_vasgn(scope, envi, e.children[0].type, x, trhs, e) end when :match_with_lvasgn # /regexp/ =~ rhs - env1, t1, eff1 = tc(scope, env, e.children[0]) # the regexp - env2, t2, eff2 = tc(scope, env, e.children[1]) # the rhs - ts, es = tc_send(scope, env2, RDL::Globals.types[:regexp], :=~, [t2], nil, e) - [env2, ts, es] + env1, t1 = tc(scope, env, e.children[0]) # the regexp + env2, t2 = tc(scope, env, e.children[1]) # the rhs + ts = tc_send(scope, env2, RDL::Globals.types[:regexp], :=~, [t2], nil, e) + [env2, ts] # (regexp # (str "foo") # (regopt)) # (str "foo")) when :nth_ref, :back_ref - [env, RDL::Globals.types[:string], [:+, :+]] + [env, RDL::Globals.types[:string]] when :const if e.children[0].nil? && e.children[1] == :ENV ## ENV @@ -980,21 +907,20 @@ def self._tc(scope, env, e) else c = to_type(find_constant(env, scope, e)) end - [env, c, [:+, :+]] + [env, c] when :defined? # do not type check subexpression, since it may not be type correct, e.g., undefined variable - [env, RDL::Globals.types[:string], [:+, :+]] + [env, RDL::Globals.types[:string]] when :send, :csend # children[0] = receiver; if nil, receiver is self # children[1] = method name, a symbol # children [2..] = actual args - return tc_var_type(scope, env, e) + [[:+, :+]] if (e.children[0].nil? || is_RDL(e.children[0])) && e.children[1] == :var_type - return tc_type_cast(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil? ## TODO: could be more precise with effects here, punting for now - return tc_note_type(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :note_type - return tc_instantiate!(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :instantiate! + return tc_var_type(scope, env, e) if (e.children[0].nil? || is_RDL(e.children[0])) && e.children[1] == :var_type + return tc_type_cast(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil? + return tc_note_type(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :note_type + return tc_instantiate!(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :instantiate! envi = env tactuals = [] - eff = [:+, :+] block = scope[:block] map_case = false e_map_case = ti_map_case = nil @@ -1037,45 +963,39 @@ def self._tc(scope, env, e) e_map_case = ei ti_map_case = ti else - ti, effi = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType - eff = effect_union(eff, effi) + ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType block = [ti, ei] end else - envi, ti, effi = tc(sscope, envi, ei) - eff = effect_union(eff, effi) + envi, ti = tc(sscope, envi, ei) tactuals << ti end } - envi, trecv, effrec = if e.children[0].nil? then [envi, envi[:self], [:+, :+]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver - eff = effect_union(effrec, eff) + envi, trecv = if e.children[0].nil? then [envi, envi[:self]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver if map_case && trecv.is_a?(RDL::Type::GenericType) #raise "Expected GenericType, got #{trecv}." unless trecv.is_a?(RDL::Type::GenericType) trecv.is_a?(RDL::Type::GenericType) - ti_map_case, effi = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) + ti_map_case = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) map_block_type = RDL::Type::MethodType.new([trecv.params[0]], nil, ti_map_case.canonical.ret) - eff = effect_union(eff, effi) block = [map_block_type, e_map_case] end - tres, effres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e) - [envi, tres.canonical, effect_union(effres, eff) ] + tres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e) + [envi, tres.canonical] } when :yield - ## TODO: effects # very similar to send except the callee is the method's block error :no_block, [], e unless scope[:tblock] error :block_block, [], e if scope[:tblock].is_a?(RDL::Type::MethodType) && scope[:tblock].block scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception envi = env tactuals = [] - eff = [:+, :+] - e.children[0..-1].each { |ei| envi, ti, effi = tc(scope, envi, ei); tactuals << ti ; eff = effect_union(effi, eff)} + e.children[0..-1].each { |ei| envi, ti = tc(scope, envi, ei); tactuals << ti } if scope[:tblock].is_a?(RDL::Type::VarType) block_ret_type = RDL::Type::VarType.new(cls: scope[:klass], meth: scope[:meth], category: :block_ret, name: "block_return") block_type = RDL::Type::MethodType.new(tactuals, nil, block_ret_type) RDL::Type::Type.leq(scope[:tblock], block_type, ast: e) - return [envi, block_ret_type, eff] + return [envi, block_ret_type] else unless tc_arg_types(scope[:tblock], tactuals) msg = < 1 @@ -1461,8 +1351,8 @@ def self._tc(scope, env, e) scope_merge(scope, block: nil, break: env, next: env) { |sscope| trecv = get_super_owner(envi[:self], scope[:meth]) - tres, effres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) - [envi, tres.canonical, effres] + tres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) + [envi, tres.canonical] } else error :unsupported_expression, [e.type, e], e @@ -1705,7 +1595,6 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) trecvs = if trecvs.is_a? RDL::Type::UnionType then union = true; trecvs.types else union = false; [trecvs] end trets = [] - eff = [:+, :+] trecvs.each { |trecv| if trecv.is_a?(RDL::Type::ChoiceType) choice_hash = { } @@ -1716,7 +1605,7 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) t = t.canonical tarms = t.is_a?(RDL::Type::UnionType) ? t.types : [t] tarms.each { |t| - ts, _ = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) + ts = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) choice_hash[num] = RDL::Type::UnionType.new(*ts).canonical } rescue StaticTypeError => err @@ -1733,21 +1622,13 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) ts = [RDL::Type::ChoiceType.new(choice_hash, [trecv] + trecv.connecteds)] end else - ts, es = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) + ts = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) end - if es.nil? || (es.all? { |effect| effect.nil? }) ## could be multiple, because every time e is called, nil is added to effects - ## should probably change default effect to be [:-, :-], but for now I want it like this, - ## so I can easily see when a method has been used and its effect set to the default. - #puts "Going to assume method #{meth} for receiver #{trecv} has effect [:-, :-]." - eff = [:-, :-] - else - es.each { |effect| eff = effect_union(eff, effect) unless effect.nil? } - end trets.concat(ts) } trets.map! {|t| (t.is_a?(RDL::Type::AnnotatedArgType) || t.is_a?(RDL::Type::BoundArgType)) ? t.type : t} - return [RDL::Type::UnionType.new(*trets), eff] + return RDL::Type::UnionType.new(*trets) end # Like tc_send but trecv should never be a union type @@ -1767,7 +1648,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, elsif trecv.is_a?(RDL::Type::AnnotatedArgType) || trecv.is_a?(RDL::Type::DependentArgType) trecv = trecv.type end - return [tc_send_class(trecv, e), [[:+, :+]]] if (meth == :class) && (tactuals.empty?) + return tc_send_class(trecv, e) if (meth == :class) && (tactuals.empty?) ts = [] # Array, i.e., an intersection types case trecv when RDL::Type::SingletonType @@ -1787,7 +1668,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, trecv_lookup = RDL::Util.add_singleton_marker(trecv.val.to_s) self_inst = trecv end - ts, es = lookup(scope, trecv_lookup, meth_lookup, e) + ts = lookup(scope, trecv_lookup, meth_lookup, e) ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if init && (ts.nil?) # there's always a nullary new if initialize is undefined error :no_singleton_method_type, [trecv.val, meth], e unless ts inst = {self: self_inst} @@ -1802,13 +1683,13 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, else raise "unsupported self type for to_proc #{env[:self]}" end - ts, es = lookup(scope, klass.to_s, trecv.val, e) + ts = lookup(scope, klass.to_s, trecv.val, e) ts = filter_comp_types(ts, false) ## no comp types in this case error :no_type_for_symbol, [trecv.val.inspect], e if ts.nil? - return [ts, nil] ## TODO: not sure what to do hear about effect + return ts else klass = trecv.val.class.to_s - ts, es = lookup(scope, klass, meth, e) + ts = lookup(scope, klass, meth, e) error :no_instance_method_type, [klass, meth], e unless ts inst = {self: trecv} self_klass = trecv.val.class @@ -1817,14 +1698,14 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, meth_lookup = meth trecv_lookup = RDL::Util.add_singleton_marker(trecv.val.to_s) self_inst = trecv - ts, es = lookup(scope, trecv_lookup, meth_lookup, e) + ts = lookup(scope, trecv_lookup, meth_lookup, e) ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if (meth == :new) && (ts.nil?) # there's always a nullary new if initialize is undefined error :no_singleton_method_type, [trecv.val, meth], e unless ts inst = {self: self_inst} self_klass = trecv.val ts = ts.map { |t| t.instantiate(inst) } when RDL::Type::NominalType - ts, es = lookup(scope, trecv.name, meth, e) + ts = lookup(scope, trecv.name, meth, e) unless ts klass = RDL::Util.to_class(scope[:klass]) if klass.class == Module @@ -1832,10 +1713,10 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, # Here a module method is calling a non-existent method; check for it in all mixees # TODO: Handle :extend nts = RDL::Globals.module_mixees[klass].map { |k, kind| if kind == :include then RDL::Type::NominalType.new(k) end } - return [@types[:bot], :+] if nts.empty? # if module not mixed in, this call can't happen; so %bot plus pure + return [RDL::Globals.types[:bot]] if nts.empty? # if module not mixed in, this call can't happen; so %bot ut = RDL::Type::UnionType.new(*nts) - ts, es = tc_send(scope, env, ut, meth, tactuals, block, e, op_asgn) - return [[ts], [es]] + t = tc_send(scope, env, ut, meth, tactuals, block, e, op_asgn) + return [t] end error :no_instance_method_type, [trecv.name, meth], e end @@ -1843,11 +1724,11 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, inst = {self: trecv} self_klass = RDL::Util.to_class(trecv.name) when RDL::Type::GenericType - ts, es = lookup(scope, trecv.base.name, meth, e) + ts = lookup(scope, trecv.base.name, meth, e) if ts.nil? && (trecv.base.name == "ActiveRecord_Relation") && defined? DBType ## ActiveRecord_Relation methods that don't exist are delegated to underlying scoped class. ## Maybe there's a way not to bake this in to typechecker, but I can't think of one. - ts, es = lookup(scope, "[s]"+DBType.rec_to_nominal(trecv).name, meth, e) + ts = lookup(scope, "[s]"+DBType.rec_to_nominal(trecv).name, meth, e) error :no_instance_method_type, [trecv.base.name, meth], e unless ts ts = ts.map { |t| RDL::Type::MethodType.new(t.args, t.block, trecv) } end @@ -1856,7 +1737,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, self_klass = RDL::Util.to_class(trecv.base.name) when RDL::Type::TupleType if RDL::Config.instance.use_comp_types - ts, es = lookup(scope, "Array", meth, e) + ts = lookup(scope, "Array", meth, e) error :no_instance_method_type, ["Array", meth], e unless ts #inst = trecv.to_inst.merge(self: trecv) t_bind = trecv.promote.params[0].to_s == "t" ? RDL::Globals.types[:bot] : trecv.promote.params[0] @@ -1866,14 +1747,14 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ## need to promote in this case error :tuple_finite_hash_promote, ['tuple', 'Array'], e unless trecv.promote! trecv = trecv.canonical - ts, es = lookup(scope, trecv.base.name, meth, e) + ts = lookup(scope, trecv.base.name, meth, e) error :no_instance_method_type, [trecv.base.name, meth], e unless ts inst = trecv.to_inst.merge(self: trecv) self_klass = RDL::Util.to_class(trecv.base.name) end when RDL::Type::FiniteHashType if RDL::Config.instance.use_comp_types - ts, es = lookup(scope, "Hash", meth, e) + ts = lookup(scope, "Hash", meth, e) error :no_instance_method_type, ["Hash", meth], e unless ts #inst = trecv.to_inst.merge(self: trecv) k_bind = trecv.promote.params[0].to_s == "k" ? RDL::Globals.types[:bot] : trecv.promote.params[0] @@ -1884,14 +1765,14 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ## need to promote in this case error :tuple_finite_hash_promote, ['finite hash', 'Hash'], e unless trecv.promote! trecv = trecv.canonical - ts, es = lookup(scope, trecv.base.name, meth, e) + ts = lookup(scope, trecv.base.name, meth, e) error :no_instance_method_type, [trecv.base.name, meth], e unless ts inst = trecv.to_inst.merge(self: trecv) self_klass = RDL::Util.to_class(trecv.base.name) end when RDL::Type::PreciseStringType if RDL::Config.instance.use_comp_types - ts, es = lookup(scope, "String", meth, e) + ts = lookup(scope, "String", meth, e) error :no_instance_method_type, ["String", meth], e unless ts inst = { self: trecv } self_klass = String @@ -1899,7 +1780,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ## need to promote in this case error :tuple_finite_hash_promote, ['precise string type', 'String'], e unless trecv.promote! trecv = trecv.canonical - ts, es = lookup(scope, trecv.name, meth, e) + ts = lookup(scope, trecv.name, meth, e) error :no_instance_method_type, [trecv.name, meth], e unless ts inst = { self: trecv } self_klass = RDL::Util.to_class(trecv.name) @@ -1954,7 +1835,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, #self_klass = nil #error :recv_var_type, [trecv], e - return [[ret_type]] + return [ret_type] when RDL::Type::MethodType if meth == :call # Special case - invokes the Proc @@ -1964,7 +1845,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, tc_send_one_recv(scope, env, RDL::Globals.types[:proc], meth, tactuals, block, e, op_asgn, union) end when RDL::Type::DynamicType - return [[trecv]] + return [trecv] else raise RuntimeError, "receiver type #{trecv} of kind #{trecv.class} not supported yet, meth=#{meth}" end @@ -2019,34 +1900,11 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, #apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? if tmeth_inst begin - effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block + tc_block(scope, env, tmeth.block, block, tmeth_inst) if block rescue BlockTypeError => _ block_mismatch = true end - if es - es = es.map { |es_effect| if es_effect.nil? then es_effect else es_effect.clone end } - es.each { |es_effect| ## expecting just one effect per method right now. can clean this up later. - if !es_effect.nil? && (es_effect[1] == :blockdep || es_effect[0] == :blockdep) - #raise "Got block-dependent effect for method #{meth}, but no block." unless block && effblock - if !(block && effblock) - ## In this case we called a block-dependent method, - ## but with no block. It could, e.g., return an enumerator. - ## Could have more intricate handling, but for now will just - ## be conseervative and return [:-, :-] - es_effect[0] = :- - es_effect[1] = :- - elsif effblock[0] == :+ or effblock[0] == :~ - es_effect[1] = :+ - es_effect[0] = :+ - elsif effblock[0] == :- - es_effect[1] = :- - es_effect[0] = :- - else - raise "unexpected effect #{effblock[0]}" - end - end - } - end + if init#trecv.is_a?(RDL::Type::SingletonType) && meth == :new init_typ = RDL::Type::NominalType.new(trecv.val) if (tmeth.ret.instance_of?(RDL::Type::GenericType)) @@ -2159,7 +2017,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, error :arg_type_single_receiver_error, [name, meth, msg], e end # TODO: issue warning if trets.size > 1 ? - return [trets, es] + return trets end def self.apply_deferred_constraints(deferred_constraints, e) @@ -2411,10 +2269,9 @@ def self.tc_block(scope, env, tblock, block, inst) args_hash[a] = v v } - _, ret_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], nil] else tc(scope, env.merge(Env.new(args_hash)), body) end + _, ret_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(scope, env.merge(Env.new(args_hash)), body) end block_type = RDL::Type::MethodType.new(arg_vartypes, nil, ret_type) RDL::Type::Type.leq(block_type, tblock, inst, false, ast: body) - eff else # must be [block-args, block-body] args, body = block env, targs = args_hash(scope, env, tblock, args, block[1], 'block') @@ -2422,10 +2279,9 @@ def self.tc_block(scope, env, tblock, block, inst) # note: okay if outer_env shadows, since nested scope will include outer scope by next line targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this env = env.merge(Env.new(targs_dup)) - _, body_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], [:+, :+]] else tc(bscope, env.merge(Env.new(targs)), body) end + _, body_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(bscope, env.merge(Env.new(targs)), body) end error :bad_return_type, [body_type, tblock.ret], body, block: true unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false, ast: body) # - eff } end end @@ -2445,19 +2301,18 @@ def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_un # return array of all matching types from context_types, if any ts = [] scope[:context_types].each { |ctk, ctm, ctt| ts << ctt if ctk.to_s == klass && ctm == name } - return [ts, [[:-, :-]]] unless ts.empty? ## not sure what to do about effects here, so just going to be super conservative + return ts unless ts.empty? end if scope[:context_types] scope[:context_types].each { |k, m, t| - return [t, [[:-, :-]]] if k == klass && m = name ## not sure what to do about effects here, so just going to be super conservative + return t if k == klass && m = name } end t = RDL::Globals.info.get_with_aliases(klass, name, :type) - e = RDL::Globals.info.get_with_aliases(klass, name, :effect) - return [t, e] if t # simplest case, no need to walk inheritance hierarchy - return [[make_unknown_method_type(klass, name)]] if RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } + return t if t # simplest case, no need to walk inheritance hierarchy + return [make_unknown_method_type(klass, name)] if RDL::Globals.to_infer.values.any? { |set| set.include?([klass, name]) } klass_name = RDL::Util.has_singleton_marker(klass) ? RDL::Util.remove_singleton_marker(klass) : klass - return [[make_unknown_method_type(klass, name)]] if make_unknown && (name == :initialize) && (RDL::Util.to_class(klass_name).instance_method(:initialize).owner == RDL::Util.to_class(klass_name)) # don't want to walk up hierarchy in initialize case where it is specifically defined for this class + return [make_unknown_method_type(klass, name)] if make_unknown && (name == :initialize) && (RDL::Util.to_class(klass_name).instance_method(:initialize).owner == RDL::Util.to_class(klass_name)) # don't want to walk up hierarchy in initialize case where it is specifically defined for this class the_klass = RDL::Util.to_class(klass) is_singleton = RDL::Util.has_singleton_marker(klass) included = RDL::Util.to_class(klass.gsub("[s]", "")).included_modules @@ -2475,14 +2330,12 @@ def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_un anc_lookup = ancestor.to_s end tancestor = RDL::Globals.info.get_with_aliases(anc_lookup, name, :type) - eancestor = RDL::Globals.info.get_with_aliases(anc_lookup, name, :effect) - return [tancestor, eancestor] if tancestor - return [[make_unknown_method_type(anc_lookup, name)]] if RDL::Globals.to_infer.values.any? { |set| set.include?([anc_lookup, name]) } + return tancestor if tancestor + return [make_unknown_method_type(anc_lookup, name)] if RDL::Globals.to_infer.values.any? { |set| set.include?([anc_lookup, name]) } # special caes: Kernel's singleton methods are *also* added when included?! if ancestor == Kernel tancestor = RDL::Globals.info.get_with_aliases(RDL::Util.add_singleton_marker('Kernel'), name, :type) - eancestor = RDL::Globals.info.get_with_aliases(RDL::Util.add_singleton_marker('Kernel'), name, :effect) - return [tancestor, eancestor] if tancestor + return tancestor if tancestor end if ancestor.instance_methods(false).member?(name) ## Milod: Not sure what the purpose of the below lines is. @@ -2515,12 +2368,12 @@ def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_un ret = RDL::Globals.types[:dyn] ret = RDL::Type::NominalType.new(the_klass) if name == :initialize - return [[RDL::Type::MethodType.new(args, nil, ret)]] + return [RDL::Type::MethodType.new(args, nil, ret)] else #return nil ## Trying new approach: create unknown method type for any methods without types. if make_unknown - return [[make_unknown_method_type(klass, name)]] if (RDL::Util.to_class(klass).instance_methods + RDL::Util.to_class(klass).private_instance_methods).include?(name) + return [make_unknown_method_type(klass, name)] if (RDL::Util.to_class(klass).instance_methods + RDL::Util.to_class(klass).private_instance_methods).include?(name) else return nil end @@ -2663,7 +2516,6 @@ def message RDL_MESSAGES = { bad_return_type: "got type `%s' where return type `%s' expected", - bad_effect: "got effect `%s' where effect `%s' expected", bad_inst_type: "instantiate! called on object of type `%s' where Generic Type was expected", inst_not_param: "instantiate! receiver is of class `%s' which is not parameterized", inst_num_args: "instantiate! expecting `%s' type parameters, got `%s' parameters", diff --git a/lib/rdl/types/rdl_types.rb b/lib/rdl/types/rdl_types.rb index 6fe8d413..2beb8d9e 100644 --- a/lib/rdl/types/rdl_types.rb +++ b/lib/rdl/types/rdl_types.rb @@ -1,45 +1,45 @@ RDL.type_params 'RDL::Type::SingletonType', [:t], :satisfies? -RDL.type 'RDL::Type::SingletonType', :initialize, "(x) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::SingletonType', :val, "() -> t", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::SingletonType', :nominal, "() -> RDL::Type::NominalType", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::SingletonType', :initialize, "(x) -> self", wrap: false +RDL.type 'RDL::Type::SingletonType', :val, "() -> t", wrap: false +RDL.type 'RDL::Type::SingletonType', :nominal, "() -> RDL::Type::NominalType", wrap: false -RDL.type 'RDL::Type::NominalType', :initialize, "(Class or String) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::NominalType', :klass, "() -> Class", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::NominalType', :name, "() -> String", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::NominalType', :initialize, "(Class or String) -> self", wrap: false +RDL.type 'RDL::Type::NominalType', :klass, "() -> Class", wrap: false +RDL.type 'RDL::Type::NominalType', :name, "() -> String", wrap: false -RDL.type 'RDL::Type::GenericType', :initialize, "(RDL::Type::Type, *RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::GenericType', :params, "() -> Array", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::GenericType', :base, "() -> RDL::Type::NominalType", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::GenericType', :initialize, "(RDL::Type::Type, *RDL::Type::Type) -> self", wrap: false +RDL.type 'RDL::Type::GenericType', :params, "() -> Array", wrap: false +RDL.type 'RDL::Type::GenericType', :base, "() -> RDL::Type::NominalType", wrap: false -RDL.type 'RDL::Type::UnionType', :initialize, "(*RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::UnionType', :canonical, "() -> RDL::Type::Type", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::UnionType', :types, "() -> Array", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::UnionType', :initialize, "(*RDL::Type::Type) -> self", wrap: false +RDL.type 'RDL::Type::UnionType', :canonical, "() -> RDL::Type::Type", wrap: false +RDL.type 'RDL::Type::UnionType', :types, "() -> Array", wrap: false -RDL.type 'RDL::Type::TupleType', :initialize, "(*RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::TupleType', :params, "() -> Array", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::TupleType', :promote, "(?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::TupleType', :promote!, "(?RDL::Type::Type) -> %bool", wrap: false, effect: [:-, :+] -RDL.type 'RDL::Type::TupleType', :check_bounds, "(?%bool) -> %bool", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::TupleType', :initialize, "(*RDL::Type::Type) -> self", wrap: false +RDL.type 'RDL::Type::TupleType', :params, "() -> Array", wrap: false +RDL.type 'RDL::Type::TupleType', :promote, "(?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false +RDL.type 'RDL::Type::TupleType', :promote!, "(?RDL::Type::Type) -> %bool", wrap: false +RDL.type 'RDL::Type::TupleType', :check_bounds, "(?%bool) -> %bool", wrap: false -RDL.type 'RDL::Type::FiniteHashType', :elts, "() -> Hash<%any, RDL::Type::Type>", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::FiniteHashType', :elts=, "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", wrap: false, effect: [:-, :+] -RDL.type 'RDL::Type::FiniteHashType', :promote, "(?%any, ?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::FiniteHashType', :promote!, "(?%any, ?RDL::Type::Type) -> %bool", wrap: false, effect: [:-, :+] -RDL.type 'RDL::Type::FiniteHashType', :initialize, "(Hash<%any, RDL::Type::Type> or {}, ?RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::FiniteHashType', :check_bounds, "(?%bool) -> %bool", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::FiniteHashType', :elts, "() -> Hash<%any, RDL::Type::Type>", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :elts=, "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :promote, "(?%any, ?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :promote!, "(?%any, ?RDL::Type::Type) -> %bool", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :initialize, "(Hash<%any, RDL::Type::Type> or {}, ?RDL::Type::Type) -> self", wrap: false +RDL.type 'RDL::Type::FiniteHashType', :check_bounds, "(?%bool) -> %bool", wrap: false -RDL.type 'RDL::Type::OptionalType', :initialize, "(RDL::Type::Type) -> self", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::OptionalType', :initialize, "(RDL::Type::Type) -> self", wrap: false -RDL.type 'RDL::Type::VarargType', :initialize, '(RDL::Type::Type) -> self', wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::VarargType', :initialize, '(RDL::Type::Type) -> self', wrap: false -RDL.type 'RDL::Type::VarType', :initialize, "(String) -> self", wrap: false, effect: [:+, :+] +RDL.type 'RDL::Type::VarType', :initialize, "(String) -> self", wrap: false -RDL.type "RDL::Type::Type", 'self.leq', "(RDL::Type::Type, RDL::Type::Type) -> %bool", wrap: false, effect: [:+, :+] +RDL.type "RDL::Type::Type", 'self.leq', "(RDL::Type::Type, RDL::Type::Type) -> %bool", wrap: false -RDL.type 'RDL::Globals', 'self.parser', "() -> RDL::Type::Parser", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Globals', 'self.types', "() -> Hash", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Type::Parser', :scan_str, "(String) -> RDL::Type::Type", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Config', 'self.instance', "() -> RDL::Config", wrap: false, effect: [:+, :+] -RDL.type 'RDL::Config', 'weak_update_promote', "() -> %bool", wrap: false, effect: [:+, :+] -RDL.type "Object", '__getobj__', "() -> self", wrap: false, effect: [:+, :+] ## needed due to type casting +RDL.type 'RDL::Globals', 'self.parser', "() -> RDL::Type::Parser", wrap: false +RDL.type 'RDL::Globals', 'self.types', "() -> Hash", wrap: false +RDL.type 'RDL::Type::Parser', :scan_str, "(String) -> RDL::Type::Type", wrap: false +RDL.type 'RDL::Config', 'self.instance', "() -> RDL::Config", wrap: false +RDL.type 'RDL::Config', 'weak_update_promote', "() -> %bool", wrap: false +RDL.type "Object", '__getobj__', "() -> self", wrap: false ## needed due to type casting diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 93b46432..de3eb415 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -279,7 +279,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con right.methods.each_pair { |m, t| return false unless lklass.method_defined?(m) || RDL::Typecheck.lookup({}, klass_lookup, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) ## Added the second condition because Rails lazily defines some methods. - types, _ = RDL::Typecheck.lookup({}, lklass.to_s, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) + types = RDL::Typecheck.lookup({}, lklass.to_s, m, nil, make_unknown: false)#RDL::Globals.info.get(lklass, m, :type) if RDL::Config.instance.use_comp_types types = RDL::Typecheck.filter_comp_types(types, true) else @@ -378,16 +378,16 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con klass = left.base.klass right.methods.each_pair { |meth, t| if (klass.to_s == "ActiveRecord_Relation") && !klass.method_defined?(meth) && defined? DBType - types, _ = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false) + types = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false) if !types - base_types, _ = RDL::Typecheck.lookup({}, "[s]"+DBType.rec_to_nominal(left).name, meth, {}, make_unknown: false) + base_types = RDL::Typecheck.lookup({}, "[s]"+DBType.rec_to_nominal(left).name, meth, {}, make_unknown: false) return false unless base_types types = base_types.map { |t| RDL::Type::MethodType.new(t.args, t.block, left) } end return false unless types else return false unless klass.method_defined?(meth) || RDL::Typecheck.lookup({}, klass.to_s, meth, nil, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) ## Added the second condition because Rails lazily defines some methods. - types, _ = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) + types = RDL::Typecheck.lookup({}, klass.to_s, meth, {}, make_unknown: false)#RDL::Globals.info.get(klass, meth, :type) end if RDL::Config.instance.use_comp_types diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 4f3d3c2f..bbfd57e7 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -270,7 +270,6 @@ def self.do_method_added(the_self, sing, klass, meth) raise RuntimeError, "Deferred #{kind} contract from class #{prev_klass} being applied in class #{tmp_klass} to #{meth}" end RDL::Globals.info.add(klass, meth, kind, contract) - RDL::Globals.info.add(klass, meth, :effect, h[:effect]) if h.has_key?(:effect) RDL::Wrap.wrap(klass, meth) if h[:wrap] unless !h.has_key?(:typecheck) || RDL::Globals.info.set(klass, meth, :typecheck, h[:typecheck]) raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}" @@ -398,7 +397,7 @@ def post(*args, wrap: RDL::Config.instance.post_defaults[:wrap], version: nil, & # type(klass, meth, type) # type(meth, type) # type(type) - def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil, effect: nil) + def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil) return if version && !(Gem::Requirement.new(version).satisfied_by? Gem.ruby_version) klass, meth, type = begin RDL::Wrap.process_type_args(self, *args) @@ -412,7 +411,6 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: err.set_backtrace bt raise err end - effect[0] = :- if effect && effect[0] == :~ ## For now, treating pure-ish :~ as :-, since we realized it doesn't actually affect termination checking. typs = type.args + [type.ret] if type.block block_type = type.block.is_a?(RDL::Type::OptionalType) ? type.block.type : type.block @@ -425,7 +423,6 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: # warn "#{RDL::Util.pp_klass_method(klass, meth)}: methods that end in ? should have return type %bool" # end RDL::Globals.info.add(klass, meth, :type, type) - RDL::Globals.info.add(klass, meth, :effect, effect) unless RDL::Globals.info.set(klass, meth, :typecheck, typecheck) raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}" end @@ -449,7 +446,7 @@ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL: end else RDL::Globals.deferred << [klass, :type, type, {wrap: wrap, - typecheck: typecheck, effect: effect}] + typecheck: typecheck}] end nil end @@ -971,7 +968,6 @@ def self.load_rails_schema def self.check_type_code RDL.config { |config| config.use_comp_types = false } count = 1 - #code_type = RDL::Globals.parser.scan_str "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL::Globals.dep_types.each { |klass, meth, typ| klass = RDL::Util.has_singleton_marker(klass) ? RDL::Util.remove_singleton_marker(klass) : klass arg_list = "(trec, targs" @@ -997,7 +993,7 @@ def self.check_type_code end eval tmp_eval ast = Parser::CurrentRuby.parse tmp_meth - RDL::Typecheck.typecheck("[s]#{klass}", "tc_#{meth}#{count}".to_sym, ast, [code_type], [[:-, :+]]) + RDL::Typecheck.typecheck("[s]#{klass}", "tc_#{meth}#{count}".to_sym, ast, [code_type]) count += 1 end } diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 2484830a..b3f6fe07 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -12,7 +12,7 @@ def Array.to_type(t) RDL::Globals.parser.scan_str "#T #{t}" end end -RDL.type Array, 'self.to_type', "(Object) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.to_type', "(Object) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.output_type(trec, targs, meth_name, default1, default2=default1, use_sing_val: true, nil_false_default: false) @@ -45,7 +45,7 @@ def Array.output_type(trec, targs, meth_name, default1, default2=default1, use_s RDL::Globals.parser.scan_str "#T #{default2}" end end -RDL.type Array, 'self.output_type', "(RDL::Type::Type, Array, Symbol, String or Symbol, ?(String or Symbol), { use_sing_val: ?%bool, nil_false_default: ?%bool }) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.output_type', "(RDL::Type::Type, Array, Symbol, String or Symbol, ?(String or Symbol), { use_sing_val: ?%bool, nil_false_default: ?%bool }) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.any_or_t(trec, vararg=false) @@ -58,7 +58,7 @@ def Array.any_or_t(trec, vararg=false) if vararg then RDL::Type::VarargType.new(ret) else ret end end end -RDL.type Array, 'self.any_or_t', "(RDL::Type::Type, ?%bool) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.any_or_t', "(RDL::Type::Type, ?%bool) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.promoted_or_t(trec, vararg=false) @@ -71,7 +71,7 @@ def Array.promoted_or_t(trec, vararg=false) if vararg then RDL::Type::VarargType.new(ret) else ret end end end -RDL.type Array, 'self.promoted_or_t', "(RDL::Type::Type, ?%bool) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.promoted_or_t', "(RDL::Type::Type, ?%bool) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.promote_tuple(trec) @@ -82,7 +82,7 @@ def Array.promote_tuple(trec) trec end end -RDL.type Array, 'self.promote_tuple', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] +RDL.type Array, 'self.promote_tuple', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code def Array.promote_tuple!(trec) @@ -94,7 +94,7 @@ def Array.promote_tuple!(trec) trec end end -RDL.type Array, 'self.promote_tuple!', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:~, :+] +RDL.type Array, 'self.promote_tuple!', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code RDL.type :Array, :<<, '(``any_or_t(trec)``) -> ``append_push_output(trec, targs, :<<)``' @@ -109,7 +109,7 @@ def Array.append_push_output(trec, targs, meth) RDL::Globals.parser.scan_str "#T Array" end end -RDL.type Array, 'self.append_push_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.append_push_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :[], '(Range) -> ``output_type(trec, targs, :[], :promoted_array, "Array")``' RDL.type :Array, :[], '(Integer or Float) -> ``output_type(trec, targs, :[], :promoted_param, "t")``' @@ -132,7 +132,7 @@ def Array.plus_input(targs) RDL::Globals.types[:array] end end -RDL.type Array, 'self.plus_input', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Array, 'self.plus_input', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Array.plus_output(trec, targs) @@ -165,7 +165,7 @@ def Array.plus_output(trec, targs) end end end -RDL.type Array, 'self.plus_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Array, 'self.plus_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :-, '(Array) -> ``output_type(trec, targs, :-, :promoted_array, "Array")``' RDL.type :Array, :slice, '(Range) -> ``output_type(trec, targs, :slice, :promoted_array, "Array")``' @@ -198,7 +198,7 @@ def Array.assign_output(trec, targs) RDL::Globals.parser.scan_str "#T t" end end -RDL.type Array, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :[]=, '(Integer, Integer, ``any_or_t(trec)``) -> t' RDL.type :Array, :[]=, '(Integer, Integer, ``any_or_t(trec)``) -> t' @@ -220,7 +220,7 @@ def Array.multi_assign_output(trec, targs) end =end end -RDL.type Array, 'self.multi_assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.multi_assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :[]=, '(Range, ``any_or_t(trec)``) -> ``multi_assign_output(trec, targs)``' RDL.type :Array, :assoc, '(t) -> Array' @@ -240,7 +240,7 @@ def Array.map_output(trec) RDL::Globals.parser.scan_str "#T Array" end end -RDL.type Array, 'self.map_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.map_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :map!, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :collect, '() {(``promoted_or_t(trec)``) -> u} -> Array' @@ -310,7 +310,7 @@ def Array.each_arg2(trec) RDL.type :Array, :each_index, '() { (Integer) -> %any } -> self' RDL.type :Array, :each_index, '() -> Enumerator' -RDL.type :Array, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``', effect: [:+, :+] +RDL.type :Array, :empty?, '() -> ``output_type(trec, targs, :empty?, "%bool")``' RDL.type :Array, :fetch, '(Integer) -> ``output_type(trec, targs, :[], :promoted_param, "t")``' RDL.type :Array, :fetch, '(Integer, %any) -> ``RDL::Type::UnionType.new(targs[1], output_type(trec, targs, :[], :promoted_param, "t"))``' RDL.type :Array, :fetch, '(Integer) { (Integer) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :[], :promoted_param, "t"))``' @@ -333,7 +333,7 @@ def Array.fill_output(trec, targs) RDL::Globals.parser.scan_str "#T Array" end end -RDL.type Array, 'self.fill_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.fill_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :fill, '(``promoted_or_t(trec)``, Integer, ?Integer) -> ``promote_tuple!(trec)``' ## can be more precise for this one, but would require many cases RDL.type :Array, :fill, '(``promoted_or_t(trec)``, Range) -> ``promote_tuple!(trec)``' @@ -367,7 +367,7 @@ def Array.include_output(trec, targs) RDL::Globals.types[:bool] end end -RDL.type Array, 'self.include_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Array, 'self.include_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :insert, '(Integer, ``promoted_or_t(trec)``) -> ``promote_tuple!(trec)``' @@ -417,7 +417,7 @@ def Array.reverse_output(trec) RDL::Globals.parser.scan_str "#T Array" end end -RDL.type Array, 'self.reverse_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Array, 'self.reverse_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Array, :reverse_each, '() { (``promoted_or_t(trec)``) -> %any } -> self' RDL.type :Array, :reverse_each, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' @@ -470,31 +470,31 @@ def Array.reverse_output(trec) ######### Non-dependet types below ######### -RDL.type :Array, :<<, '(t) -> Array', effect: [:-, :+] -RDL.type :Array, :[], '(Range) -> Array', effect: [:+, :+] -RDL.type :Array, :[], '(Integer or Float) -> t', effect: [:+, :+] -RDL.type :Array, :[], '(Integer, Integer) -> Array', effect: [:+, :+] +RDL.type :Array, :<<, '(t) -> Array' +RDL.type :Array, :[], '(Range) -> Array' +RDL.type :Array, :[], '(Integer or Float) -> t' +RDL.type :Array, :[], '(Integer, Integer) -> Array' RDL.type :Array, :&, '(Array) -> Array' RDL.type :Array, :*, '(Integer) -> Array' RDL.type :Array, :*, '(String) -> String' -RDL.type :Array, :+, '(Enumerable) -> Array', effect: [:+, :+] -RDL.type :Array, :+, '(Array) -> Array', effect: [:+, :+] +RDL.type :Array, :+, '(Enumerable) -> Array' +RDL.type :Array, :+, '(Array) -> Array' RDL.type :Array, :-, '(Array) -> Array' RDL.type :Array, :slice, '(Range) -> Array' RDL.type :Array, :slice, '(Integer) -> t' RDL.type :Array, :slice, '(Integer, Integer) -> Array' -RDL.type :Array, :[]=, '(Integer, t) -> t', effect: [:-, :+] -RDL.type :Array, :[]=, '(Integer, Integer, t) -> t', effect: [:-, :+] +RDL.type :Array, :[]=, '(Integer, t) -> t' +RDL.type :Array, :[]=, '(Integer, Integer, t) -> t' # RDL.type :Array, :[]=, '(Integer, Integer, Array) -> Array' # RDL.type :Array, :[]=, '(Range, Array) -> Array' -RDL.type :Array, :[]=, '(Range, t) -> t', effect: [:-, :+] +RDL.type :Array, :[]=, '(Range, t) -> t' RDL.type :Array, :assoc, '(t) -> Array' RDL.type :Array, :at, '(Integer) -> t' RDL.type :Array, :clear, '() -> Array' -RDL.type :Array, :map, '() {(t) -> u} -> Array', effect: [:blockdep, :blockdep] -RDL.type :Array, :map, '() -> Enumerator', effect: [:blockdep, :blockdep] -RDL.type :Array, :map!, '() {(t) -> u} -> Array', effect: [:-, :blockdep] -RDL.type :Array, :map!, '() -> Enumerator', effect: [:-, :blockdep] +RDL.type :Array, :map, '() {(t) -> u} -> Array' +RDL.type :Array, :map, '() -> Enumerator' +RDL.type :Array, :map!, '() {(t) -> u} -> Array' +RDL.type :Array, :map!, '() -> Enumerator' RDL.type :Array, :collect, '() { (t) -> u } -> Array' RDL.type :Array, :collect, '() -> Enumerator' RDL.type :Array, :combination, '(Integer) { (Array) -> %any } -> Array' @@ -516,8 +516,8 @@ def Array.reverse_output(trec) RDL.type :Array, :drop, '(Integer) -> Array' RDL.type :Array, :drop_while, '() { (t) -> %bool } -> Array' RDL.type :Array, :drop_while, '() -> Enumerator' -RDL.type :Array, :each, '() -> Enumerator', effect: [:-, :blockdep] -RDL.type :Array, :each, '() { (t) -> %any } -> Array', effect: [:-, :blockdep] +RDL.type :Array, :each, '() -> Enumerator' +RDL.type :Array, :each, '() { (t) -> %any } -> Array' RDL.type :Array, :each_index, '() { (Integer) -> %any } -> Array' RDL.type :Array, :each_index, '() -> Enumerator' RDL.type :Array, :empty?, '() -> %bool' @@ -536,7 +536,7 @@ def Array.reverse_output(trec) RDL.type :Array, :index, '() -> Enumerator' RDL.type :Array, :first, '() -> t' RDL.type :Array, :first, '(Integer) -> Array' -RDL.type :Array, :include?, '(u) -> %bool', effect: [:+, :+] +RDL.type :Array, :include?, '(u) -> %bool' RDL.type :Array, :initialize, '() -> self' RDL.type :Array, :initialize, '(Integer) -> self' RDL.type :Array, :initialize, '(Integer, t) -> self' @@ -547,8 +547,8 @@ def Array.reverse_output(trec) RDL.type :Array, :keep_if, '() { (t) -> %bool } -> Array' RDL.type :Array, :last, '() -> t' RDL.type :Array, :last, '(Integer) -> Array' -RDL.type :Array, :member?, '(u) -> %bool', effect: [:+, :+] -RDL.type :Array, :length, '() -> Integer', effect: [:+, :+] +RDL.type :Array, :member?, '(u) -> %bool' +RDL.type :Array, :length, '() -> Integer' RDL.type :Array, :pack, "(String) -> String" RDL.type :Array, :permutation, '(?Integer) -> Enumerator' RDL.type :Array, :permutation, '(?Integer) { (Array) -> %any } -> Array' @@ -556,7 +556,7 @@ def Array.reverse_output(trec) RDL.type :Array, :pop, '() -> t' RDL.type :Array, :product, '(*Array) -> Array>' RDL.type :Array, :rassoc, '(u) -> t' -RDL.type :Array, :reject, '() { (t) -> %bool } -> Array', effect: [:+, :blockdep] +RDL.type :Array, :reject, '() { (t) -> %bool } -> Array' RDL.type :Array, :reject, '() -> Enumerator' RDL.type :Array, :reject!, '() { (t) -> %bool } -> Array' RDL.type :Array, :reject!, '() -> Enumerator' @@ -564,7 +564,7 @@ def Array.reverse_output(trec) RDL.type :Array, :repeated_combination, '(Integer) -> Enumerator' RDL.type :Array, :repeated_permutation, '(Integer) { (Array) -> %any } -> Array' RDL.type :Array, :repeated_permutation, '(Integer) -> Enumerator' -RDL.type :Array, :reverse, '() -> Array', effect: [:+, :+] +RDL.type :Array, :reverse, '() -> Array' RDL.type :Array, :reverse!, '() -> Array' RDL.type :Array, :reverse_each, '() { (t) -> %any } -> Array' RDL.type :Array, :reverse_each, '() -> Enumerator' diff --git a/lib/types/core/basic_object.rb b/lib/types/core/basic_object.rb index 2fbd79e9..9e906246 100644 --- a/lib/types/core/basic_object.rb +++ b/lib/types/core/basic_object.rb @@ -1,9 +1,9 @@ RDL.nowrap :BasicObject -RDL.type :BasicObject, :==, '(%any other) -> %bool', effect: [:+, :+] +RDL.type :BasicObject, :==, '(%any other) -> %bool' RDL.type :BasicObject, :equal?, '(%any other) -> %bool' -RDL.type :BasicObject, :!, '() -> %bool', effect: [:+, :+] -RDL.type :BasicObject, :!=, '(%any other) -> %bool', effect: [:+, :+] +RDL.type :BasicObject, :!, '() -> %bool' +RDL.type :BasicObject, :!=, '(%any other) -> %bool' RDL.type :BasicObject, :instance_eval, '(String, ?String filename, ?Integer lineno) -> %any' RDL.type :BasicObject, :instance_eval, '() { () -> %any } -> %any' RDL.type :BasicObject, :instance_exec, '(*%any args) { (*%any) -> %any } -> %any' diff --git a/lib/types/core/class.rb b/lib/types/core/class.rb index b3eab955..8eb396bb 100644 --- a/lib/types/core/class.rb +++ b/lib/types/core/class.rb @@ -13,5 +13,5 @@ RDL.type :Class, :class, '() -> Class' RDL.type :Class, :superclass, '() -> Class' RDL.type :Class, :name, '() -> String' -RDL.type :Class, :==, '(%any) -> %bool', effect: [:+, :+] -RDL.type :Class, :===, '(%any) -> %bool', effect: [:+, :+] +RDL.type :Class, :==, '(%any) -> %bool' +RDL.type :Class, :===, '(%any) -> %bool' diff --git a/lib/types/core/enumerable.rb b/lib/types/core/enumerable.rb index 3b7cc3cf..d45b4020 100644 --- a/lib/types/core/enumerable.rb +++ b/lib/types/core/enumerable.rb @@ -2,9 +2,9 @@ RDL.type_params :Enumerable, [:t], :all? -RDL.type :Enumerable, :all?, '() -> %bool', effect: [:blockdep, :blockdep] -RDL.type :Enumerable, :all?, '() { (t) -> %bool } -> %bool', effect: [:blockdep, :blockdep] -RDL.type :Enumerable, :all?, '() { (k, v) -> %bool } -> %bool', effect: [:blockdep, :blockdep] +RDL.type :Enumerable, :all?, '() -> %bool' +RDL.type :Enumerable, :all?, '() { (t) -> %bool } -> %bool' +RDL.type :Enumerable, :all?, '() { (k, v) -> %bool } -> %bool' RDL.type :Enumerable, :any?, '() -> %bool' RDL.type :Enumerable, :any?, '() { (t) -> %any } -> %bool' # RDL.type :Enumerable, :chunk, '(XXXX : *XXXX)' # TODO @@ -28,8 +28,8 @@ RDL.type :Enumerable, :each_cons, '(Integer n) -> Enumerator' # RDL.type :Enumerable, :each_entry, '(XXXX : *XXXX)' # TODO RDL.rdl_alias :Enumerable, :each_slice, :each_cons -RDL.type :Enumerable, :each_with_index, '() { (t, Integer) -> %any } -> Enumerable', effect: [:blockdep, :blockdep] # args! note may not return self -RDL.type :Enumerable, :each_with_index, '() -> Enumerable', effect: [:blockdep, :blockdep] # args! note may not return self +RDL.type :Enumerable, :each_with_index, '() { (t, Integer) -> %any } -> Enumerable' +RDL.type :Enumerable, :each_with_index, '() -> Enumerable' # args! note may not return self # RDL.type :Enumerable, :each_with_object, '(XXXX : XXXX)' #TODO RDL.type :Enumerable, :entries, '() -> Array' # TODO args? RDL.rdl_alias :Enumerable, :find, :detect diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 44463619..01314211 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -54,7 +54,7 @@ def Hash.output_type(trec, targs, meth_name, default1, default2=default1, nil_de end end end -RDL.type Hash, 'self.output_type', "(RDL::Type::Type, Array, Symbol, Symbol or String, ?(Symbol or String), { nil_default: ?%bool, use_sing_val: ?%bool } ) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.output_type', "(RDL::Type::Type, Array, Symbol, Symbol or String, ?(Symbol or String), { nil_default: ?%bool, use_sing_val: ?%bool } ) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.to_type(t) @@ -74,7 +74,7 @@ def Hash.to_type(t) RDL::Type::NominalType.new(t.class) end end -RDL.type Hash, 'self.to_type', "(%any) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.to_type', "(%any) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.any_or_k(trec) case trec @@ -93,7 +93,7 @@ def Hash.any_or_k(trec) raise "unexpected, got #{trec}" end end -RDL.type Hash, 'self.any_or_k', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.any_or_k', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.any_or_v(trec) case trec @@ -106,7 +106,7 @@ def Hash.any_or_v(trec) raise "unexpected" end end -RDL.type Hash, 'self.any_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.any_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.promoted_or_v(trec) case trec @@ -119,7 +119,7 @@ def Hash.promoted_or_v(trec) raise "unexpected" end end -RDL.type Hash, 'self.promoted_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.promoted_or_v', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.promoted_or_k(trec) case trec @@ -151,7 +151,7 @@ def Hash.weak_promote(val) val end end -RDL.type Hash, 'self.weak_promote', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.weak_promote', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false #RDL.type :Hash, 'self.[]', '(*%any) -> ``hash_create_output_from_list(targs)``' RDL.type :Hash, 'self.[]', '(*%any) -> ``hash_create_output(targs)``' @@ -182,9 +182,9 @@ def Hash.hash_create_output(targs) args = targs.map { |a| i = i+1 ; if i.even? && a.is_a?(RDL::Type::SingletonType) then RDL.type_cast(a, "RDL::Type::SingletonType", force: true).val else a end } RDL::Type::FiniteHashType.new(RDL.type_cast(Hash[*args], "Hash<%any, RDL::Type::Type>", force: true), nil) end -RDL.type Hash, 'self.hash_create_output', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.hash_create_output', "(Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false -RDL.type :Hash, :[], '(``any_or_k(trec)``) -> ``output_type(trec, targs, :[], :default_or_promoted_val, "v", nil_default: true)``', effect: [:+, :+] +RDL.type :Hash, :[], '(``any_or_k(trec)``) -> ``output_type(trec, targs, :[], :default_or_promoted_val, "v", nil_default: true)``' RDL.type :Hash, :[]=, '(``any_or_k(trec)``, ``any_or_v(trec)``) -> ``assign_output(trec, targs)``' @@ -208,7 +208,7 @@ def Hash.assign_output(trec, targs) trec.params[1] end end -RDL.type Hash, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, 'self.assign_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type Hash, :initialize, "(*%any) -> ``RDL::Type::FiniteHashType.new({}, nil, default: targs[0])``" RDL.type Hash, :initialize, "() { (Hash, x) -> y } -> ``RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:top], RDL::Globals.types[:top])``" @@ -249,7 +249,7 @@ def Hash.delete_output(trec, targs, block) RDL::Globals.parser.scan_str "#T #{t}" end end -RDL.type Hash, 'self.delete_output', "(RDL::Type::Type, Array, %bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.delete_output', "(RDL::Type::Type, Array, %bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Hash, :delete_if, '() { (``promoted_or_k(trec)``, ``promoted_or_v(trec)``) -> %any } -> self' RDL.type :Hash, :delete_if, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. @@ -267,9 +267,9 @@ def Hash.delete_output(trec, targs, block) RDL.type :Hash, :fetch, '(``any_or_k(trec)``, ``targs[1] ? targs[1] : RDL::Globals.types[:top]``) -> ``RDL::Type::UnionType.new(targs[1] ? targs[1] : RDL::Globals.types[:top], output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``any_or_k(trec)``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { () -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' -RDL.type :Hash, :first, '() -> ``output_type(trec, targs, :first, :default_or_promoted_val, "v", nil_default: true)``', effect: [:+, :+] +RDL.type :Hash, :first, '() -> ``output_type(trec, targs, :first, :default_or_promoted_val, "v", nil_default: true)``' RDL.type :Hash, :member?, '(%any) -> ``output_type(trec, targs, :member?, "%bool")``' -RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``', effect: [:+, :+] +RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``' RDL.type :Hash, :key?, '(%any) -> ``output_type(trec, targs, :key?, "%bool")``' RDL.type :Hash, :has_value?, '(%any) -> ``output_type(trec, targs, :has_value?, "%bool")``' RDL.type :Hash, :value?, '(%any) -> ``output_type(trec, targs, :value?, "%bool")``' @@ -289,7 +289,7 @@ def Hash.invert_output(trec) #RDL::Globals.parser.scan_str "#T Hash" end end -RDL.type Hash, 'self.invert_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.invert_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Hash, :keep_if, '() { (``any_or_k(trec)``,``any_or_v(trec)``) -> %bool } -> self' RDL.type :Hash, :keep_if, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), RDL::Type::TupleType.new(any_or_k(trec), any_or_v(trec)))``' ## I had made a mistake here, type checker caught it. @@ -321,7 +321,7 @@ def Hash.merge_input(trec, targs, mutate=false) RDL::Globals.types[:hash] end end -RDL.type Hash, 'self.merge_input', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.merge_input', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.merge_output(trec, targs, mutate=false) @@ -383,7 +383,7 @@ def Hash.merge_output(trec, targs, mutate=false) end end -RDL.type Hash, 'self.merge_output', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, 'self.merge_output', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Hash, :merge, '(Hash) { (k,v,b) -> v or b } -> Hash' RDL.type :Hash, :rassoc, '(``any_or_v(trec)``) -> ``RDL::Type::TupleType.new(output_type(trec, targs, :key, :promoted_key, "k", nil_default: true, use_sing_val: false),targs[0])``' @@ -406,7 +406,7 @@ def Hash.shift_output(trec) RDL::Type::TupleType.new(trec.params[0], trec.params[1]) end end -RDL.type Hash, 'self.shift_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] +RDL.type Hash, 'self.shift_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false #RDL.type :Hash, :to_a, '() -> ``output_type(trec, targs, :to_a, "Array<[k, v]>")``' RDL.type :Hash, :to_a, '() -> ``to_a_output_type(trec)")``' @@ -443,7 +443,7 @@ def Hash.values_at_input(trec) RDL::Type::VarargType.new(trec.params[0]) end end -RDL.type Hash, 'self.values_at_input', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, 'self.values_at_input', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def Hash.values_at_output(trec, targs) @@ -463,7 +463,7 @@ def Hash.values_at_output(trec, targs) RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), trec.params[1]) end end -RDL.type Hash, 'self.values_at_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Hash, 'self.values_at_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false @@ -471,15 +471,15 @@ def Hash.values_at_output(trec, targs) ######### Non-dependent types below ######### -RDL.type :Hash, 'self.[]', '(*u) -> Hash', effect: [:+, :+] # example: Hash[1,2,3,4] -RDL.type :Hash, 'self.[]', '(Array<[a,b]>) -> Hash', effect: [:+, :+] -RDL.type :Hash, 'self.[]', '([to_hash: () -> Hash]) -> Hash', effect: [:+, :+] +RDL.type :Hash, 'self.[]', '(*u) -> Hash' # example: Hash[1,2,3,4] +RDL.type :Hash, 'self.[]', '(Array<[a,b]>) -> Hash' +RDL.type :Hash, 'self.[]', '([to_hash: () -> Hash]) -> Hash' RDL.type :Hash, :[], '(k) -> v' -RDL.type :Hash, :[]=, '(k, v) -> v', effect: [:-, :+] +RDL.type :Hash, :[]=, '(k, v) -> v' RDL.type :Hash, :store, '(k,v) -> v' -RDL.type :Hash, :any?, "() { (k, v) -> %any } -> %bool", effect: [:blockdep, :blockdep] +RDL.type :Hash, :any?, "() { (k, v) -> %any } -> %bool" # RDL.type :Hash, :assoc, '(k) -> [k, v]' # TODO RDL.type :Hash, :assoc, '(k) -> Array' RDL.type :Hash, :clear, '() -> Hash' @@ -497,44 +497,44 @@ def Hash.values_at_output(trec, targs) RDL.type :Hash, :delete, '(k) { (k) -> u } -> u or v' RDL.type :Hash, :delete_if, '() { (k,v) -> %bool } -> Hash' RDL.type :Hash, :delete_if, '() -> Enumerator<[k, v]>' -RDL.type :Hash, :each, '() { (k,v) -> %any } -> Hash', effect: [:blockdep, :blockdep] -RDL.type :Hash, :each, '() -> Enumerator<[k, v]>', effect: [:blockdep, :blockdep] +RDL.type :Hash, :each, '() { (k,v) -> %any } -> Hash' +RDL.type :Hash, :each, '() -> Enumerator<[k, v]>' RDL.type :Hash, :each_pair, '() { (k,v) -> %any } -> Hash' RDL.type :Hash, :each_pair, '() -> Enumerator<[k, v]>' -RDL.type :Hash, :each_key, '() { (k) -> %any } -> Hash', effect: [:blockdep, :blockdep] -RDL.type :Hash, :each_key, '() -> Enumerator<[k, v]>', effect: [:blockdep, :blockdep] +RDL.type :Hash, :each_key, '() { (k) -> %any } -> Hash' +RDL.type :Hash, :each_key, '() -> Enumerator<[k, v]>' RDL.type :Hash, :each_value, '() { (v) -> %any } -> Hash' RDL.type :Hash, :each_value, '() -> Enumerator<[k, v]>' RDL.type :Hash, :empty?, '() -> %bool' -RDL.type :Hash, :except, '(%any) -> self', effect: [:+, :+] +RDL.type :Hash, :except, '(%any) -> self' RDL.type :Hash, :fetch, '(k) -> v' RDL.type :Hash, :fetch, '(k,u) -> u or v' RDL.type :Hash, :fetch, '(k) { (k) -> u } -> u or v' -RDL.type :Hash, :map, "() { (k, v) -> x } -> Array", effect: [:+, :blockdep] +RDL.type :Hash, :map, "() { (k, v) -> x } -> Array" RDL.type :Hash, :member?, '(t) -> %bool' RDL.type :Hash, :has_key?, '(t) -> %bool' RDL.type :Hash, :key?, '(t) -> %bool' RDL.type :Hash, :has_value?, '(t) -> %bool' RDL.type :Hash, :value?, '(t) -> %bool' RDL.type :Hash, :to_s, '() -> String' -RDL.type :Hash, :include?, '(%any) -> %bool', effect: [:+, :+] +RDL.type :Hash, :include?, '(%any) -> %bool' RDL.type :Hash, :inspect, '() -> String' -RDL.type :Hash, :invert, '() -> Hash', effect: [:+, :+] +RDL.type :Hash, :invert, '() -> Hash' RDL.type :Hash, :keep_if, '() { (k,v) -> %bool } -> Hash' RDL.type :Hash, :keep_if, '() -> Enumerator<[k, v]>' RDL.type :Hash, :key, '(t) -> k' -RDL.type :Hash, :keys, '() -> Array', effect: [:+, :+] +RDL.type :Hash, :keys, '() -> Array' RDL.type :Hash, :length, '() -> Integer' -RDL.type :Hash, :size, '() -> Integer', effect: [:+, :+] -RDL.type :Hash, :merge, '(Hash) -> Hash', effect: [:+, :+] -RDL.type :Hash, :merge, '(Hash) { (k,v,b) -> v or b } -> Hash', effect: [:+, :+] +RDL.type :Hash, :size, '() -> Integer' +RDL.type :Hash, :merge, '(Hash) -> Hash' +RDL.type :Hash, :merge, '(Hash) { (k,v,b) -> v or b } -> Hash' # RDL.type :Hash, :rassoc, '(k) -> Tuple' RDL.type :Hash, :rassoc, '(k) -> Array' RDL.type :Hash, :rehash, '() -> Hash' RDL.type :Hash, :reject, '() -> Enumerator<[k, v]>' RDL.type :Hash, :reject, '() {(k,v) -> %bool} -> Hash' RDL.type :Hash, :reject!, '() {(k,v) -> %bool} -> Hash' -RDL.type :Hash, :select, '() {(k,v) -> %bool} -> Hash', effect: [:+, :blockdep] +RDL.type :Hash, :select, '() {(k,v) -> %bool} -> Hash' RDL.type :Hash, :select!, '() {(k,v) -> %bool} -> Hash' # RDL.type :Hash, :shift, '() -> Tuple' RDL.type :Hash, :shift, '() -> Array' @@ -543,5 +543,5 @@ def Hash.values_at_output(trec, targs) RDL.type :Hash, :to_hash, '() -> self' RDL.type :Hash, :to_h, '() -> self' RDL.type :Hash, :values, '() -> Array' -RDL.type :Hash, :values_at, '(*k) -> Array', effect: [:+, :+] +RDL.type :Hash, :values_at, '(*k) -> Array' RDL.type :Hash, :with_indifferent_access, '() -> self' diff --git a/lib/types/core/integer.rb b/lib/types/core/integer.rb index cf1e537a..1c6d2564 100644 --- a/lib/types/core/integer.rb +++ b/lib/types/core/integer.rb @@ -13,7 +13,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL::Globals.parser.scan_str "#T #{type}" end end -RDL.type Numeric, 'self.sing_or_type', "(RDL::Type::Type, Array, Symbol, String) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:~, :+] +RDL.type Numeric, 'self.sing_or_type', "(RDL::Type::Type, Array, Symbol, String) -> RDL::Type::Type", typecheck: :type_code, wrap: false RDL.type :Integer, :%, '(Integer x {{ x!=0 }}) -> ``sing_or_type(trec, targs, :%, "Integer")``' @@ -62,17 +62,17 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :/, '(Complex x {{ x!=0 }}) -> ``sing_or_type(trec, targs, :/, "Complex")``' RDL.pre(:Integer, :/) { |x| x!=0 && if (x.real.is_a?(BigDecimal)||x.imaginary.is_a?(BigDecimal)) then (if x.real.is_a?(Float) then (x.real!=Float::INFINITY && !(x.real.nan?)) elsif(x.imaginary.is_a?(Float)) then x.imaginary!=Float::INFINITY && !(x.imaginary.nan?) else true end) else true end && if (x.real.is_a?(Rational) && x.imaginary.is_a?(Float)) then !x.imaginary.nan? else true end} -RDL.type :Integer, :<, '(Integer) -> ``sing_or_type(trec, targs, :<, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<, '(Float) -> ``sing_or_type(trec, targs, :<, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<, '(Rational) -> ``sing_or_type(trec, targs, :<, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<, '(BigDecimal) -> ``sing_or_type(trec, targs, :<, "%bool")``', effect: [:+, :+] +RDL.type :Integer, :<, '(Integer) -> ``sing_or_type(trec, targs, :<, "%bool")``' +RDL.type :Integer, :<, '(Float) -> ``sing_or_type(trec, targs, :<, "%bool")``' +RDL.type :Integer, :<, '(Rational) -> ``sing_or_type(trec, targs, :<, "%bool")``' +RDL.type :Integer, :<, '(BigDecimal) -> ``sing_or_type(trec, targs, :<, "%bool")``' RDL.type :Integer, :<<, '(Integer) -> ``sing_or_type(trec, targs, :<<, "Integer")``' -RDL.type :Integer, :<=, '(Integer) -> ``sing_or_type(trec, targs, :<=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<=, '(Float) -> ``sing_or_type(trec, targs, :<=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<=, '(Rational) -> ``sing_or_type(trec, targs, :<=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :<=, '(BigDecimal) -> ``sing_or_type(trec, targs, :<=, "%bool")``', effect: [:+, :+] +RDL.type :Integer, :<=, '(Integer) -> ``sing_or_type(trec, targs, :<=, "%bool")``' +RDL.type :Integer, :<=, '(Float) -> ``sing_or_type(trec, targs, :<=, "%bool")``' +RDL.type :Integer, :<=, '(Rational) -> ``sing_or_type(trec, targs, :<=, "%bool")``' +RDL.type :Integer, :<=, '(BigDecimal) -> ``sing_or_type(trec, targs, :<=, "%bool")``' RDL.type :Integer, :<=>, '(Integer) -> ``sing_or_type(trec, targs, :<=>, "Integer")``' RDL.post(:Integer, :<=>) { |r,x| r == -1 || r == 0 || r == 1 } @@ -89,15 +89,15 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :===, '(Object) -> ``sing_or_type(trec, targs, :===, "%bool")``' -RDL.type :Integer, :>, '(Integer) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>, '(Float) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>, '(Rational) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>, '(BigDecimal) -> ``sing_or_type(trec, targs, :>, "%bool")``', effect: [:+, :+] +RDL.type :Integer, :>, '(Integer) -> ``sing_or_type(trec, targs, :>, "%bool")``' +RDL.type :Integer, :>, '(Float) -> ``sing_or_type(trec, targs, :>, "%bool")``' +RDL.type :Integer, :>, '(Rational) -> ``sing_or_type(trec, targs, :>, "%bool")``' +RDL.type :Integer, :>, '(BigDecimal) -> ``sing_or_type(trec, targs, :>, "%bool")``' -RDL.type :Integer, :>=, '(Integer) -> ``sing_or_type(trec, targs, :>=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>=, '(Float) -> ``sing_or_type(trec, targs, :>=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>=, '(Rational) -> ``sing_or_type(trec, targs, :>=, "%bool")``', effect: [:+, :+] -RDL.type :Integer, :>=, '(BigDecimal) -> ``sing_or_type(trec, targs, :>=, "%bool")``', effect: [:+, :+] +RDL.type :Integer, :>=, '(Integer) -> ``sing_or_type(trec, targs, :>=, "%bool")``' +RDL.type :Integer, :>=, '(Float) -> ``sing_or_type(trec, targs, :>=, "%bool")``' +RDL.type :Integer, :>=, '(Rational) -> ``sing_or_type(trec, targs, :>=, "%bool")``' +RDL.type :Integer, :>=, '(BigDecimal) -> ``sing_or_type(trec, targs, :>=, "%bool")``' RDL.type :Integer, :>>, '(Integer) -> Integer r {{ r >= 0 }}' ## TODO @@ -239,11 +239,11 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :**, '(Complex) -> Complex' RDL.pre(:Integer, :**) { |x| x!=0 && if (x.real.is_a?(BigDecimal)||x.imaginary.is_a?(BigDecimal)) then (if x.real.is_a?(Float) then (x.real!=Float::INFINITY && !(x.real.nan?)) elsif(x.imaginary.is_a?(Float)) then x.imaginary!=Float::INFINITY && !(x.imaginary.nan?) else true end) else true end} -RDL.type :Integer, :+, '(Integer) -> Integer', effect: [:+, :+] -RDL.type :Integer, :+, '(Float) -> Float', effect: [:+, :+] -RDL.type :Integer, :+, '(Rational) -> Rational', effect: [:+, :+] -RDL.type :Integer, :+, '(BigDecimal) -> BigDecimal', effect: [:+, :+] -RDL.type :Integer, :+, '(Complex) -> Complex', effect: [:+, :+] +RDL.type :Integer, :+, '(Integer) -> Integer' +RDL.type :Integer, :+, '(Float) -> Float' +RDL.type :Integer, :+, '(Rational) -> Rational' +RDL.type :Integer, :+, '(BigDecimal) -> BigDecimal' +RDL.type :Integer, :+, '(Complex) -> Complex' RDL.type :Integer, :-, '(Integer) -> Integer' RDL.type :Integer, :-, '(Float) -> Float' @@ -283,9 +283,9 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :<=>, '(BigDecimal) -> Object' RDL.post(:Integer, :<=>) { |r,x| r == -1 || r == 0 || r == 1 } -RDL.type :Integer, :==, '(Object) -> %bool', effect: [:+, :+] +RDL.type :Integer, :==, '(Object) -> %bool' -RDL.type :Integer, :===, '(Object) -> %bool', effect: [:+, :+] +RDL.type :Integer, :===, '(Object) -> %bool' RDL.type :Integer, :>, '(Integer) -> %bool' RDL.type :Integer, :>, '(Float) -> %bool' @@ -370,7 +370,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.post(:Integer, :denominator) { |r,x| r == 1 } RDL.type :Integer, :downto, '(Integer) { (Integer) -> %any } -> Integer' RDL.type :Integer, :downto, '(Integer limit) -> Enumerator' -RDL.type :Integer, :even?, '() -> %bool', effect: [:+ ,:+] +RDL.type :Integer, :even?, '() -> %bool' RDL.type :Integer, :gcd, '(Integer) -> Integer' RDL.type :Integer, :gcdlcm, '(Integer) -> [Integer, Integer]' RDL.type :Integer, :floor, '() -> Integer' @@ -380,7 +380,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :lcm, '(Integer) -> Integer' RDL.type :Integer, :next, '() -> Integer' RDL.type :Integer, :numerator, '() -> Integer' -RDL.type :Integer, :odd?, '() -> %bool', effect: [:+, :+] +RDL.type :Integer, :odd?, '() -> %bool' RDL.type :Integer, :ord, '() -> Integer' RDL.type :Integer, :phase, '() -> %numeric' RDL.type :Integer, :pred, '() -> Integer' diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index 6ddd304a..0f9f37b9 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -62,9 +62,9 @@ RDL.type :Kernel, 'self.printf', '(?IO, ?String, *%any) -> nil' RDL.type :Kernel, :proc, '() {(*%any) -> %any} -> Proc' # TODO more precise RDL.type :Kernel, 'self.putc', '(Integer) -> Integer' -RDL.type :Kernel, 'self.puts', '(*[to_s : () -> String]) -> nil', effect: [:-,:+] -RDL.type :Kernel, 'self.raise', '() -> %bot', effect: [:+, :+] -RDL.type :Kernel, 'raise', '() -> %bot', effect: [:+, :+] +RDL.type :Kernel, 'self.puts', '(*[to_s : () -> String]) -> nil' +RDL.type :Kernel, 'self.raise', '() -> %bot' +RDL.type :Kernel, 'raise', '() -> %bot' # RDL.type :Kernel, 'self.raise', '(String or [exception : () -> String], ?String, ?Array) -> %any' # TODO: above same as fail? RDL.type :Kernel, 'self.rand', '(Integer or Range max) -> Integer' @@ -91,7 +91,7 @@ # RDL.type :Kernel, 'self.trap' # TODO # RDL.type :Kernel, 'self.untrace_var' # TODO RDL.type :Kernel, 'self.warn', '(*String msg) -> nil' -RDL.type :Kernel, :clone, '() -> self', effect: [:~, :+] +RDL.type :Kernel, :clone, '() -> self' RDL.type :Kernel, :raise, '() -> %bot' RDL.type :Kernel, :raise, '(String) -> %bot' RDL.type :Kernel, :raise, '(Class, ?String, ?Array) -> %bot' diff --git a/lib/types/core/module.rb b/lib/types/core/module.rb index 87d0ce5f..c480b0b7 100644 --- a/lib/types/core/module.rb +++ b/lib/types/core/module.rb @@ -54,7 +54,7 @@ RDL.type :Module, :public_method_defined?, '(Symbol or String) -> %bool' RDL.type :Module, :remove_class_variable, '(Symbol) -> %any' RDL.type :Module, :singleton_class?, '() -> %bool' -RDL.type :Module, :to_s, '() -> String', effect: [:+, :+] +RDL.type :Module, :to_s, '() -> String' # private methods below here RDL.type :Module, :alias_method, '(Symbol or String new_name, Symbol or String old_name) -> self' RDL.type :Module, :append_features, '(Module) -> self' diff --git a/lib/types/core/object.rb b/lib/types/core/object.rb index 0085374b..38117c91 100644 --- a/lib/types/core/object.rb +++ b/lib/types/core/object.rb @@ -24,11 +24,11 @@ RDL.type :Object, :!~, '(%any other) -> %bool', wrap: false RDL.type :Object, :<=>, '(%any other) -> Integer or nil', wrap: false -RDL.type :Object, :===, '(%any other) -> %bool', wrap: false, effect: [:+, :+] -RDL.type :Object, :==, '(%any other) -> %bool', wrap: false, effect: [:+, :+] +RDL.type :Object, :===, '(%any other) -> %bool', wrap: false +RDL.type :Object, :==, '(%any other) -> %bool', wrap: false RDL.type :Object, :=~, '(%any other) -> nil', wrap: false RDL.type :Object, :class, '() -> Class', wrap: false -RDL.type :Object, :clone, '() -> self', wrap: false, effect: [:+, :+] +RDL.type :Object, :clone, '() -> self', wrap: false # RDL.type :Object, :define_singleton_method, '(XXXX : *XXXX)') # TODO RDL.type :Object, :display, '(IO port) -> nil', wrap: false RDL.type :Object, :dup, '() -> self an_object', wrap: false @@ -45,11 +45,11 @@ RDL.type :Object, :instance_variable_get, '(Symbol or String) -> %any', wrap: false RDL.type :Object, :instance_variable_set, '(Symbol or String, %any) -> %any', wrap: false # returns 2nd argument RDL.type :Object, :instance_variables, '() -> Array', wrap: false -RDL.type :Object, :is_a?, '(Class or Module) -> %bool', wrap: false, effect: [:+, :+] +RDL.type :Object, :is_a?, '(Class or Module) -> %bool', wrap: false RDL.type :Object, :kind_of?, '(Class) -> %bool', wrap: false RDL.type :Object, :method, '(Symbol) -> Method', wrap: false RDL.type :Object, :methods, '(?%bool regular) -> Array', wrap: false -RDL.type :Object, :nil?, '() -> %bool', wrap: false, effect: [:+, :+] +RDL.type :Object, :nil?, '() -> %bool', wrap: false RDL.type :Object, :private_methods, '(?%bool all) -> Array', wrap: false RDL.type :Object, :protected_methods, '(?%bool all) -> Array', wrap: false RDL.type :Object, :public_method, '(Symbol) -> Method', wrap: false @@ -57,7 +57,7 @@ RDL.type :Object, :public_send, '(Symbol or String, *%any args) -> %any', wrap: false RDL.type :Object, :remove_instance_variable, '(Symbol) -> %any', wrap: false # RDL.type :Object, :respond_to?, '(Symbol or String, ?%bool include_all) -> %bool' -RDL.type :Object, :send, '(Symbol or String, *%any) -> Object', wrap: false, effect: [:-, :+] # Can't wrap this, used outside wrap switch +RDL.type :Object, :send, '(Symbol or String, *%any) -> Object', wrap: false RDL.type :Object, :singleton_class, '() -> Class', wrap: false RDL.type :Object, :singleton_method, '(Symbol) -> Method', wrap: false RDL.type :Object, :singleton_methods, '(?%bool all) -> Array', wrap: false diff --git a/lib/types/core/string.rb b/lib/types/core/string.rb index 3ca446f6..548eac60 100644 --- a/lib/types/core/string.rb +++ b/lib/types/core/string.rb @@ -28,7 +28,7 @@ def String.output_type(trec, targs, meth, type) end end -RDL.type String, 'self.output_type', "(RDL::Type::Type, Array, Symbol, String) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.output_type', "(RDL::Type::Type, Array, Symbol, String) -> RDL::Type::Type" def String.to_type(v) case v @@ -45,7 +45,7 @@ def String.to_type(v) end end -RDL.type String, 'self.to_type', "(%any) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.to_type', "(%any) -> RDL::Type::Type" def String.any_string(a) case a @@ -56,7 +56,7 @@ def String.any_string(a) end end -RDL.type String, 'self.any_string', "(%any) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.any_string', "(%any) -> RDL::Type::Type" def String.string_promote!(trec) case trec @@ -68,7 +68,7 @@ def String.string_promote!(trec) end end -RDL.type String, 'self.string_promote!', "(%any) -> RDL::Type::Type", effect: [:~, :+] +RDL.type String, 'self.string_promote!', "(%any) -> RDL::Type::Type" RDL.type :String, :initialize, '(?String str) -> self new_str' @@ -83,7 +83,7 @@ def String.plus_output(trec, targs) end end -RDL.type String, 'self.plus_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.plus_output', "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL.type :String, :+, '(``any_string(targs[0])``) -> ``plus_output(trec, targs)``' @@ -108,17 +108,17 @@ def String.append_output(trec, targs) end end -RDL.type String, 'self.append_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.append_output', "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL.type :String, :<=>, '(String) -> ``output_type(trec, targs, :<=>, "Integer")``' -RDL.type :String, :==, '(%any) -> ``output_type(trec, targs, :==, "%bool")``', effect: [:+, :+] +RDL.type :String, :==, '(%any) -> ``output_type(trec, targs, :==, "%bool")``' RDL.type :String, :===, '(%any) -> ``output_type(trec, targs, :===, "%bool")``' RDL.type :String, :=~, '(Object) -> ``output_type(trec, targs, :=~, "Integer")``', wrap: false # Wrapping this messes up $1 etc -RDL.type :String, :[], '(Integer, ?Integer) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] -RDL.type :String, :[], '(Range or Regexp) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] -RDL.type :String, :[], '(Regexp, Integer) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] -RDL.type :String, :[], '(Regexp, String) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] -RDL.type :String, :[], '(String) -> ``output_type(trec, targs, :[], "String")``', effect: [:+, :+] +RDL.type :String, :[], '(Integer, ?Integer) -> ``output_type(trec, targs, :[], "String")``' +RDL.type :String, :[], '(Range or Regexp) -> ``output_type(trec, targs, :[], "String")``' +RDL.type :String, :[], '(Regexp, Integer) -> ``output_type(trec, targs, :[], "String")``' +RDL.type :String, :[], '(Regexp, String) -> ``output_type(trec, targs, :[], "String")``' +RDL.type :String, :[], '(String) -> ``output_type(trec, targs, :[], "String")``' RDL.type :String, :ascii_only?, '() -> ``output_type(trec, targs, :ascii_only?, "%bool")``' RDL.type :String, :b, '() -> ``output_type(trec, targs, :b, "String")``' RDL.type :String, :bytes, '() -> ``output_type(trec, targs, :bytes, "Array")``' @@ -138,7 +138,7 @@ def String.cap_down_output(trec, meth) end end -RDL.type String, 'self.cap_down_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.cap_down_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type" RDL.type :String, :casecmp, '(String) -> ``output_type(trec, targs, :casecmp, "Integer")``' RDL.type :String, :center, '(Integer, ?String) -> ``output_type(trec, targs, :center, "String")``' @@ -164,7 +164,7 @@ def String.chop_output(trec) end end -RDL.type String, 'self.chop_output', "(RDL::Type::Type) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.chop_output', "(RDL::Type::Type) -> RDL::Type::Type" RDL.type :String, :chr, '() -> ``output_type(trec, targs, :chr, "String")``' RDL.type :String, :clear, '() -> ``clear_output(trec)``' @@ -180,7 +180,7 @@ def String.clear_output(trec) end end -RDL.type String, 'self.clear_output', "(RDL::Type::Type) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.clear_output', "(RDL::Type::Type) -> RDL::Type::Type" RDL.type :String, :codepoints, '() -> ``output_type(trec, targs, :codepoints, "Array")``' RDL.type :String, :concat, '(Integer or Object) -> ``append_output(trec, targs)``' @@ -221,7 +221,7 @@ def String.clear_output(trec) RDL.type :String, :gsub!, '(Regexp or String) -> ``string_promote!(trec); RDL::Type::NominalType.new(Enumerator)``', wrap: false RDL.type :String, :hash, '() -> Integer' RDL.type :String, :hex, '() -> ``output_type(trec, targs, :getbyte, "Integer")``' -RDL.type :String, :include?, '(String) -> ``output_type(trec, targs, :include?, "%bool")``', effect: [:+, :+] +RDL.type :String, :include?, '(String) -> ``output_type(trec, targs, :include?, "%bool")``' RDL.type :String, :index, '(Regexp or String, ?Integer) -> ``output_type(trec, targs, :index, "Integer")``' RDL.type :String, :replace, '(String) -> ``replace_output(trec, targs)``' @@ -242,7 +242,7 @@ def String.replace_output(trec, targs) end end -RDL.type String, 'self.replace_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.replace_output', "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL.type :String, :insert, '(Integer, String) -> String' ## TODO @@ -265,7 +265,7 @@ def String.insert_output(trec, targs) end end -RDL.type String, 'self.insert_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.insert_output', "(RDL::Type::Type, Array) -> RDL::Type::Type" RDL.type :String, :inspect, '() -> ``output_type(trec, targs, :inspect, "String")``' RDL.type :String, :intern, '() -> ``output_type(trec, targs, :intern, "Symbol")``' @@ -296,7 +296,7 @@ def String.lrstrip_output(trec, meth) end end -RDL.type String, 'self.lrstrip_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.lrstrip_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type" RDL.type :String, :match, '(Regexp or String) -> MatchData' RDL.type :String, :match, '(Regexp or String, Integer) -> MatchData' @@ -319,7 +319,7 @@ def String.mutate_output(trec, meth) end end -RDL.type String, 'self.mutate_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type", effect: [:+, :+] +RDL.type String, 'self.mutate_output', "(RDL::Type::Type, Symbol) -> RDL::Type::Type" RDL.type :String, :oct, '() -> ``output_type(trec, targs, :oct, "Integer")``' @@ -348,11 +348,11 @@ def String.mutate_output(trec, meth) RDL.type :String, :slice!, '(Regexp, Integer) -> ``string_promote!(trec)``' RDL.type :String, :slice!, '(Regexp, String) -> ``string_promote!(trec)``' RDL.type :String, :slice!, '(String) -> ``string_promote!(trec)``' -RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> ``output_type(trec, targs, :split, "Array")``', effect: [:+, :+] -RDL.type :String, :split, '(?Integer) -> ``output_type(trec, targs, :split, "Array")``', effect: [:+, :+] +RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> ``output_type(trec, targs, :split, "Array")``' +RDL.type :String, :split, '(?Integer) -> ``output_type(trec, targs, :split, "Array")``' RDL.type :String, :squeeze, '() -> ``output_type(trec, targs, :squeeze, "String")``' RDL.type :String, :squeeze!, '() -> ``mutate_output(trec, :squeeze!)``' -RDL.type :String, :start_with?, '(* String) -> ``output_type(trec, targs, :start_with?, "%bool")``', effect: [:+, :+] +RDL.type :String, :start_with?, '(* String) -> ``output_type(trec, targs, :start_with?, "%bool")``' RDL.type :String, :strip, '() -> ``output_type(trec, targs, :strip, "String")``' RDL.type :String, :strip!, '() -> ``mutate_output(trec, :strip!)``' RDL.type :String, :sub, '(Regexp or String, String or Hash) -> ``output_type(trec, targs, :sub, "String")``', wrap: false # Can't wrap these, since they mess with $1 etc @@ -367,9 +367,9 @@ def String.mutate_output(trec, meth) RDL.type :String, :to_f, '() -> ``output_type(trec, targs, :to_f, "Float")``' RDL.type :String, :to_i, '(?Integer) -> ``output_type(trec, targs, :to_i, "Integer")``' RDL.type :String, :to_r, '() -> Rational' -RDL.type :String, :to_s, '() -> self', effect: [:+, :+] +RDL.type :String, :to_s, '() -> self' RDL.type :String, :to_str, '() -> self' -RDL.type :String, :to_sym, '() -> ``output_type(trec, targs, :to_sym, "Symbol")``', effect: [:+, :+] +RDL.type :String, :to_sym, '() -> ``output_type(trec, targs, :to_sym, "Symbol")``' RDL.type :String, :tr, '(String, String) -> ``output_type(trec, targs, :tr, "String")``' RDL.type :String, :tr!, '(String, String) -> ``string_promote!(trec)``' RDL.type :String, :tr_s, '(String, String) -> ``output_type(trec, targs, :tr_s, "String")``' @@ -405,14 +405,14 @@ def String.mutate_output(trec, meth) RDL.type :String, :+, '(String) -> String' RDL.type :String, :<<, '(Object) -> String' RDL.type :String, :<=>, '(String other) -> Integer or nil ret' -RDL.type :String, :==, '(%any) -> %bool', effect: [:+, :+] +RDL.type :String, :==, '(%any) -> %bool' RDL.type :String, :===, '(%any) -> %bool' RDL.type :String, :=~, '(Object) -> Integer or nil', wrap: false # Wrapping this messes up $1 etc -RDL.type :String, :[], '(Integer, ?Integer) -> String or nil', effect: [:+, :+] -RDL.type :String, :[], '(Range or Regexp) -> String or nil', effect: [:+, :+] -RDL.type :String, :[], '(Regexp, Integer) -> String or nil', effect: [:+, :+] -RDL.type :String, :[], '(Regexp, String) -> String or nil', effect: [:+, :+] -RDL.type :String, :[], '(String) -> String or nil', effect: [:+, :+] +RDL.type :String, :[], '(Integer, ?Integer) -> String or nil' +RDL.type :String, :[], '(Range or Regexp) -> String or nil' +RDL.type :String, :[], '(Regexp, Integer) -> String or nil' +RDL.type :String, :[], '(Regexp, String) -> String or nil' +RDL.type :String, :[], '(String) -> String or nil' RDL.type :String, :ascii_only?, '() -> %bool' RDL.type :String, :b, '() -> String' RDL.type :String, :bytes, '() -> Array' # TODO: bindings to parameterized (vars) @@ -466,7 +466,7 @@ def String.mutate_output(trec, meth) RDL.type :String, :gsub!, '(Regexp or String) -> Enumerator', wrap: false RDL.type :String, :hash, '() -> Integer' RDL.type :String, :hex, '() -> Integer' -RDL.type :String, :include?, '(String) -> %bool', effect: [:+, :+] +RDL.type :String, :include?, '(String) -> %bool' RDL.type :String, :index, '(Regexp or String, ?Integer) -> Integer or nil' RDL.type :String, :replace, '(String) -> String' RDL.type :String, :insert, '(Integer, String) -> String' @@ -504,11 +504,11 @@ def String.mutate_output(trec, meth) RDL.type :String, :slice!, '(Regexp, Integer) -> String or nil' RDL.type :String, :slice!, '(Regexp, String) -> String or nil' RDL.type :String, :slice!, '(String) -> String or nil' -RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> Array', effect: [:+, :+] -RDL.type :String, :split, '(?Integer) -> Array', effect: [:+, :+] +RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> Array' +RDL.type :String, :split, '(?Integer) -> Array' RDL.type :String, :squeeze, '(?String) -> String' RDL.type :String, :squeeze!, '(?String) -> String' -RDL.type :String, :start_with?, '(* String) -> %bool', effect: [:+, :+] +RDL.type :String, :start_with?, '(* String) -> %bool' RDL.type :String, :strip, '() -> String' RDL.type :String, :strip!, '() -> String' RDL.type :String, :sub, '(Regexp or String, String or Hash) -> String', wrap: false # Can't wrap these, since they mess with $1 etc @@ -523,9 +523,9 @@ def String.mutate_output(trec, meth) RDL.type :String, :to_f, '() -> Float' RDL.type :String, :to_i, '(?Integer) -> Integer' RDL.type :String, :to_r, '() -> Rational' -RDL.type :String, :to_s, '() -> String', effect: [:+, :+] +RDL.type :String, :to_s, '() -> String' RDL.type :String, :to_str, '() -> self' -RDL.type :String, :to_sym, '() -> Symbol', effect: [:+, :+] +RDL.type :String, :to_sym, '() -> Symbol' RDL.type :String, :tr, '(String, String) -> String' RDL.type :String, :tr!, '(String, String) -> String or nil' RDL.type :String, :tr_s, '(String, String) -> String' diff --git a/lib/types/core/symbol.rb b/lib/types/core/symbol.rb index 6cbf3b09..461c4296 100644 --- a/lib/types/core/symbol.rb +++ b/lib/types/core/symbol.rb @@ -2,7 +2,7 @@ RDL.type :Symbol, 'self.all_symbols', '() -> Array' RDL.type :Symbol, :<=>, '(Symbol other) -> Integer or nil' -RDL.type :Symbol, :==, '(Object) -> %bool', effect: [:+, :+] +RDL.type :Symbol, :==, '(Object) -> %bool' RDL.type :Symbol, :=~, '(Object) -> Integer or nil' RDL.type :Symbol, :[], '(Integer idx) -> String' RDL.type :Symbol, :[], '(Integer b, Integer n) -> String' @@ -22,6 +22,6 @@ RDL.rdl_alias :Symbol, :slice, :[] RDL.type :Symbol, :swapcase, '() -> Symbol' RDL.type :Symbol, :to_proc, '() -> Proc' # TODO proc -RDL.type :Symbol, :to_s, "() -> String", effect: [:+, :+] -RDL.type :Symbol, :to_sym, "() -> self", effect: [:+, :+] +RDL.type :Symbol, :to_s, "() -> String" +RDL.type :Symbol, :to_sym, "() -> self" RDL.type :Symbol, :upcase, '() -> Symbol' diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index 67143ea1..a325613d 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -17,11 +17,11 @@ class ActiveRecord::Base type :save!, '(?{ validate: %bool }) -> %bool', wrap: false type 'self.transaction', '() {() -> u} -> u', wrap: false - type RDL::Globals, 'self.ar_db_schema', "() -> Hash<%any, RDL::Type::GenericType>", wrap: false, effect: [:+, :+] - type String, :singularize, "() -> String", wrap: false, effect: [:+, :+] - type String, :camelize, "() -> String", wrap: false, effect: [:+, :+] - type String, :pluralize, "() -> String", wrap: false, effect: [:+, :+] - type String, :underscore, "() -> String", wrap: false, effect: [:+, :+] + type RDL::Globals, 'self.ar_db_schema', "() -> Hash<%any, RDL::Type::GenericType>", wrap: false + type String, :singularize, "() -> String", wrap: false + type String, :camelize, "() -> String", wrap: false + type String, :pluralize, "() -> String", wrap: false + type String, :underscore, "() -> String", wrap: false type Object, :try, "(Symbol) -> ``try_output(trec, targs)``", wrap: false @@ -71,7 +71,7 @@ def self.access_output(trec, targs) raise 'unexpected type' end end - RDL.type ActiveRecord::Base, 'self.access_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type ActiveRecord::Base, 'self.access_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.uc_first_arg(trec) case trec @@ -85,7 +85,7 @@ def self.uc_first_arg(trec) raise "unexpected type" end end - RDL.type ActiveRecord::Base, 'self.uc_first_arg', "(RDL::Type::Type) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type ActiveRecord::Base, 'self.uc_first_arg', "(RDL::Type::Type) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code def self.uc_second_arg(trec, targs) case trec @@ -99,7 +99,7 @@ def self.uc_second_arg(trec, targs) raise "unexpected type" end end - RDL.type ActiveRecord::Base, 'self.uc_second_arg', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type ActiveRecord::Base, 'self.uc_second_arg', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code end @@ -344,12 +344,12 @@ def self.rec_to_nominal(t) end end end - RDL.type DBType, 'self.rec_to_nominal', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.rec_to_nominal', "(RDL::Type::Type) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.rec_to_array(trec) RDL::Type::GenericType.new(RDL::Globals.types[:array], rec_to_nominal(trec)) end - RDL.type DBType, 'self.rec_to_array', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.rec_to_array', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code ## given a receiver type in various kinds of query calls, returns the accepted finite hash type input, ## or a union of types if the receiver represents joined tables. @@ -405,7 +405,7 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false, include_assocs: raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}." end end - RDL.type DBType, 'self.rec_to_schema_type', "(RDL::Type::Type, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.rec_to_schema_type', "(RDL::Type::Type, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code ## turns a given table name into the appropriate finite hash type based on table schema, with optional or top-type values ## [+ tname +] is the table name as a symbol @@ -445,15 +445,15 @@ def self.table_name_to_schema_type(tname, check_col, takes_array=false, include_ end RDL::Type::FiniteHashType.new(RDL.type_cast(h, "Hash<%any, RDL::Type::Type>", force: true), nil) end - RDL.type DBType, 'self.table_name_to_schema_type', "(Symbol, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.table_name_to_schema_type', "(Symbol, %bool, ?%bool) -> RDL::Type::FiniteHashType", wrap: false, typecheck: :type_code def self.where_input_type(trec, targs) handle_sql_strings(trec, targs) if targs[0].is_a?(RDL::Type::PreciseStringType) && !targs[1].nil? && !targs[1].kind_of_var_input? tschema = rec_to_schema_type(trec, true, true) return RDL::Type::UnionType.new(tschema, RDL::Globals.types[:string], RDL::Globals.types[:array]) ## no indepth checking for string or array cases end - RDL.type Object, 'self.handle_sql_strings', "(RDL::Type::Type, Array) -> %any", wrap: false, effect: [:+, :+] - RDL.type DBType, 'self.where_input_type', "(RDL::Type::Type, Array) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type Object, 'self.handle_sql_strings', "(RDL::Type::Type, Array) -> %any", wrap: false + RDL.type DBType, 'self.where_input_type', "(RDL::Type::Type, Array) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code def self.find_input_type(trec, targs) handle_sql_strings(trec, targs) if targs[0].is_a? RDL::Type::PreciseStringType @@ -480,13 +480,13 @@ def self.where_noarg_output_type(trec) raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type #{trec}." end end - RDL.type DBType, 'self.where_noarg_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.where_noarg_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code def self.not_input_type(trec, targs) tschema = rec_to_schema_type(trec, true) return RDL::Type::UnionType.new(tschema, RDL::Globals.types[:string], RDL::Globals.types[:array]) ## no indepth checking for string or array cases end - RDL.type DBType, 'self.not_input_type', "(RDL::Type::Type, Array) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.not_input_type', "(RDL::Type::Type, Array) -> RDL::Type::UnionType", wrap: false, typecheck: :type_code def self.exists_input_type(trec, targs) raise "Unexpected number of arguments to ActiveRecord::Base#exists?." unless targs.size <= 1 @@ -500,7 +500,7 @@ def self.exists_input_type(trec, targs) end return RDL::Type::OptionalType.new(RDL::Type::UnionType.new(RDL::Globals.types[:integer], RDL::Globals.types[:string], typ)) end - RDL.type DBType, 'self.exists_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.exists_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.find_output_type(trec, targs) @@ -536,7 +536,7 @@ def self.find_output_type(trec, targs) DBType.rec_to_nominal(trec) end end - RDL.type DBType, 'self.find_output_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.find_output_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.joins_one_input_type(trec, targs) return RDL::Globals.types[:top] unless targs.size == 1 ## trivial case, won't be matched @@ -578,7 +578,7 @@ def self.joins_one_input_type(trec, targs) raise RDL::Typecheck::StaticTypeError, "Unexpected arg type #{arg0} in call to joins." end end - RDL.type DBType, 'self.joins_one_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.joins_one_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.joins_multi_input_type(trec, targs) return RDL::Globals.types[:top] unless targs.size > 1 ## trivial case, won't be matched @@ -587,7 +587,7 @@ def self.joins_multi_input_type(trec, targs) } return targs[0] ## since this method is called as first argument in type end - RDL.type DBType, 'self.joins_multi_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.joins_multi_input_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.associated_with?(rec, sym) tschema = RDL::Globals.ar_db_schema[rec.to_s.to_sym] @@ -611,7 +611,7 @@ def self.associated_with?(rec, sym) } return false end - RDL.type DBType, 'self.associated_with?', "(Class or Symbol or String, Symbol) -> %bool", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.associated_with?', "(Class or Symbol or String, Symbol) -> %bool", wrap: false, typecheck: :type_code def self.get_joined_args(targs) arg_types = RDL.type_cast([], "Array", force: true) @@ -640,7 +640,7 @@ def self.get_joined_args(targs) raise "oops, didn't expect to get here." end end - RDL.type DBType, 'self.get_joined_args', "(Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.get_joined_args', "(Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.joins_output(trec, targs) arg_type = get_joined_args(targs) @@ -666,7 +666,7 @@ def self.joins_output(trec, targs) ret = RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), jt) return ret end - RDL.type DBType, 'self.joins_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.joins_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", wrap: false, typecheck: :type_code def self.plus_output_type(trec, targs) typs = RDL.type_cast([], "Array", force: true) @@ -693,7 +693,7 @@ def self.plus_output_type(trec, targs) } RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), RDL::Type::UnionType.new(*typs)) end - RDL.type DBType, 'self.plus_output_type', "(RDL::Type::Type, Array) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.plus_output_type', "(RDL::Type::Type, Array) -> RDL::Type::GenericType", wrap: false, typecheck: :type_code def self.count_input(trec, targs) hash_type = rec_to_schema_type(trec, true)## Bug found here. orginally had: rec_to_schema_type(trec, targs).elts @@ -710,7 +710,7 @@ def self.count_input(trec, targs) } return RDL::Type::OptionalType.new(RDL::Type::UnionType.new(*typs)) end - RDL.type DBType, 'self.count_input', "(RDL::Type::Type, Array) -> RDL::Type::OptionalType", wrap: false, typecheck: :type_code, effect: [:+, :+] + RDL.type DBType, 'self.count_input', "(RDL::Type::Type, Array) -> RDL::Type::OptionalType", wrap: false, typecheck: :type_code def self.each_block_arg(trec) diff --git a/lib/types/sequel/comp_types.rb b/lib/types/sequel/comp_types.rb index 9d683e43..6c84a98e 100644 --- a/lib/types/sequel/comp_types.rb +++ b/lib/types/sequel/comp_types.rb @@ -6,7 +6,7 @@ class SequelDB type :transaction, "() { () -> %any } -> self", wrap: false - type RDL::Globals, 'self.seq_db_schema', "()-> Hash", wrap: false, effect: [:+, :+] + type RDL::Globals, 'self.seq_db_schema', "()-> Hash", wrap: false def self.gen_output_type(targ) case targ @@ -21,7 +21,7 @@ def self.gen_output_type(targ) end end - RDL.type SequelDB, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type SequelDB, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false end module Sequel::Mysql2; end @@ -36,7 +36,7 @@ class Sequel::Mysql2::Database type :transaction, "() { () -> %any } -> self", wrap: false - type RDL::Globals, 'self.seq_db_schema', "()-> Hash", wrap: false, effect: [:+, :+] + type RDL::Globals, 'self.seq_db_schema', "()-> Hash", wrap: false def self.gen_output_type(targ) case targ @@ -50,7 +50,7 @@ def self.gen_output_type(targ) raise "unexpected type #{targ.class}" end end - RDL.type Sequel::Mysql2::Database, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Sequel::Mysql2::Database, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false end @@ -71,13 +71,13 @@ def self.gen_output_type(targ) raise "unexpected type" end end - RDL.type Sequel, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Sequel, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false def self.qualify_output_type(targs) raise "unexpected types" unless targs.all? { |a| a.is_a?(RDL::Type::SingletonType) } RDL::Type::GenericType.new(RDL::Type::NominalType.new(SeqQualIdent), targs[0], targs[1]) end - RDL.type Sequel, 'self.qualify_output_type', "(Array) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Sequel, 'self.qualify_output_type', "(Array) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false end class SeqIdent extend RDL::Annotate @@ -98,7 +98,7 @@ def self.gen_output_type(trec, targ) raise "unexpected trec type" end end - RDL.type SeqIdent, 'self.gen_output_type', "(RDL::Type::Type, RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type SeqIdent, 'self.gen_output_type', "(RDL::Type::Type, RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false end @@ -122,7 +122,7 @@ class Table def self.get_schema(hash) hash.select { |key, val| ![:__last_joined, :__all_joined, :__selected, :__orm].member?(key) } end - RDL.type Table, 'self.get_schema', "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_schema', "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", typecheck: :type_code, wrap: false def self.get_all_joined(t) case t @@ -142,7 +142,7 @@ def self.get_all_joined(t) raise "unexpected type #{t} in __all_joined clause" end end - RDL.type Table, 'self.get_all_joined', "(RDL::Type::Type) -> Array", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_all_joined', "(RDL::Type::Type) -> Array", typecheck: :type_code, wrap: false def self.join_ret_type(trec, targs) raise RDL::Typecheck::StaticTypeError, "Unexpected number of arguments to `join`." unless targs.size == 2 @@ -202,7 +202,7 @@ def self.join_ret_type(trec, targs) raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type in call to `join`." end end - RDL.type Table, 'self.join_ret_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.join_ret_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false type :insert, "(``insert_arg_type(trec, targs)``) -> Integer", wrap: false @@ -254,13 +254,13 @@ def self.order_input(trec, targs) raise "unexpected type #{trec}" end end - RDL.type Table, 'self.order_input', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.order_input', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.map_block_input(trec) schema = get_schema(RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts) RDL::Type::FiniteHashType.new(schema, nil) end - RDL.type Table, 'self.map_block_input', "(RDL::Type::GenericType) -> RDL::Type::FiniteHashType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.map_block_input', "(RDL::Type::GenericType) -> RDL::Type::FiniteHashType", typecheck: :type_code, wrap: false def self.all_output(trec) f = first_output(trec) @@ -291,7 +291,7 @@ def self.all_output(trec) return RDL::Type::GenericType.new(RDL::Globals.types[:array], f) end end - RDL.type Table, 'self.all_output', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.all_output', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false def self.select_map_output(trec, targs, meth) raise "VarargType not supported for select_map." if meth == :select_map && targs[0].is_a?(RDL::Type::VarargType) @@ -357,7 +357,7 @@ def self.select_map_output(trec, targs, meth) raise 'unexpected type #{trec}' end end - RDL.type Table, 'self.select_map_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.select_map_output', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false def self.get_input(trec) case trec @@ -368,12 +368,12 @@ def self.get_input(trec) raise 'unexpected type #{trec}' end end - RDL.type Table, 'self.get_input', "(RDL::Type::Type) -> RDL::Type::UnionType", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_input', "(RDL::Type::Type) -> RDL::Type::UnionType", typecheck: :type_code, wrap: false def self.get_output(trec, targs) RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[RDL.type_cast(targs[0], "RDL::Type::SingletonType", force: true).val] end - RDL.type Table, 'self.get_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_output', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.first_output(trec) case trec @@ -389,7 +389,7 @@ def self.first_output(trec) raise 'unexpected type #{trec}' end end - RDL.type Table, 'self.first_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.first_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.insert_arg_type(trec, targs, tuple=false) raise "Cannot insert/update for joined table." if RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[:__all_joined].is_a?(RDL::Type::UnionType) @@ -399,7 +399,7 @@ def self.insert_arg_type(trec, targs, tuple=false) schema_arg_type(trec, targs, :insert) end end - RDL.type Table, 'self.insert_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.insert_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.import_arg_type(trec, targs) raise "Cannot import for joined table." if RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[:__all_joined].is_a?(RDL::Type::UnionType) @@ -419,7 +419,7 @@ def self.import_arg_type(trec, targs) end return targs[0] end - RDL.type Table, 'self.import_arg_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.import_arg_type', "(RDL::Type::Type, Array) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.get_nominal_where_type(type) ## `where` can accept arrays/tuples and tables with a single column selected @@ -449,7 +449,7 @@ def self.get_nominal_where_type(type) end end - RDL.type Table, 'self.get_nominal_where_type', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.get_nominal_where_type', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.schema_arg_type(trec, targs, meth) return RDL::Type::NominalType.new(Hash) if targs.size != 1 @@ -495,7 +495,7 @@ def self.schema_arg_type(trec, targs, meth) end end - RDL.type Table, 'self.schema_arg_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.schema_arg_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false ## [+ column_name +] is a symbol or SeqQualIdent Generic type of the qualified column name, e.g. :person__age ## [+ all_joined +] is an array of symbols of joined tables (must check if qualifying table is a member) @@ -526,7 +526,7 @@ def self.check_qual_column(column_name, all_joined, type=nil) end return qual_table_schema[qual_column] end - RDL.type Table, 'self.check_qual_column', "(Symbol or RDL::Type::GenericType, Array, ?RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.check_qual_column', "(Symbol or RDL::Type::GenericType, Array, ?RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.schema_arg_tuple_type(trec, targs, meth) return RDL::Type::NominalType.new(Array) if targs.size != 2 @@ -584,7 +584,7 @@ def self.schema_arg_tuple_type(trec, targs, meth) raise 'not yet implemented' end end - RDL.type Table, 'self.schema_arg_tuple_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.schema_arg_tuple_type', "(RDL::Type::Type, Array, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.where_arg_type(trec, targs, tuple=false) return targs[0] if targs[0] == RDL::Globals.types[:string] @@ -616,7 +616,7 @@ def self.where_arg_type(trec, targs, tuple=false) return ret end end - RDL.type Table, 'self.where_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.where_arg_type', "(RDL::Type::Type, Array, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false def self.unique_ids?(ids, joined) j = get_all_joined(joined) @@ -634,6 +634,6 @@ def self.unique_ids?(ids, joined) } return true end - RDL.type Table, 'self.unique_ids?', "(Array, RDL::Type::Type) -> %bool", typecheck: :type_code, wrap: false, effect: [:+, :+] + RDL.type Table, 'self.unique_ids?', "(Array, RDL::Type::Type) -> %bool", typecheck: :type_code, wrap: false end From f50f60ff5a4ffbb5f3e4dd80c15f3155354ffa80 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Tue, 12 May 2020 13:34:21 -0400 Subject: [PATCH 057/124] * Add wrap log area * If one method aliases another, record this during inference instead of raising an exception * Add an internal error error message * Add RDL.alias method; move rdl_alias body into RDL.alias and have rdl_alias call RDL.alias --- lib/rdl/config.rb | 3 ++- lib/rdl/typecheck.rb | 19 +++++++++++++------ lib/rdl/wrap.rb | 42 ++++++++++++++++++++++++------------------ 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index acf7b3bb..cf66bbf6 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -42,7 +42,8 @@ def self.reset(c=RDL::Config.instance) c.disable_log_colors = false c.log_levels = { typecheck: :info, - inference: :info + inference: :info, + wrap: :critical, } end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 4ff7aef0..1aa3a60d 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -285,7 +285,12 @@ def self._infer(klass, meth) raise RuntimeError, "Unexpected ast type #{ast.type}" end - raise RuntimeError, "Method #{name} defined where method #{meth} expected" if name.to_sym != meth + if name.to_sym != meth + RDL::Logging.log :inference, :info, "Aliasing #{meth} to #{name}" + RDL.alias klass, meth, name + return + end + error :internal, "Method #{name} defined where method #{meth} expected", ast if name.to_sym != meth context_types = RDL::Globals.info.get(klass, meth, :context_types) if RDL::Util.has_singleton_marker(klass) @@ -345,7 +350,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil) else raise RuntimeError, "Unexpected ast type #{ast.type}" end - raise RuntimeError, "Method #{name} defined where method #{meth} expected" if name.to_sym != meth + error :internal, "Method #{name} defined where method #{meth} expected", ast if name.to_sym != meth context_types = RDL::Globals.info.get(klass, meth, :context_types) types.each { |type| if RDL::Util.has_singleton_marker(klass) @@ -378,7 +383,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil) end until old_captured == scope[:captured] error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || meth == :initialize ||RDL::Type::Type.leq(body_type, type.ret, ast: ast) } - + if RDL::Config.instance.check_comp_types new_meth = WrapCall.rewrite(ast) # rewrite ast to insert dynamic checks RDL::Util.silent_warnings { RDL::Util.to_class(klass).class_eval(new_meth) } # redefine method in the same class @@ -575,7 +580,7 @@ def self._tc(scope, env, e) when :complex, :rational # constants [env, RDL::Type::NominalType.new(e.children[0].class)] when :int, :float - if RDL::Config.instance.number_mode + if RDL::Config.instance.number_mode [env, RDL::Type::NominalType.new(Integer)] else [env, RDL::Type::SingletonType.new(e.children[0])] @@ -916,7 +921,7 @@ def self._tc(scope, env, e) # children[1] = method name, a symbol # children [2..] = actual args return tc_var_type(scope, env, e) if (e.children[0].nil? || is_RDL(e.children[0])) && e.children[1] == :var_type - return tc_type_cast(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil? + return tc_type_cast(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil? return tc_note_type(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :note_type return tc_instantiate!(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :instantiate! envi = env @@ -2305,7 +2310,7 @@ def self.lookup(scope, klass, name, e, make_unknown: RDL::Config.instance.use_un end if scope[:context_types] scope[:context_types].each { |k, m, t| - return t if k == klass && m = name + return t if k == klass && m = name } end t = RDL::Globals.info.get_with_aliases(klass, name, :type) @@ -2574,6 +2579,8 @@ def message infer_constraint_error: "%s constraint generated here.", + internal: "internal error: %s", + empty: "", } end diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index bbfd57e7..dcb0b68b 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -245,6 +245,7 @@ def self.do_method_added(the_self, sing, klass, meth) else loc = the_self.instance_method(meth).source_location end + RDL::Logging.log :wrap, :trace, "Method #{klass}##{meth} added" RDL::Globals.info.set(klass, meth, :source_location, loc) # Apply any deferred contracts and reset list @@ -672,25 +673,8 @@ def attr_writer_type(*args) nil end - # Aliases contracts for meth_old and meth_new. Currently, this must - # be called for any aliases or they will not be wrapped with - # contracts. Only creates aliases in the current class. def rdl_alias(klass=self, new_name, old_name) - klass = klass.to_s - klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main") - RDL::Globals.aliases[klass] = {} unless RDL::Globals.aliases[klass] - if RDL::Globals.aliases[klass][new_name] - raise RuntimeError, - "Tried to alias #{new_name}, already aliased to #{RDL::Globals.aliases[klass][new_name]}" - end - RDL::Globals.aliases[klass][new_name] = old_name - - if Module.const_defined?(klass) && RDL::Util.to_class(klass).method_defined?(new_name) - RDL::Wrap.wrap(klass, new_name) - else - RDL::Globals.to_wrap << [klass, old_name] - end - nil + RDL.alias klass, new_name, old_name end # [+ klass +] is the class whose type parameters to set; self if omitted @@ -1093,6 +1077,28 @@ def self.deinstantiate!(obj) obj end + # Aliases contracts for meth_old and meth_new in klass. Currently, this + # must be called for any aliases or they will not be wrapped with + # contracts. + def self.alias(klass, new_name, old_name) + klass = klass.to_s + klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main") + RDL::Globals.aliases[klass] = {} unless RDL::Globals.aliases[klass] + if RDL::Globals.aliases[klass][new_name] + raise RuntimeError, + "Tried to alias #{new_name}, already aliased to #{RDL::Globals.aliases[klass][new_name]}" + end + RDL::Globals.aliases[klass][new_name] = old_name + + if Module.const_defined?(klass) && RDL::Util.to_class(klass).method_defined?(new_name) + RDL::Wrap.wrap(klass, new_name) + else + RDL::Globals.to_wrap << [klass, old_name] + end + nil + end + + private def self.add_ar_assoc(hash, aname, aklass) kl_type = RDL::Type::SingletonType.new(aklass) From 5011ae865a9c531f89dc4dd313865d1bf2a96331 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Tue, 12 May 2020 18:01:12 -0400 Subject: [PATCH 058/124] Allow A <= B if B is an ancestor of A and A and B have the same number of type params --- lib/rdl/types/type.rb | 4 +++- test/test_le.rb | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index de3eb415..194aedd4 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -356,7 +356,9 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con # with wrong number of parameters but never checked against # instantiated instances raise TypeError, "No type parameters defined for #{left.base.name}" unless formals - return false unless left.base == right.base + return false unless (left.base == right.base || + (left.base.klass.ancestors.member?(right.base.klass) && + left.params.length == right.params.length)) return variance.zip(left.params, right.params).all? { |v, tl, tr| case v when :+ diff --git a/test/test_le.rb b/test/test_le.rb index d5d2c01b..31efb00d 100644 --- a/test/test_le.rb +++ b/test/test_le.rb @@ -215,6 +215,15 @@ def test_finite_hash assert (not (tt("{x: ?Integer}") <= tt("{x: Integer}"))) end + def test_generic + RDL.type_params :Array, [:t], :all? + RDL.type_params :Enumerable, [:t], :all? + + assert (tt("Array") <= tt("Array")) + assert (not (tt("Array") <= tt("Array"))) + assert (tt("Array") <= tt("Enumerable")) + end + def test_method tss = MethodType.new([RDL::Globals.types[:string]], nil, RDL::Globals.types[:string]) tso = MethodType.new([RDL::Globals.types[:string]], nil, RDL::Globals.types[:object]) From 2066e8ea216b85481f516c4a3a720299b4fb9cdd Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Tue, 12 May 2020 20:11:53 -0400 Subject: [PATCH 059/124] Types for PrettyPrint --- lib/types/core/prettyprint.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 lib/types/core/prettyprint.rb diff --git a/lib/types/core/prettyprint.rb b/lib/types/core/prettyprint.rb new file mode 100644 index 00000000..ddc8f301 --- /dev/null +++ b/lib/types/core/prettyprint.rb @@ -0,0 +1,11 @@ +RDL.type :PrettyPrint, 'self.format', '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> PrettyPrint' +RDL.type :PrettyPrint, 'self.singleline_format', '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> PrettyPrint' +RDL.type :PrettyPrint, :initialize, '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> PrettyPrint' +RDL.type :PrettyPrint, :break_outmost_groups, '() -> %bot' +RDL.type :PrettyPrint, :breakable, '(?String sep, ?Integer width) -> %bot' +RDL.type :PrettyPrint, :current_group, '() -> %any' +RDL.type :PrettyPrint, :fill_breakable, '(?String sep, ?Integer width) -> %bot' +RDL.type :PrettyPrint, :flush, '() -> %bot' +RDL.type :PrettyPrint, :group_sub, '() -> %bot' +RDL.type :PrettyPrint, :nest, '(Integer indent) { (a) -> b } -> b' +RDL.type :PrettyPrint, :text, '(String obj, ?Integer width) -> %bot' From 85c9d869a15e3f956522b5ec009ff39c69481857 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 13 May 2020 17:16:24 -0400 Subject: [PATCH 060/124] new test case for when with blocks --- test/test_typecheck.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 93873eb2..84d59059 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -211,10 +211,11 @@ def setup @ts34 = RDL::Type::UnionType.new(@ts3, @t4) @t3n = RDL::Type::UnionType.new(@t3, RDL::Globals.types[:nil]) @t4n = RDL::Type::UnionType.new(@t4, RDL::Globals.types[:nil]) - @env = RDL::Typecheck::Env.new(self: tt("TestTypecheck")) + @env = RDL::Typecheck::Env.new(self: tt("TestTypecheck"), captured: Hash.new) @scopef = { tret: RDL::Globals.types[:integer] } @tfs = RDL::Type::UnionType.new(RDL::Globals.types[:integer], RDL::Globals.types[:string]) @scopefs = { tret: @tfs, tblock: nil } + @scopec = { captured: Hash.new } ### Uncomment below to see test names. Useful for hanging tests. #puts "Start #{@NAME}" end @@ -1039,6 +1040,18 @@ def test_when assert do_tc("x = 6; case when (x = 3) then 'foo' when (x = 4) then 'foo' else x = 5 end; x", env: @env) <= @t345 end + def test_when_block + skip + RDL.type TestTypecheck, :m1, '(X) -> %any' + RDL.type TestTypecheck, :m2, '(Y) -> %any' + RDL.type TestTypecheck, :m3, '() { () -> %any } -> %any' + assert do_tc("x = _any_object; case x when X then m1(x) when Y then m2(x) end", env: @env) + assert_raises(RDL::Typecheck::StaticTypeError) { + assert do_tc("x = _any_object; case x when Y then m1(x) when Y then m2(x) end", env: @env) + } + assert do_tc("x = _any_object; case x when X then m3() { m1(x) } when Y then m3() { m2(x) } end", env: @env, scope: @scopec) + end + def test_while_until # TODO these don't do a great job checking control flow assert do_tc("while true do end") <= RDL::Globals.types[:nil] From 2813f48786327b272e3521cb68ad4bd851f1ea1a Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Tue, 19 May 2020 13:30:32 -0400 Subject: [PATCH 061/124] Add a to_s method to Env, clarify test_when_block --- lib/rdl/typecheck.rb | 8 +++++++- test/test_typecheck.rb | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 1aa3a60d..f5822559 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -94,6 +94,10 @@ def ==(other) return @env == other.env end + def to_s + @env.to_a.map { |k,v| "#{k}: #{if v[:fixed] then '[fixed]' end}#{v[:type]}"}.join(", ") + end + # merges bindings in self with bindings in other, preferring bindings in other if there is a common key def merge(other) result = Env.new @@ -1398,7 +1402,9 @@ def self.tc_var(scope, env, kind, name, e) case kind when :lvar # local variable error :undefined_local_or_method, [name], e unless env.has_key? name - capture(scope, name, env[name].canonical, ast: e) if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name)) + if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name)) + capture(scope, name, env[name].canonical, ast: e) + end if scope[:captured] && scope[:captured].has_key?(name) then [env, scope[:captured][name]] else diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 84d59059..f2ce9cdb 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -1049,6 +1049,8 @@ def test_when_block assert_raises(RDL::Typecheck::StaticTypeError) { assert do_tc("x = _any_object; case x when Y then m1(x) when Y then m2(x) end", env: @env) } + assert do_tc("x = _any_object; case x when X then m1(x) when Y then m3() { m2(x) } end", env: @env, scope: @scopec) + # TODO: The next test case fails because `x` is captured in both blocks, giving it type `X or Y` in the second block assert do_tc("x = _any_object; case x when X then m3() { m1(x) } when Y then m3() { m2(x) } end", env: @env, scope: @scopec) end From 910806ca4d0c9e50a6bf865c46e272c54f1a385c Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 21 May 2020 10:12:58 -0400 Subject: [PATCH 062/124] add logging of var name/types in an app, query twin network against all names in an app --- lib/rdl/constraint.rb | 14 ++++++++++++++ lib/rdl/heuristics.rb | 25 ++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 032ccbc7..8eb93715 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,7 +1,14 @@ require 'csv' +module << RDL::Typecheck + ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. + attr_accessor :type_names_map +end + module RDL::Typecheck + @type_names_map = Hash.new [] + def self.resolve_constraints RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." RDL::Globals.constrained_types.each { |klass, name| @@ -102,6 +109,9 @@ def self.extract_var_sol(var, category) } =end @new_constraints = true if !new_cons.empty? + if typ.is_a?(RDL::Type::NominalType) || typ.is_a?(RDL::Type::GenericType) + @type_names_map[typ] = @type_names_map[typ] | [var.name.to_sym] + end return typ #sol = typ end @@ -147,6 +157,10 @@ def self.extract_var_sol(var, category) sol = var end + if typ.is_a?(RDL::Type::NominalType) || typ.is_a?(RDL::Type::GenericType) + @type_names_map[typ] = @type_names_map[typ] | [var.name.to_sym] + end + return sol end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index acb3a052..1c88644b 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -51,9 +51,30 @@ def self.struct_to_nominal(var_type) def self.twin_network_guess(var) return unless (var_type.category == :arg) || (var_type.category == :var) ## this rule only applies to args and (instance/class/global) variables name1 = var_type.name - name2 = "count" ## TODO: replace this with actual names from the current program + sols = [] + uri = URI "http://127.0.0.1:5000/" + RDL::Typecheck.type_names_map.each { |t, names| + params = { words: [name1] + names } + uri.query = URI.encode_www_form(params) + res = Net::HTTP.get_response(uri) + puts "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" + + sim_score = res.body.to_f + if sim_score > 0.8 + puts "Twin network found #{name1} and list #{names} have average similarity score of #{sim_score}." + puts "Adding #{t} as a potential solution." + sols << t + else + puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}." + end + } + + ## TODO: Is creating UnionType the right way to go? + return RDL::Type::UnionType.new(*sols).canonical + +=begin params = { in1: name1, in2: name2 } uri.query = URI.encode_www_form(params) @@ -73,6 +94,8 @@ def self.twin_network_guess(var) puts "Twin network found insufficient similarity score of #{sim_score} between #{name1} and #{name2}." return nil end +=end + end From a6784ee9ff4da9a56ea38ef4da52e44e2a8de1f7 Mon Sep 17 00:00:00 2001 From: Geoff Huston Date: Tue, 26 May 2020 19:22:07 -0400 Subject: [PATCH 063/124] added note_type test (#99) --- test/test_typecheck.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 84d59059..3670fa59 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -2138,4 +2138,22 @@ def uses_baz3(x, y) end } end + + # From https://stackoverflow.com/a/22777806 + def capture_stdout + stdout = $stdout + $stdout = StringIO.new + yield + $stdout.string + ensure + $stdout = stdout + end + + def test_note_type + output = capture_stdout do + do_tc "RDL.note_type Hash.new" + end + output = output.lines.map(&:chomp) + assert_equal "(string):1:15: note: Type is `Hash'", output[0] + end end From 5587375a682111863707a48aeb37ee7c2a110342 Mon Sep 17 00:00:00 2001 From: Geoff Huston Date: Tue, 26 May 2020 19:22:37 -0400 Subject: [PATCH 064/124] Infer filtering (#101) * Added filtering; changed up some logging * first pass of bringing source-file printing into RDL::Logging * log can optionally write to a file at its own log level * caching struct_to_nominal matching classes * changed infer_added filtering API --- lib/rdl/boot.rb | 2 + lib/rdl/config.rb | 11 ++- lib/rdl/constraint.rb | 29 ++++-- lib/rdl/heuristics.rb | 9 ++ lib/rdl/logging.rb | 200 ++++++++++++++++++++++++++++++++++++------ lib/rdl/typecheck.rb | 2 +- lib/rdl/types/type.rb | 8 ++ lib/rdl/wrap.rb | 29 +++++- rdl.gemspec | 1 + test/test_infer.rb | 1 + 10 files changed, 251 insertions(+), 41 deletions(-) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 15aa1773..8c3db110 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -4,6 +4,7 @@ require 'parser/current' #require 'method_source' require 'colorize' +require 'rake' module RDL end @@ -116,6 +117,7 @@ class << RDL::Globals # add accessors and readers for module variables attr_accessor :no_infer_meths attr_accessor :no_infer_files attr_accessor :infer_added + attr_accessor :infer_added_filter_dirs attr_accessor :module_mixees end diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index cf66bbf6..ead99b35 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -11,6 +11,7 @@ class RDL::Config attr_accessor :use_precise_string, :number_mode, :use_unknown_types, :infer_empties attr_accessor :continue_on_errors attr_accessor :log_levels, :disable_log_colors + attr_accessor :log_file, :log_file_levels def initialize RDL::Config.reset(self) @@ -43,7 +44,15 @@ def self.reset(c=RDL::Config.instance) c.log_levels = { typecheck: :info, inference: :info, - wrap: :critical, + heuristic: :info, + wrap: :critical + } + c.log_file = nil + c.log_file_levels = { + typecheck: :info, + inference: :info, + heuristic: :info, + wrap: :critical } end diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 032ccbc7..b9f04cb1 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -82,8 +82,12 @@ def self.extract_var_sol(var, category) ## Try each rule. Return first non-nil result. ## If no non-nil results, return original solution. ## TODO: check constraints. + heuristics_start_time = Time.now + RDL::Logging.log_header :heuristic, :debug, "Beginning Heuristics..." + RDL::Heuristic.rules.each { |name, rule| - #puts "Trying rule `#{name}` for variable #{var}." + start_time = Time.now + RDL::Logging.log :heuristic, :debug, "Trying rule `#{name}` for variable #{var}." typ = rule.call(var) new_cons = {} begin @@ -101,17 +105,26 @@ def self.extract_var_sol(var, category) } } =end + RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" @new_constraints = true if !new_cons.empty? + RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? + return typ #sol = typ end rescue RDL::Typecheck::StaticTypeError => e - RDL::Logging.log :typecheck, :debug_error, "Attempted to apply heuristic rule #{name} to var #{var}" - RDL::Logging.log :typecheck, :trace, "... but got the following error: #{e}" + RDL::Logging.log :heuristic, :debug_error, "Attempted to apply heuristic rule #{name} to var #{var}" + RDL::Logging.log :heuristic, :trace, "... but got the following error: #{e}" undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is + ensure + total_time = Time.now - start_time + RDL::Logging.log :hueristic, :debug, "Heuristic #{name} took #{total_time} to evaluate" end } + + heuristics_total_time = Time.now - heuristics_start_time + RDL::Logging.log_header :heuristic, :debug, "Evaluated heuristics in #{heuristics_total_time}" end ## out here, none of the heuristics applied. ## Try to use `sol` as solution -- there is a chance it will @@ -130,6 +143,7 @@ def self.extract_var_sol(var, category) } =end @new_constraints = true if !new_cons.empty? + RDL::Logging.log :inference, :trace, "New Constraints branch B" if !new_cons.empty? if sol.is_a?(RDL::Type::GenericType) new_params = sol.params.map { |p| if p.is_a?(RDL::Type::VarType) && !p.to_infer then p else extract_var_sol(p, category) end } @@ -139,8 +153,8 @@ def self.extract_var_sol(var, category) sol = RDL::Type::TupleType.new(*new_params) end rescue RDL::Typecheck::StaticTypeError => e - RDL::Logging.log :typecheck, :debug_error, "Attempted to apply solution #{sol} for var #{var}" - RDL::Logging.log :typecheck, :trace, "... but got the following error: #{e}" + RDL::Logging.log :inference, :debug_error, "Attempted to apply solution #{sol} for var #{var}" + RDL::Logging.log :inference, :trace, "... but got the following error: #{e}" undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is @@ -313,13 +327,15 @@ def self.extract_solutions(render_report = true) ## Go through once to come up with solution for all var types. #until !@new_constraints RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" + counter = 0; typ_sols = {} loop do + counter += 1 @new_constraints = false typ_sols = {} - RDL::Logging.log :inference, :info, "Running solution extraction..." + RDL::Logging.log :inference, :info, "[#{counter}] Running solution extraction..." RDL::Globals.constrained_types.each { |klass, name| begin @@ -364,6 +380,7 @@ def self.extract_solutions(render_report = true) break if !@new_constraints end + ensure make_extraction_report(typ_sols) if render_report end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 4090db76..642d0819 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -2,6 +2,8 @@ class RDL::Heuristic @rules = {} + @meth_cache = {} + def self.add(name, &blk) raise RuntimeError, "Expected heuristic name to be Symbol, given #{name}." unless name.is_a? Symbol raise RuntimeError, "Expected block to be provided for heuristic." if blk.nil? @@ -11,12 +13,19 @@ def self.add(name, &blk) def self.matching_classes(meth_names) meth_names.delete(:initialize) meth_names.delete(:new) + + return @meth_cache[meth_names] if @meth_cache.key? meth_names + RDL::Logging.log :heuristics, :debug, "Checking matching classes for #{meth_names}" + matching_classes = ObjectSpace.each_object(Class).select { |c| class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods matching_classes += ObjectSpace.each_object(Module).select { |c| class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods + + @meth_cache[meth_names] = matching_classes + matching_classes end def self.struct_to_nominal(var_type) diff --git a/lib/rdl/logging.rb b/lib/rdl/logging.rb index 27272c0d..29911562 100644 --- a/lib/rdl/logging.rb +++ b/lib/rdl/logging.rb @@ -1,5 +1,9 @@ - class RDL::Logging + LEVELS = %i[ + trace debug debug_error info + warning error critical + ].freeze + def self.log_level_colors(a) colors = { trace: :yellow, @@ -14,58 +18,196 @@ def self.log_level_colors(a) end def self.log_level_leq(a, b) - levels = [:trace, :debug, :debug_error, :info, :warning, :error, :critical] - a_idx = levels.find_index(a) - b_idx = levels.find_index(b) - - raise "#{a} is not a valid log level; valid levels are #{levels}" unless a_idx - raise "#{b} is not a valid log level; valid levels are #{levels}" unless b_idx + a_idx = LEVELS.find_index(a) + b_idx = LEVELS.find_index(b) a_idx <= b_idx end - def self.log_str(area, level, message, message_color: nil) - tracing = RDL::Config.instance.log_levels[area] == :trace - no_color = RDL::Config.instance.disable_log_colors + def self.extract_file_line(str) + match = str.match(/.*\/(.*?\.rb)(:([0-9]+))?/) + match && [match[1] || '', match[3] || ''] || ['', ''] + end + + def self.log_str(no_color, tracing, message_level, message, message_color: nil) + place = caller.find { |s| !s.match(/logging.rb/) } + file, line = extract_file_line(place) + meth = place.match(/in `(block.*?in )?(.*?)'/) - place = caller.find { |s| s.include?('lib/rdl') && !s.include?('in `log') } - file_line = place.match(/.*\/(.*?\.rb:[0-9]+)/)[1] - meth = place.match(/in `(block.*?in )?(.*?)'/)[2] + meth = meth ? meth[2] : 'unknown' - lc = log_level_colors(level) + lc = log_level_colors(message_level) - meth = meth.to_s.colorize(lc) unless no_color + meth = meth.to_s.colorize(lc) unless no_color + file_line = "#{file}:#{line} " file_line = file_line.colorize(:light_black) unless no_color - message = message.colorize(message_color) if message_color && !no_color + message = message.colorize(message_color) if message_color && !no_color + message = message.uncolorize if no_color level_str = '' - level_str = "#{level.to_s.upcase} " if no_color + level_str = "#{message_level.to_s.upcase} " if no_color depth_string = '' depth_string = " #{caller.length - 1}" if tracing - leader = level_str + file_line + ' [' + meth + "#{depth_string}]" + leader = level_str + file_line + '[' + meth + "#{depth_string}]" spacers = '' spacers = ' ' * ((caller.length - 1) / 2) if tracing - spacers + leader + ' ' + message + [spacers + leader + ' ' + message, (spacers + leader).uncolorize.length + 1] end - def self.log_header(area, level, header) - return unless log_level_leq(RDL::Config.instance.log_levels[area], level) - no_color = RDL::Config.instance.disable_log_colors - if no_color - stars = "***************" - puts "\n" + stars + ' ' + log_str(area, level, header, message_color: { mode: :bold }) + ' ' + stars + def self.generate_no_color_header(tracing, message_level, header) + no_color_str, = log_str(true, tracing, message_level, header, message_color: { mode: :bold }) + "\n*************** " + no_color_str + end + + def self.log_header(area, message_level, header) + log_header_to_file(area, message_level, header) + + log_level = RDL::Config.instance.log_levels[area] || :info + tracing = log_level == :trace + + return unless log_level_leq(log_level, message_level) + + if RDL::Config.instance.disable_log_colors + puts generate_no_color_header(tracing, message_level) else - puts "\n" + log_str(area, level, header, message_color: { mode: :bold }) + str, = log_str(false, tracing, message_level, header, message_color: { mode: :bold }) + puts "\n" + str end end - def self.log(area, level, message) - return unless log_level_leq(RDL::Config.instance.log_levels[area], level) + def self.log_header_to_file(area, message_level, header) + return unless RDL::Config.instance.log_file + log_level = RDL::Config.instance.log_file_levels[area] || :info + + return unless log_level_leq(log_level, message_level) + + tracing = log_level == :trace + + no_color_str = generate_no_color_header(tracing, message_level, header) + File.open(RDL::Config.instance.log_file, "a+") { |f| f.puts no_color_str } + end + + def self.log(area, message_level, message, ast: nil) + log_message_to_file(area, message_level, message, ast) + log_level = RDL::Config.instance.log_levels[area] || :info - puts log_str(area, level, message, message_color: :white) + return unless log_level_leq(log_level, message_level) + + tracing = log_level == :trace + + no_color = RDL::Config.instance.disable_log_colors + str, len = log_str(no_color, tracing, message_level, message, message_color: :white) + + puts str + puts ast_render len, ast.loc.expression, no_color if ast end + def self.log_message_to_file(area, message_level, message, ast) + return unless RDL::Config.instance.log_file + log_level = RDL::Config.instance.log_file_levels[area] || :info + + return unless log_level_leq(log_level, message_level) + + tracing = log_level == :trace + + str, = log_str(true, tracing, message_level, message) + ast_str = ast ? ast_render(0, ast.loc.expression) : nil + + File.open(RDL::Config.instance.log_file, "a+") do |f| + f.puts str + f.puts ast_str if ast_str + end + end + + ###### Borrowed from Diagnostic.rb ########################################### + + def self.ast_render(offset, location, no_color) + first_line = first_line_only(location) + last_line = last_line_only(location) + num_lines = (location.last_line - location.line) + 1 + buffer = location.source_buffer + + # location.column_range; .source; .source_buffer; .source_line + + lineno, column = buffer.decompose_position(location.begin_pos) + last_lineno, last_column = buffer.decompose_position(location.end_pos) + + file_name, = extract_file_line(buffer.name) + source_loc = "#{file_name}:" + unless no_color + source_loc = source_loc.colorize(:light_black) + end + + [' ' * offset + source_loc + "#{lineno}:#{column} - #{last_lineno}:#{last_column}"] + + render_line(offset, first_line, no_color, num_lines > 2, false) + + render_line(offset, last_line, no_color, false, true) + end + + ## + # Renders one source line in clang diagnostic style, with highlights. + # + # @return [Array] + # + def self.render_line(offset, range, no_color, ellipsis=false, range_end=false) + source_line = range.source_line + highlight_line = ' ' * source_line.length + + # @highlights.each do |highlight| + # line_range = range.source_buffer.line_range(range.line) + # if highlight = highlight.intersect(line_range) + # highlight_line[highlight.column_range] = '~' * highlight.size + # end + # end + + if range.is?("\n") + highlight_line += "^" + else + if !range_end && range.size >= 1 + highlight_line[range.column_range] = '^' + '~' * (range.size - 1) + else + highlight_line[range.column_range] = '~' * range.size + end + end + + highlight_line += '...' if ellipsis + file_name, = extract_file_line(range.source_buffer.name) + + [source_line, highlight_line].map do |line| + source_loc = "#{file_name}:#{range.line}:" + unless no_color + source_loc = source_loc.colorize(:light_black) + end + + ' ' * offset + source_loc + " #{line}" + end + end + + ## + # If necessary, shrink a `Range` so as to include only the first line. + # + # @return [Parser::Source::Range] + # + def self.first_line_only(range) + if range.line != range.last_line + range.resize(range.source =~ /\n/) + else + range + end + end + + ## + # If necessary, shrink a `Range` so as to include only the last line. + # + # @return [Parser::Source::Range] + # + def self.last_line_only(range) + if range.line != range.last_line + range.adjust(begin_pos: range.source =~ /[^\n]*\z/) + else + range + end + end end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 1aa3a60d..127af8cb 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -234,7 +234,7 @@ def self.infer(klass, meth) _infer(klass, meth) rescue => exn raise exn unless RDL::Config.instance.continue_on_errors - RDL::Logging.log :inference, :warning, "Error; Skipping inference for #{RDL::Util.pp_klass_method(klass, meth)}" + RDL::Logging.log :inference, :error, "Error; Skipping inference for #{RDL::Util.pp_klass_method(klass, meth)}" RDL::Logging.log :inference, :debug, "... got exception: #{exn}" # RDL::Globals.info.set(klass, meth, :type, [RDL::Globals.types[:dyn]]) end diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 194aedd4..da1f7fe0 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -7,6 +7,14 @@ class TypeError < StandardError; end class Type @@contract_cache = {} + def solution + @solution + end + + def solution= + RDL::Logging :typecheck, :warning, "Solution written to #{self.class}" + end + def to_contract c = @@contract_cache[self] return c if c diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index dcb0b68b..3aa74a09 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -245,7 +245,7 @@ def self.do_method_added(the_self, sing, klass, meth) else loc = the_self.instance_method(meth).source_location end - RDL::Logging.log :wrap, :trace, "Method #{klass}##{meth} added" + RDL::Logging.log :wrap, :trace, "[#{loc && loc[0] || '??'}] Method #{klass}##{meth} added" RDL::Globals.info.set(klass, meth, :source_location, loc) # Apply any deferred contracts and reset list @@ -295,10 +295,14 @@ def self.do_method_added(the_self, sing, klass, meth) # long as it isn't marked as being skipped unless ((RDL::Util.has_singleton_marker(klass) && RDL::Globals.no_infer_meths.include?([RDL::Util.remove_singleton_marker(klass).to_s, "self."+meth.to_s])) || - (RDL::Globals.no_infer_meths.include?([klass.to_s, meth.to_s]))) + (RDL::Globals.no_infer_meths.include?([klass.to_s, meth.to_s])) || + (loc && RDL::Globals.infer_added_filter_dirs && !RDL::Globals.infer_added_filter_dirs.member?(loc[0]))) + RDL::Logging.log :wrap, :trace, "... Added" tag = RDL::Globals.infer_added RDL::Globals.to_infer[tag] = Set.new unless RDL::Globals.to_infer[tag] RDL::Globals.to_infer[tag].add([klass, meth]) + else + RDL::Logging.log :wrap, :trace, "... Rejected" end end @@ -808,11 +812,28 @@ def self.at(sym, &blk) end # Mark all untyped methods added in the passed block as being inferred - def self.infer_added(tag) + def self.infer_added(tag, include: nil, exclude: nil) + dirs = RDL::Globals.infer_added_filter_dirs tmp = RDL::Globals.infer_added + + filter_list = nil + filter_list = FileList[include] if include + filter_list = (filter_list || FileList['**/*']).exclude(exclude) if exclude + RDL::Globals.infer_added = tag + RDL::Globals.infer_added_filter_dirs = filter_list && filter_list.map { |f| File.join(Dir.pwd, f) } + + if RDL::Globals.infer_added_filter_dirs + RDL::Logging.log :wrap, :debug, "Got Filter..." + RDL::Globals.infer_added_filter_dirs.each { |d| RDL::Logging.log :wrap, :trace, "\t#{d}" } + else + RDL::Logging.log :wrap, :debug, "No filter..." + end + yield + RDL::Globals.infer_added = tmp + RDL::Globals.infer_added_filter_dirs = dirs end # Invokes all callbacks from rdl_at(sym), in unspecified order. @@ -844,7 +865,7 @@ def self.do_infer(sym, render_report: true) num_casts += RDL::Typecheck.get_num_casts rescue Exception => e if RDL::Config.instance.continue_on_errors - RDL::Logging.log :inference, :debug, "Error: #{e}; recording %dyn" + RDL::Logging.log :inference, :debug_error, "Error: #{e}; recording %dyn" # RDL::Globals.info.set(klass, meth, :type, [RDL::Globals.types[:dyn]]) else raise e diff --git a/rdl.gemspec b/rdl.gemspec index 21321f56..028f33ef 100644 --- a/rdl.gemspec +++ b/rdl.gemspec @@ -19,6 +19,7 @@ EOF s.homepage = 'https://github.com/tupl-tufts/rdl' s.license = 'BSD-3-Clause' s.add_runtime_dependency 'parser', '~>2.3', '>= 2.3.1.4' + s.add_runtime_dependency 'rake', '~>13.0', '>= 13.0.1' s.add_runtime_dependency 'sql-parser', '~>0.0.2' s.add_runtime_dependency 'method_source' s.add_runtime_dependency 'colorize', '~>0.8', '>= 0.8.1' diff --git a/test/test_infer.rb b/test/test_infer.rb index 5be5e2fa..24df1153 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -19,6 +19,7 @@ def setup # TODO: this will go away after config/reset RDL::Config.instance.use_precise_string = false RDL::Config.instance.log_levels[:inference] = :error + # RDL::Config.instance.log_levels[:inference] = :debug RDL.readd_comp_types RDL.type_params :Hash, [:k, :v], :all? unless RDL::Globals.type_params['Hash'] From 8be966086b1713522c227173234e7d39c5fd67fd Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 28 May 2020 14:51:52 -0400 Subject: [PATCH 065/124] * Add assumption that methods that take blocks with effects don't store them and call them later * Add Env#clone and Env#merge_block_env methods * Get rid of notion of capturing variables * tc_send, tc_send_one_recv, and tc_block now return an env, which is the weakly updated env after the block (if any) is called --- README.md | 1 + lib/rdl/typecheck.rb | 207 ++++++++++++++++++++++------------------- lib/rdl/types/union.rb | 2 +- test/test_typecheck.rb | 11 +-- 4 files changed, 120 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 42729fd3..0de9b2d3 100644 --- a/README.md +++ b/README.md @@ -762,6 +762,7 @@ RDL's static type checker makes some assumptions that should hold unless your Ru * `Class#===` is not redefined * `Proc#call` is not redefined * `Object#class` is not redefined +* Methods that take blocks with externally visible effects do not store those blocks and then call them later (More assumptions will be added here as they are added to RDL...) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index f5822559..d0072f8f 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -46,6 +46,7 @@ def on_defs(node) # Local variable environment # tracks the types of local variables, and whether they're "fixed," i.e., # whether they should be treated flow-insensitively + # Envs should be immutable class Env attr_accessor :env @@ -66,6 +67,11 @@ def [](var) return @env[var][:type] end + def clone + result = Hash.new + result.env = @env.clone + end + # force should only be used with care! currently only used when type is being refined to a subtype in a lexical scope def bind(var, typ, force: false) raise RuntimeError, "Can't update variable with fixed type" if !force && @env[var] && @env[var][:fixed] @@ -105,6 +111,31 @@ def merge(other) return result end + # self is the caller env; other is the env at the end of a block; arg_names are the named block params (Array) + # returns a new env with everying in (body_env - arg_names) ∩ outer_env added as a weak update to self + def merge_block_env(other, arg_names) + result = Env.new + @env.each { |k, v| + if (other.env.has_key? k) && (not (arg_names.include? k)) + # weak update + unless @env[k][:fixed] == other.env[k][:fixed] + raise RuntimeError, "Variable #{k} fixed in caller but not in block should be impossible" + end + typ = RDL::Type::UnionType.new(v[:type], other.env[k][:type]) + typ = typ.canonical + if typ.instance_of?(RDL::Type::UnionType) + sings = 0 + typ.types.each { |t| sings = sings + 1 if t.instance_of?(RDL::Type::SingletonType) } + typ = typ.widen if sings > RDL::Config.instance.widen_bound + end + result.env[k] = {type: typ, fixed: @env[k][:fixed]} + else + result.env[k] = v + end + } + return result + end + # [+ envs +] is Array # any elts of envs that are nil are discarded # returns new Env where every key is mapped to the union of its bindings in the envs @@ -154,17 +185,6 @@ def self.scope_merge(scope, **elts) return r end - # add x:t to the captured map in scope - def self.capture(scope, x, t, ast: nil) - if scope[:captured][x] - if !RDL::Type::Type.leq(t, scope[:captured][x], inst={}, true, ast: ast)#t <= scope[:captured][x] - scope[:captured][x] = RDL::Type::UnionType.new(scope[:captured][x], t.instantiate(inst)).canonical #unless RDL::Type::Type.leq(t, scope[:captured][x], inst={}, true)#t <= scope[:captured][x] - end - else - scope[:captured][x] = t - end - end - # report msg at ast's loc def self.error(reason, args, ast, block: false) errtype = block ? BlockTypeError : StaticTypeError @@ -308,22 +328,20 @@ def self._infer(klass, meth) meth_type = meth_type.instantiate inst - scope = { task: :infer, klass: klass, meth: meth, tret: meth_type.ret, tblock: meth_type.block, captured: Hash.new, context_types: context_types } + scope = { task: :infer, klass: klass, meth: meth, tret: meth_type.ret, tblock: meth_type.block, context_types: context_types } # default args seem to be evaluated in the method body, so same scope _, targs = args_hash(scope, Env.new(inst), meth_type, args, ast, 'method') targs[:self] = self_type - begin - old_captured = scope[:captured].dup - if body.nil? - body_type = RDL::Globals.types[:nil] - else - targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this - @num_casts = 0 - _, body_type = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) ## TODO: need separate argument indicating we're performing inference? or is this exactly the same as type checking... - end - old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured]) - end until old_captured == scope[:captured] + # TODO: Compute captured? + + if body.nil? + body_type = RDL::Globals.types[:nil] + else + targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this + @num_casts = 0 + _, body_type = tc(scope, Env.new(targs_dup), body) ## TODO: need separate argument indicating we're performing inference? or is this exactly the same as type checking... + end #body_type = self_type if meth == :initialize body_type = RDL::Globals.parser.scan_str "#T self" if meth == :initialize # JF: Why not inst.self? @@ -370,21 +388,20 @@ def self.typecheck(klass, meth, ast=nil, types = nil) raise RuntimeError, "Type checking of methods with computed types is not currently supported." unless (type.args + [type.ret]).all? { |t| !t.instance_of?(RDL::Type::ComputedType) } inst = {self: self_type} type = type.instantiate inst - scope = { task: :check, klass: klass, meth: meth, tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types } + scope = { task: :check, klass: klass, meth: meth, tret: type.ret, tblock: type.block, context_types: context_types } # default args seem to be evaluated in the method body, so same scope _, targs = args_hash(scope, Env.new(:self => self_type), type, args, ast, 'method') targs[:self] = self_type - begin - old_captured = scope[:captured].dup - if body.nil? - body_type = RDL::Globals.types[:nil] - else - targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this - @num_casts = 0 - _, body_type = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body) - end - old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured]) - end until old_captured == scope[:captured] + + # TODO: Compute captured + + if body.nil? + body_type = RDL::Globals.types[:nil] + else + targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this + @num_casts = 0 + _, body_type = tc(scope, Env.new(targs_dup), body) + end error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || meth == :initialize ||RDL::Type::Type.leq(body_type, type.ret, ast: ast) } @@ -838,21 +855,21 @@ def self._tc(scope, env, e) else largs = [] end - tloperand = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() - envoperand, troperand = tc(scope, envleft, e.children[2]) # operand - tright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand) + envloperand, tloperand = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() + envoperand, troperand = tc(scope, envloperand, e.children[2]) # operand + envright, tright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand) tright = largs.push(tright) if largs mutation_meth = (meth.to_s + '=').to_sym - tres = tc_send(scope, envoperand, trecv, mutation_meth, tright, nil, e, true) # call recv.meth=(recvt.meth().op(operand)) - [envoperand, tres] + envres, tres = tc_send(scope, envright, trecv, mutation_meth, tright, nil, e, true) # call recv.meth=(recvt.meth().op(operand)) + [envres, tres] else # (op-asgn (Xvasgn var-name) :op operand) x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_vasgn below env = env.bind(x, RDL::Globals.types[:nil]) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn envi, trecv = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to envright, tright = tc(scope, envi, e.children[2]) # operand - trhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e) - tc_vasgn(scope, envright, e.children[0].type, x, trhs, e) + envrhs, trhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e) + tc_vasgn(scope, envrhs, e.children[0].type, x, trhs, e) end when :and_asgn, :or_asgn # very similar logic to op_asgn @@ -866,8 +883,8 @@ def self._tc(scope, env, e) else largs = [] end - tleft = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() - envright, tright = tc(scope, envleft, e.children[1]) # operand + new_envleft, tleft = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth() + envright, tright = tc(scope, new_envleft, e.children[1]) # operand else x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_var below env = env.bind(x, RDL::Globals.types[:nil]) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn @@ -891,16 +908,16 @@ def self._tc(scope, env, e) if e.children[0].type == :send mutation_meth = (meth.to_s + '=').to_sym rhs_array = [*largs, trhs] - tres = tc_send(scope, envi, trecv, mutation_meth, rhs_array, nil, e) - [envi, tres] + envres, tres = tc_send(scope, envi, trecv, mutation_meth, rhs_array, nil, e) + [envres, tres] else tc_vasgn(scope, envi, e.children[0].type, x, trhs, e) end when :match_with_lvasgn # /regexp/ =~ rhs env1, t1 = tc(scope, env, e.children[0]) # the regexp env2, t2 = tc(scope, env, e.children[1]) # the rhs - ts = tc_send(scope, env2, RDL::Globals.types[:regexp], :=~, [t2], nil, e) - [env2, ts] + es, ts = tc_send(scope, env2, RDL::Globals.types[:regexp], :=~, [t2], nil, e) + [es, ts] # (regexp # (str "foo") # (regopt)) @@ -972,7 +989,7 @@ def self._tc(scope, env, e) e_map_case = ei ti_map_case = ti else - ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType + envi, ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType block = [ti, ei] end else @@ -985,12 +1002,12 @@ def self._tc(scope, env, e) if map_case && trecv.is_a?(RDL::Type::GenericType) #raise "Expected GenericType, got #{trecv}." unless trecv.is_a?(RDL::Type::GenericType) trecv.is_a?(RDL::Type::GenericType) - ti_map_case = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) + envi, ti_map_case = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) map_block_type = RDL::Type::MethodType.new([trecv.params[0]], nil, ti_map_case.canonical.ret) block = [map_block_type, e_map_case] end - tres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e) - [envi, tres.canonical] + envres, tres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e) + [envres, tres.canonical] } when :yield # very similar to send except the callee is the method's block @@ -1068,7 +1085,7 @@ def self._tc(scope, env, e) wclause.children[0..-2].each { |guard| # first wclause.length-1 children are the guards envi, tguard = tc(scope, envi, guard) # guard type can be anything tguards << tguard - tc_send(scope, envi, tguard, :===, [tcontrol], nil, guard) unless tcontrol.nil? + envi, _ = tc_send(scope, envi, tguard, :===, [tcontrol], nil, guard) unless tcontrol.nil? envguards << envi } initial_env = Env.join(e, *envguards) @@ -1103,6 +1120,7 @@ def self._tc(scope, env, e) end tbodies << tbody + envbodies << envbody } if e.children[-1].nil? @@ -1115,7 +1133,7 @@ def self._tc(scope, env, e) #envelse = envelse.bind(e.children[0].children[0], tcontrol, force: :true) if e.children[0].type == :lvar envbodies << envelse end - return [Env.join(e, *envbodies), RDL::Type::UnionType.new(*tbodies).canonical] + [Env.join(e, *envbodies), RDL::Type::UnionType.new(*tbodies).canonical] when :while, :until # break: loop exit, i.e., right after loop guard; may take argument # next: before loop guard; argument not allowed @@ -1328,7 +1346,7 @@ def self._tc(scope, env, e) raise RuntimeError, "impossible to pass block arg and literal block" if scope[:block] envi, ti = tc(sscope, envi, ei.children[0]) # convert using to_proc if necessary - ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType + envi, ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType block = [ti, ei] else envi, ti = tc(sscope, envi, ei) @@ -1337,8 +1355,8 @@ def self._tc(scope, env, e) } trecv = get_super_owner(envi[:self], scope[:meth]) - tres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) - [envi, tres.canonical] + envres, tres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) + [envres, tres.canonical] } when :zsuper envi = env @@ -1360,8 +1378,8 @@ def self._tc(scope, env, e) scope_merge(scope, block: nil, break: env, next: env) { |sscope| trecv = get_super_owner(envi[:self], scope[:meth]) - tres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) - [envi, tres.canonical] + envres, tres = tc_send(sscope, envi, trecv, scope[:meth], tactuals, block, e) + [envres, tres.canonical] } else error :unsupported_expression, [e.type, e], e @@ -1402,14 +1420,7 @@ def self.tc_var(scope, env, kind, name, e) case kind when :lvar # local variable error :undefined_local_or_method, [name], e unless env.has_key? name - if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name)) - capture(scope, name, env[name].canonical, ast: e) - end - if scope[:captured] && scope[:captured].has_key?(name) then - [env, scope[:captured][name]] - else - [env, env[name].canonical] - end + [env, env[name].canonical] when :ivar, :cvar, :gvar klass = (if kind == :gvar then RDL::Util::GLOBAL_NAME else env[:self].to_s end) klass = "Integer" if klass == "Number" @@ -1438,11 +1449,7 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) else "global variable" end) case kind when :lvasgn - if ((scope[:captured] && scope[:captured].has_key?(name)) || - (scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name)))) - capture(scope, name, tright.canonical, ast: e) - [env, scope[:captured][name]] - elsif (env.fixed? name) + if (env.fixed? name) error :vasgn_incompat, [tright, env[name]], e unless RDL::Type::Type.leq(tright, env[name], inst={}, true, ast: e) tright.instantiate(inst) [env, tright.canonical] @@ -1478,7 +1485,7 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) } end # name is not useful here - [envi, tc_send(scope, envi, trecv, meth, [*typs, tright], nil, e)] # call receiver.meth(other args, tright) + tc_send(scope, envi, trecv, meth, [*typs, tright], nil, e) # call receiver.meth(other args, tright) else raise RuntimeError, "unknown kind #{kind}" end @@ -1616,12 +1623,13 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) t = t.canonical tarms = t.is_a?(RDL::Type::UnionType) ? t.types : [t] tarms.each { |t| - ts = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) + env, ts = tc_send_one_recv(scope, env, t, meth, tactuals, block, e, op_asgn, union) choice_hash[num] = RDL::Type::UnionType.new(*ts).canonical } rescue StaticTypeError => err errs << err trecv.remove!(num) ## choice num failed to type check, remove it from choice type + ### TODO: remove env update? end } trecv.deactivate_all_connected @@ -1630,20 +1638,25 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) elsif choice_hash.values.uniq.size == 1 ts = [choice_hash.values[0]] ## only one type resulted, no need for ChoiceType else - ts = [RDL::Type::ChoiceType.new(choice_hash, [trecv] + trecv.connecteds)] + env, ts = [RDL::Type::ChoiceType.new(choice_hash, [trecv] + trecv.connecteds)] end else - ts = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) + env, ts = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) ### XXX fix end + # TODO: envs is all possible different orders + trets.concat(ts) } trets.map! {|t| (t.is_a?(RDL::Type::AnnotatedArgType) || t.is_a?(RDL::Type::BoundArgType)) ? t.type : t} - return RDL::Type::UnionType.new(*trets) + return [env, RDL::Type::UnionType.new(*trets)] end # Like tc_send but trecv should never be a union type - # Returns array of possible return types, or throws exception if there are none + # Returns [env', Array], where + # env' is the new environment, which unions together any weak updates from the block + # the array contains all possible return types + # raises exception if there are no possible valid calls def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) raise "Type checking not currently supported for method #{meth}." if [:define_method, :module_exec].include?(meth) =begin @@ -1659,7 +1672,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, elsif trecv.is_a?(RDL::Type::AnnotatedArgType) || trecv.is_a?(RDL::Type::DependentArgType) trecv = trecv.type end - return tc_send_class(trecv, e) if (meth == :class) && (tactuals.empty?) + return [env, tc_send_class(trecv, e)] if (meth == :class) && (tactuals.empty?) ts = [] # Array, i.e., an intersection types case trecv when RDL::Type::SingletonType @@ -1697,7 +1710,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ts = lookup(scope, klass.to_s, trecv.val, e) ts = filter_comp_types(ts, false) ## no comp types in this case error :no_type_for_symbol, [trecv.val.inspect], e if ts.nil? - return ts + return [env, ts] else klass = trecv.val.class.to_s ts = lookup(scope, klass, meth, e) @@ -1724,10 +1737,10 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, # Here a module method is calling a non-existent method; check for it in all mixees # TODO: Handle :extend nts = RDL::Globals.module_mixees[klass].map { |k, kind| if kind == :include then RDL::Type::NominalType.new(k) end } - return [RDL::Globals.types[:bot]] if nts.empty? # if module not mixed in, this call can't happen; so %bot + return [env, [RDL::Globals.types[:bot]]] if nts.empty? # if module not mixed in, this call can't happen; so %bot ut = RDL::Type::UnionType.new(*nts) - t = tc_send(scope, env, ut, meth, tactuals, block, e, op_asgn) - return [t] + env, t = tc_send(scope, env, ut, meth, tactuals, block, e, op_asgn) + return [env, [t]] end error :no_instance_method_type, [trecv.name, meth], e end @@ -1835,7 +1848,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, tmeth_inst = tc_arg_types(meth_type, tactuals) raise "Expected method to be instantiated." unless tmeth_inst - tc_block(scope, env, block_type, block, tmeth_inst) + env = tc_block(scope, env, block_type, block, tmeth_inst) end else meth_type = RDL::Type::MethodType.new(tactuals, nil, ret_type) @@ -1846,17 +1859,17 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, #self_klass = nil #error :recv_var_type, [trecv], e - return [ret_type] + return [env, [ret_type]] when RDL::Type::MethodType if meth == :call # Special case - invokes the Proc ts = [trecv] else # treat as Proc - tc_send_one_recv(scope, env, RDL::Globals.types[:proc], meth, tactuals, block, e, op_asgn, union) + return tc_send_one_recv(scope, env, RDL::Globals.types[:proc], meth, tactuals, block, e, op_asgn, union) end when RDL::Type::DynamicType - return [trecv] + return [env, [trecv]] else raise RuntimeError, "receiver type #{trecv} of kind #{trecv.class} not supported yet, meth=#{meth}" end @@ -1911,9 +1924,11 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, #apply_deferred_constraints(deferred_constraints, e) unless deferred_constraints.empty? if tmeth_inst begin - tc_block(scope, env, tmeth.block, block, tmeth_inst) if block + old_env = env + env = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block rescue BlockTypeError => _ block_mismatch = true + env = old_env end if init#trecv.is_a?(RDL::Type::SingletonType) && meth == :new @@ -2028,7 +2043,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, error :arg_type_single_receiver_error, [name, meth, msg], e end # TODO: issue warning if trets.size > 1 ? - return trets + return [env, trets] end def self.apply_deferred_constraints(deferred_constraints, e) @@ -2261,7 +2276,7 @@ def self.tc_bind_arg_types(tmeth, tactuals) # [+ tblock +] is the type of the block (a MethodType) # [+ block +] is a pair [block-args, block-body] from the block AST node OR [block-type, block-arg-AST-node] - # returns if the block matches type tblock + # returns new environment if the block matches type tblock # otherwise throws an exception with a type error def self.tc_block(scope, env, tblock, block, inst) # TODO self is the same *except* instance_exec or instance_eval @@ -2280,21 +2295,25 @@ def self.tc_block(scope, env, tblock, block, inst) args_hash[a] = v v } - _, ret_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(scope, env.merge(Env.new(args_hash)), body) end - block_type = RDL::Type::MethodType.new(arg_vartypes, nil, ret_type) + body_env, body_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(scope, env.merge(Env.new(args_hash)), body) end + block_type = RDL::Type::MethodType.new(arg_vartypes, nil, body_type) RDL::Type::Type.leq(block_type, tblock, inst, false, ast: body) + ret_env = env.merge_block_env(body_env, arg_names) else # must be [block-args, block-body] args, body = block env, targs = args_hash(scope, env, tblock, args, block[1], 'block') scope_merge(scope, outer_env: env) { |bscope| + arg_names = args.children.map { |a| a.children[0] } # note: okay if outer_env shadows, since nested scope will include outer scope by next line targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this env = env.merge(Env.new(targs_dup)) - _, body_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(bscope, env.merge(Env.new(targs)), body) end + body_env, body_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(bscope, env.merge(Env.new(targs)), body) end error :bad_return_type, [body_type, tblock.ret], body, block: true unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false, ast: body) - # + ret_env = env.merge_block_env(body_env, arg_names) + error :internal, "empty self", block[1] if ret_env.env[:self].nil? } end + return ret_env end # [+ klass +] is a string containing the class name diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index 5861ed26..5c553134 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -14,7 +14,7 @@ def self.new(*types) if t.instance_of? UnionType ts.concat t.types else - raise RuntimeError, "Attempt to create union type with non-type" unless t.is_a?(Type) || t.nil? + raise RuntimeError, "Attempt to create union type with non-type #{t}" unless t.is_a?(Type) || t.nil? raise RuntimeError, "Attempt to create union with optional type" if t.is_a? OptionalType raise RuntimeError, "Attempt to create union with vararg type" if t.is_a? VarargType raise RuntimeError, "Attempt to create union with annotated type" if t.is_a? AnnotatedArgType diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index f2ce9cdb..e0315c4d 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -145,6 +145,7 @@ class TestTypecheck < Minitest::Test def setup RDL.reset RDL.type TestTypecheck, :_any_object, '() -> Object', wrap: false # a method that could return true or false + RDL.type TestTypecheck, :_any_integer, '() -> Integer', wrap: false RDL.type_params 'RDL::Type::SingletonType', [:t], :satisfies? unless RDL::Globals.type_params["RDL::Type::SingletonType"] @@ -211,11 +212,10 @@ def setup @ts34 = RDL::Type::UnionType.new(@ts3, @t4) @t3n = RDL::Type::UnionType.new(@t3, RDL::Globals.types[:nil]) @t4n = RDL::Type::UnionType.new(@t4, RDL::Globals.types[:nil]) - @env = RDL::Typecheck::Env.new(self: tt("TestTypecheck"), captured: Hash.new) + @env = RDL::Typecheck::Env.new(self: tt("TestTypecheck")) @scopef = { tret: RDL::Globals.types[:integer] } @tfs = RDL::Type::UnionType.new(RDL::Globals.types[:integer], RDL::Globals.types[:string]) @scopefs = { tret: @tfs, tblock: nil } - @scopec = { captured: Hash.new } ### Uncomment below to see test names. Useful for hanging tests. #puts "Start #{@NAME}" end @@ -773,6 +773,7 @@ def _send_blockd3 x = 'one'; _send_block1(42) { |y| for x in 1..5 do end; y }; x end } + assert (@tfs <= do_tc("y = _any_integer; _send_block1(42) { |x| y = ''; x }; y", env: @env)) end def test_send_method_generic @@ -1041,7 +1042,6 @@ def test_when end def test_when_block - skip RDL.type TestTypecheck, :m1, '(X) -> %any' RDL.type TestTypecheck, :m2, '(Y) -> %any' RDL.type TestTypecheck, :m3, '() { () -> %any } -> %any' @@ -1049,9 +1049,8 @@ def test_when_block assert_raises(RDL::Typecheck::StaticTypeError) { assert do_tc("x = _any_object; case x when Y then m1(x) when Y then m2(x) end", env: @env) } - assert do_tc("x = _any_object; case x when X then m1(x) when Y then m3() { m2(x) } end", env: @env, scope: @scopec) - # TODO: The next test case fails because `x` is captured in both blocks, giving it type `X or Y` in the second block - assert do_tc("x = _any_object; case x when X then m3() { m1(x) } when Y then m3() { m2(x) } end", env: @env, scope: @scopec) + assert do_tc("x = _any_object; case x when X then m1(x) when Y then m3() { m2(x) } end", env: @env, scope: Hash.new) + assert do_tc("x = _any_object; case x when X then m3() { m1(x) } when Y then m3() { m2(x) } end", env: @env, scope: Hash.new) end def test_while_until From 9d79b6b5f7988f606eaebdcf579965f2e2386fad Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 28 May 2020 15:14:21 -0400 Subject: [PATCH 066/124] bug fix --- lib/rdl/constraint.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 8eb93715..af213c66 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -157,8 +157,8 @@ def self.extract_var_sol(var, category) sol = var end - if typ.is_a?(RDL::Type::NominalType) || typ.is_a?(RDL::Type::GenericType) - @type_names_map[typ] = @type_names_map[typ] | [var.name.to_sym] + if sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) + @type_names_map[sol] = @type_names_map[sol] | [var.name.to_sym] end return sol From 8f81cc236abbfd77c03114bd7cadb284e1405a56 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 28 May 2020 15:31:27 -0400 Subject: [PATCH 067/124] fix error with block args being propagated to outer scope --- lib/rdl/typecheck.rb | 6 +++--- test/test_typecheck.rb | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 59106285..ca1f8894 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -2301,13 +2301,13 @@ def self.tc_block(scope, env, tblock, block, inst) ret_env = env.merge_block_env(body_env, arg_names) else # must be [block-args, block-body] args, body = block - env, targs = args_hash(scope, env, tblock, args, block[1], 'block') + targs_env, targs = args_hash(scope, env, tblock, args, block[1], 'block') scope_merge(scope, outer_env: env) { |bscope| arg_names = args.children.map { |a| a.children[0] } # note: okay if outer_env shadows, since nested scope will include outer scope by next line targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this - env = env.merge(Env.new(targs_dup)) - body_env, body_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(bscope, env.merge(Env.new(targs)), body) end + env_with_targs = targs_env.merge(Env.new(targs_dup)) + body_env, body_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(bscope, env_with_targs, body) end error :bad_return_type, [body_type, tblock.ret], body, block: true unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false, ast: body) ret_env = env.merge_block_env(body_env, arg_names) error :internal, "empty self", block[1] if ret_env.env[:self].nil? diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index fa3e0e9c..a8015489 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -774,6 +774,7 @@ def _send_blockd3 end } assert (@tfs <= do_tc("y = _any_integer; _send_block1(42) { |x| y = ''; x }; y", env: @env)) + do_tc("if _any_object then _send_block1(42) { |x| x } else 10 end", env: @env) end def test_send_method_generic From 238d81fae481cd512a39e76aedfdad927d4fb5d4 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 28 May 2020 17:24:05 -0400 Subject: [PATCH 068/124] * Remove `Env#fix` and replace with new `force` param to `Env#bind` * Fix small bug in inconsistent_var_type_type error message * Restore original var type in case statement after whens --- lib/rdl/typecheck.rb | 22 ++++++++++++++-------- test/test_typecheck.rb | 12 ++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index ca1f8894..3146a6b7 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -73,10 +73,11 @@ def clone end # force should only be used with care! currently only used when type is being refined to a subtype in a lexical scope - def bind(var, typ, force: false) + def bind(var, typ, fixed: false, force: false) raise RuntimeError, "Can't update variable with fixed type" if !force && @env[var] && @env[var][:fixed] + raise RuntimeError, "Can't fix type of already-bound variable" if !force && fixed && @env[var] result = Env.new - result.env = @env.merge(var => {type: typ, fixed: false}) + result.env = @env.merge(var => {type: typ, fixed: fixed}) return result end @@ -84,8 +85,8 @@ def has_key?(var) return @env.has_key?(var) end - def fix(var, typ) - raise RuntimeError, "Can't fix type of already-bound variable" if @env[var] + def fix(var, typ, force: false) + raise RuntimeError, "Can't fix type of already-bound variable" if !force && @env[var] result = Env.new result.env = @env.merge(var => {type: typ, fixed: true}) return result @@ -157,7 +158,7 @@ def self.join(e, *envs) other_typ = other[var] neq << other_typ unless first_typ == other_typ } - RDL::Typecheck.error :inconsistent_var_type_type, [var.to_s, (first_typ + neq).map { |t| t.to_s }.join(' and ')], e unless neq.empty? + RDL::Typecheck.error :inconsistent_var_type_type, [var.to_s, ([first_typ] + neq).map { |t| t.to_s }.join(' and ')], e unless neq.empty? env.env[var] = {type: h[:type], fixed: true} else typ = RDL::Type::UnionType.new(first_typ, *rest.map { |other| ((other.has_key? var) && other[var]) || RDL::Globals.types[:nil] }) @@ -1092,13 +1093,15 @@ def self._tc(scope, env, e) if (tguards.all? { |typ| typ.is_a?(RDL::Type::SingletonType) && (typ.val.is_a?(Class) || typ.val.nil?) }) && (e.children[0].type == :lvar) # Special case! We're branching on the type of the guard, which is a local variable. # So rebind that local variable to have the union of the guard types + var_name = e.children[0].children[0] + var_type = initial_env[var_name] new_typ = RDL::Type::UnionType.new(*(tguards.map { |typ| typ.val.nil? ? typ : RDL::Type::NominalType.new(typ.val) })).canonical # TODO adjust following for generics! if tcontrol.is_a? RDL::Type::GenericType if new_typ == tcontrol.base # special case: exact match of control type's base and type of guard; can use # geneirc type on this branch - initial_env = initial_env.bind(e.children[0].children[0], tcontrol, force: true) + initial_env = initial_env.bind(var_name, tcontrol, fixed: initial_env.fixed?(var_name), force: true) elsif !(tcontrol.base <= new_typ) && !(new_typ <= tcontrol.base) next # can't possibly match this branch else @@ -1106,7 +1109,7 @@ def self._tc(scope, env, e) end else next unless tcontrol.is_a?(RDL::Type::VarType) || (tcontrol <= new_typ || new_typ <= tcontrol) # If control can't possibly match type, skip this branch - initial_env = initial_env.bind(e.children[0].children[0], new_typ, force: true) + initial_env = initial_env.bind(var_name, new_typ, fixed: initial_env.fixed?(var_name), force: true) # note force is safe above because the env from this arm will be joined with the other envs # (where the type was not refined like this), so after the case the variable will be back to its # previous, unrefined type @@ -1117,6 +1120,8 @@ def self._tc(scope, env, e) tbody = RDL::Globals.types[:nil] else envbody, tbody = tc(scope, initial_env, wclause.children[-1]) # last wclause child is body + # reset type of var_name to its original type + envbody = envbody.bind(var_name, var_type, fixed: envbody.fixed?(var_name), force: true) end tbodies << tbody @@ -1133,6 +1138,7 @@ def self._tc(scope, env, e) #envelse = envelse.bind(e.children[0].children[0], tcontrol, force: :true) if e.children[0].type == :lvar envbodies << envelse end + # before join reset var to original type! [Env.join(e, *envbodies), RDL::Type::UnionType.new(*tbodies).canonical] when :while, :until # break: loop exit, i.e., right after loop guard; may take argument @@ -1503,7 +1509,7 @@ def self.tc_var_type(scope, env, e) rescue Racc::ParseError => err error :generic_error, [err.to_s[1..-1]], e.children[3] # remove initial newline end - [env.fix(var, typ), RDL::Globals.types[:nil]] + [env.bind(var, typ, fixed: true), RDL::Globals.types[:nil]] end def self.tc_type_cast(scope, env, e) diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index a8015489..05ee725a 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -1040,6 +1040,18 @@ def test_when assert do_tc("case when (x = 4) then x = 3 end; x", env: @env) <= @t34 assert do_tc("x = 5; case when (x = 3) then 'foo' when (x = 4) then 'foo' end; x", env: @env) # first guard always executed! <= @t34 assert do_tc("x = 6; case when (x = 3) then 'foo' when (x = 4) then 'foo' else x = 5 end; x", env: @env) <= @t345 + assert self.class.class_eval { + type "(Object) -> Object", typecheck: :now + def case_arg(x) + case x + when Integer + 1 + when String + 2 + end + x + end + } end def test_when_block From 3baf7697a556bc90c31d86c51282fea9ca9ae916 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 28 May 2020 17:33:56 -0400 Subject: [PATCH 069/124] Fix some PrettyPrint types --- lib/types/core/prettyprint.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/types/core/prettyprint.rb b/lib/types/core/prettyprint.rb index ddc8f301..df1ec4b5 100644 --- a/lib/types/core/prettyprint.rb +++ b/lib/types/core/prettyprint.rb @@ -1,4 +1,4 @@ -RDL.type :PrettyPrint, 'self.format', '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> PrettyPrint' +RDL.type :PrettyPrint, 'self.format', '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> String' RDL.type :PrettyPrint, 'self.singleline_format', '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> PrettyPrint' RDL.type :PrettyPrint, :initialize, '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> PrettyPrint' RDL.type :PrettyPrint, :break_outmost_groups, '() -> %bot' @@ -8,4 +8,4 @@ RDL.type :PrettyPrint, :flush, '() -> %bot' RDL.type :PrettyPrint, :group_sub, '() -> %bot' RDL.type :PrettyPrint, :nest, '(Integer indent) { (a) -> b } -> b' -RDL.type :PrettyPrint, :text, '(String obj, ?Integer width) -> %bot' +RDL.type :PrettyPrint, :text, '(String obj, ?Integer width) -> Integer' From 703bb7df79201e642830b97480a22cd65c7fb868 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Thu, 28 May 2020 19:35:30 -0400 Subject: [PATCH 070/124] get_ast always returns nil if it can't find the source --- lib/rdl/typecheck.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 3146a6b7..79133e05 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -214,11 +214,11 @@ def self.is_RDL(node) def self.get_ast(klass, meth) file, line = RDL::Globals.info.get(klass, meth, :source_location) - if file.nil? - return nil if RDL::Config.instance.continue_on_errors - - raise RuntimeError, "No file for #{RDL::Util.pp_klass_method(klass, meth)}" if file.nil? - end + return nil if file.nil? + # return nil if RDL::Config.instance.continue_on_errors + # + # raise RuntimeError, "No file for #{RDL::Util.pp_klass_method(klass, meth)}" if file.nil? + # end raise RuntimeError, "static type checking in irb not supported" if file == "(irb)" if file == "(pry)" From ccefc99470b42a227fdce19e1d2522d5f16f7981 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 29 May 2020 10:25:44 -0400 Subject: [PATCH 071/124] fix handling of optional AnnotatedArgTypes when checking method subtyping --- lib/rdl/types/type.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index da1f7fe0..96c54d6f 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -479,8 +479,14 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end end - if left.args.last.is_a?(OptionalType) - left = RDL::Type::MethodType.new(left.args.map { |t| if t.is_a?(RDL::Type::OptionalType) then t.type else t end }, left.block, left.ret) + last_arg = left.args.last + if last_arg.is_a?(OptionalType) || (last_arg.is_a?(AnnotatedArgType) && last_arg.type.is_a?(OptionalType)) + left = RDL::Type::MethodType.new( + left.args.map { |t| + if t.is_a?(OptionalType) then t.type + elsif t.is_a?(AnnotatedArgType) && t.type.is_a?(OptionalType) then t.type.type + else t end }, + left.block, left.ret) if left.args.size == right.args.size + 1 ## A method with an optional type in the last position can be used in place ## of a method without the optional type. So drop it and then check subtyping. From 68dfcb7d72837d643928dc2d3653bcc19cbb7777 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Mon, 1 Jun 2020 19:57:22 -0400 Subject: [PATCH 072/124] Fix a couple of nil errors --- lib/rdl/typecheck.rb | 1 + lib/rdl/wrap.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 79133e05..b1b9e98c 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -115,6 +115,7 @@ def merge(other) # self is the caller env; other is the env at the end of a block; arg_names are the named block params (Array) # returns a new env with everying in (body_env - arg_names) ∩ outer_env added as a weak update to self def merge_block_env(other, arg_names) + return self if other.nil? result = Env.new @env.each { |k, v| if (other.env.has_key? k) && (not (arg_names.include? k)) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 3aa74a09..9e252893 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -862,7 +862,7 @@ def self.do_infer(sym, render_report: true) RDL::Globals.to_infer[sym].each { |klass, meth| begin RDL::Typecheck.infer klass, meth - num_casts += RDL::Typecheck.get_num_casts + num_casts += RDL::Typecheck.get_num_casts if RDL::Typecheck.get_num_casts rescue Exception => e if RDL::Config.instance.continue_on_errors RDL::Logging.log :inference, :debug_error, "Error: #{e}; recording %dyn" From b53bb8c2dc5a93819a102f8eebc1eec92fb4a288 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Mon, 1 Jun 2020 20:01:27 -0400 Subject: [PATCH 073/124] Fix typos in Type#solution= --- lib/rdl/types/type.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 96c54d6f..4cb2d19a 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -11,8 +11,9 @@ def solution @solution end - def solution= - RDL::Logging :typecheck, :warning, "Solution written to #{self.class}" + def solution=(soln) + @solution = soln + RDL::Logging.log :typecheck, :warning, "Solution written to #{self.class}" end def to_contract @@ -479,7 +480,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con end end - last_arg = left.args.last + last_arg = left.args.last if last_arg.is_a?(OptionalType) || (last_arg.is_a?(AnnotatedArgType) && last_arg.type.is_a?(OptionalType)) left = RDL::Type::MethodType.new( left.args.map { |t| From 401926e555f1bcffc780cb9abf67e45c25253340 Mon Sep 17 00:00:00 2001 From: Geoff Huston Date: Tue, 2 Jun 2020 10:39:49 -0400 Subject: [PATCH 074/124] Infer Output Sorbet Types (#102) * Added filtering; changed up some logging * first pass of bringing source-file printing into RDL::Logging * log can optionally write to a file at its own log level * caching struct_to_nominal matching classes * converted report generation into class * storing generated type objects in report * storing generated type objects in report * initial rbi generator * refactored reporting structure * working on Sorbet reporting * changed infer_added filtering API * fixed some crashes * further refining RDL -> Sorbet conversion * updated optional argument rendering * writing rbi to file * fixed csv generation * fixed failing inference tests * emitting sorbet sets * removed debugging code --- gemfiles/Gemfile.travis | 1 + lib/rdl/boot.rb | 1 + lib/rdl/config.rb | 4 +- lib/rdl/constraint.rb | 72 +++++------ lib/rdl/logging.rb | 12 ++ lib/rdl/reporting/csv.rb | 45 +++++++ lib/rdl/reporting/reporting.rb | 54 ++++++++ lib/rdl/reporting/sorbet.rb | 222 +++++++++++++++++++++++++++++++++ lib/rdl/typecheck.rb | 1 + lib/rdl/types/optional.rb | 2 +- lib/rdl/types/type.rb | 5 + lib/rdl/types/var.rb | 16 +++ lib/rdl/types/vararg.rb | 6 +- lib/rdl/wrap.rb | 5 +- rdl.gemspec | 3 +- test/test_infer.rb | 7 +- 16 files changed, 414 insertions(+), 42 deletions(-) create mode 100644 lib/rdl/reporting/csv.rb create mode 100644 lib/rdl/reporting/reporting.rb create mode 100644 lib/rdl/reporting/sorbet.rb diff --git a/gemfiles/Gemfile.travis b/gemfiles/Gemfile.travis index ee6ceee8..765753d9 100644 --- a/gemfiles/Gemfile.travis +++ b/gemfiles/Gemfile.travis @@ -7,3 +7,4 @@ gem 'sql-parser' gem 'method_source' gem 'colorize' gem 'coderay' +gem 'parlour' \ No newline at end of file diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 8c3db110..85290633 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -176,6 +176,7 @@ class << RDL::Globals require 'rdl/wrap.rb' require 'rdl/query.rb' require 'rdl/typecheck.rb' +require 'rdl/reporting/reporting.rb' require 'rdl/constraint.rb' require 'rdl/heuristics.rb' #require_relative 'rdl/stats.rb' diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index ead99b35..9704198a 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -45,6 +45,7 @@ def self.reset(c=RDL::Config.instance) typecheck: :info, inference: :info, heuristic: :info, + reporting: :info, wrap: :critical } c.log_file = nil @@ -52,7 +53,8 @@ def self.reset(c=RDL::Config.instance) typecheck: :info, inference: :info, heuristic: :info, - wrap: :critical + wrap: :critical, + reporting: :info } end diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index b9f04cb1..b4c375df 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -228,7 +228,7 @@ def self.extract_meth_sol(tmeth) if tmeth.ret.to_s == "self" ret_sol = tmeth.ret else - ret_sol = tmeth.ret.is_a?(RDL::Type::VarType) ? extract_var_sol(tmeth.ret, :ret) : tmeth.ret + ret_sol = tmeth.ret.is_a?(RDL::Type::VarType) ? extract_var_sol(tmeth.ret, :ret) : tmeth.ret end tmeth.ret.solution = ret_sol @@ -238,14 +238,15 @@ def self.extract_meth_sol(tmeth) def self.make_extraction_report(typ_sols) + report = RDL::Reporting::InferenceReport.new #return unless $orig_types # complete_types = [] # incomplete_types = [] - CSV.open("infer_data.csv", "wb") { |csv| - csv << ["Class", "Method", "Inferred Type", "Original Type", "Source Code", "Comments"] - } + # CSV.open("infer_data.csv", "wb") { |csv| + # csv << ["Class", "Method", "Inferred Type", "Original Type", "Source Code", "Comments"] + # } correct_types = 0 total_potential = 0 @@ -253,6 +254,7 @@ def self.make_extraction_report(typ_sols) var_types = 0 typ_sols.each_pair { |km, typ| klass, meth = km + orig_typ = RDL::Globals.info.get(klass, meth, :orig_type) if orig_typ.is_a?(Array) raise "expected just one original type for #{klass}##{meth}" unless orig_typ.size == 1 @@ -289,41 +291,44 @@ def self.make_extraction_report(typ_sols) total_potential += 1 var_types += 1 end - end if !meth.to_s.include?("@") && !meth.to_s.include?("$")#orig_typ.is_a?(RDL::Type::MethodType) - CSV.open("infer_data.csv", "a+") { |csv| - ast = RDL::Typecheck.get_ast(klass, meth) - code = ast.loc.expression.source - # if RDL::Util.has_singleton_marker(klass) - # comment = RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)).method(meth).comment - # else - # comment = RDL::Util.to_class(klass).instance_method(meth).comment - # end - csv << [klass, meth, typ, orig_typ, code] #, comment - # if typ.include?("XXX") - # incomplete_types << [klass, meth, typ, orig_typ, code, comment] - # else - # complete_types << [klass, meth, typ, orig_typ, code, comment] - # end - } + ast = RDL::Typecheck.get_ast(klass, meth) + code = ast.loc.expression.source + # if RDL::Util.has_singleton_marker(klass) + # comment = RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)).method(meth).comment + # else + # comment = RDL::Util.to_class(klass).instance_method(meth).comment + # end + # csv << [klass, meth, typ, orig_typ, code] #, comment + + report[klass] << { klass: klass, method_name: meth, type: typ, + orig_type: orig_typ, source_code: code } + + + # if typ.include?("XXX") + # incomplete_types << [klass, meth, typ, orig_typ, code, comment] + # else + # complete_types << [klass, meth, typ, orig_typ, code, comment] + # end end } - # CSV.open("infer_data.csv", "a+") { |csv| - # complete_types.each { |row| csv << row } - # csv << ["X", "X", "X", "X", "X", "X"] - # incomplete_types.each { |row| csv << row } - # } RDL::Logging.log_header :inference, :info, "Extraction Complete" RDL::Logging.log :inference, :info, "Total correct (that could be automatically inferred): #{correct_types}" RDL::Logging.log :inference, :info, "Total # method types: #{meth_types}" RDL::Logging.log :inference, :info, "Total # variable types: #{var_types}" RDL::Logging.log :inference, :info, "Total # individual types: #{total_potential}" + rescue => e + RDL::Logging.log :inference, :error, "Report Generation Error" + RDL::Logging.log :inference, :debug_error, "... got #{e}" + raise e unless RDL::Config.instance.continue_on_errors + ensure + return report end - def self.extract_solutions(render_report = true) + def self.extract_solutions() ## Go through once to come up with solution for all var types. #until !@new_constraints RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" @@ -352,9 +357,9 @@ def self.extract_solutions(render_report = true) block_string = block_sol ? " { #{block_sol} }" : nil RDL::Logging.log :inference, :trace, "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" - RDL::Type::VarType.print_XXX! - block_string = block_sol ? " { #{block_sol} }" : nil - typ_sols[[klass.to_s, name.to_sym]] = "(#{arg_sols.join(', ')})#{block_string} -> #{ret_sol}" + # meth_sol = RDL::Type::MethodType.new arg_sols, block_sol, ret_sol + + typ_sols[[klass.to_s, name.to_sym]] = tmeth elsif name.to_s == "splat_param" else ## Instance/Class (also some times splat parameter) variables: @@ -367,21 +372,18 @@ def self.extract_solutions(render_report = true) #typ.solution = var_sol RDL::Logging.log :inference, :trace, "Extracted solution for #{klass} variable #{name} is #{var_sol}." - RDL::Type::VarType.print_XXX! - typ_sols[[klass.to_s, name.to_sym]] = var_sol.to_s + typ_sols[[klass.to_s, name.to_sym]] = typ end rescue => e - raise e unless RDL::Config.instance.continue_on_errors - RDL::Logging.log :inference, :debug_error, "Error while exctracting solution for #{RDL::Util.pp_klass_method(klass, name)}: #{e}; continuing..." - typ_sols[[klass.to_s, name.to_sym]] = "-- Extraction Error --" + raise e unless RDL::Config.instance.continue_on_errors end } break if !@new_constraints end ensure - make_extraction_report(typ_sols) if render_report + return make_extraction_report(typ_sols) end diff --git a/lib/rdl/logging.rb b/lib/rdl/logging.rb index 29911562..edbfdd82 100644 --- a/lib/rdl/logging.rb +++ b/lib/rdl/logging.rb @@ -4,6 +4,14 @@ class RDL::Logging warning error critical ].freeze + class << self + LEVELS.each do |level| + define_method level do |area, message| + log area, level.to_sym, message + end + end + end + def self.log_level_colors(a) colors = { trace: :yellow, @@ -80,6 +88,8 @@ def self.log_header(area, message_level, header) def self.log_header_to_file(area, message_level, header) return unless RDL::Config.instance.log_file + + header = header.to_s log_level = RDL::Config.instance.log_file_levels[area] || :info return unless log_level_leq(log_level, message_level) @@ -91,6 +101,8 @@ def self.log_header_to_file(area, message_level, header) end def self.log(area, message_level, message, ast: nil) + message = message.to_s + log_message_to_file(area, message_level, message, ast) log_level = RDL::Config.instance.log_levels[area] || :info diff --git a/lib/rdl/reporting/csv.rb b/lib/rdl/reporting/csv.rb new file mode 100644 index 00000000..d05faacd --- /dev/null +++ b/lib/rdl/reporting/csv.rb @@ -0,0 +1,45 @@ +require 'csv' + +module RDL::Reporting::CSV + + def meth_to_s(meth) + RDL::Type::VarType.print_XXX! + block_string = meth.block ? " { #{meth.block} }" : nil + "(#{meth.args.join(', ')})#{block_string} -> #{meth.ret}" + end + + def to_csv(path, open_file = nil) + csv = open_file || CSV.open(path, 'wb') + + unless open_file + csv << ['Class', 'Method', 'Inferred Type', 'Original Type', + 'Source Code', 'Comments'] + end + + @methods.each do |method| + raise 'Error: unknown class' unless @full_name + class_str = @full_name + + RDL::Logging.debug :inference, "Rendering #{RDL::Util.pp_klass_method(class_str, method.method_name)}" + + if method.type.solution.is_a?(RDL::Type::MethodType) + meth = method.type.solution + inf_type = meth_to_s meth + else + RDL::Logging.warning :inference, "Got a non-method type in type solutions: #{method.type.class}" + + RDL::Type::VarType.print_XXX! + inf_type = method.type.to_s # This would be weird + end + + csv << [class_str, method.method_name, inf_type, + method.orig_type, method.source_code] + end + + @children.each_key do |key| + @children[key].to_csv(path, csv) + end + + csv.close unless open_file + end +end diff --git a/lib/rdl/reporting/reporting.rb b/lib/rdl/reporting/reporting.rb new file mode 100644 index 00000000..cc3adf9b --- /dev/null +++ b/lib/rdl/reporting/reporting.rb @@ -0,0 +1,54 @@ +module RDL::Reporting + require_relative './csv.rb' + require_relative './sorbet.rb' + + class InferenceReport + include RDL::Reporting::CSV + include RDL::Reporting::Sorbet + + class Method + attr_accessor :klass, :method_name, :type, :orig_type, :source_code, + :comments + end + + attr_reader :full_name + + def initialize(full_name = nil) + RDL::Logging.debug :inference, "MK #{full_name}" + @full_name = full_name + @children = {} + @methods = [] + end + + def [](className) + # TODO: We need to know whether or not each level is a module or class... + part, parts = className.split '::', 2 + part = part.to_sym + + unless @children.key? part + child_full_name = @full_name ? "#{@full_name}::" : '' + child_full_name += part.to_s + @children[part] = self.class.new(child_full_name) + end + + return @children[part][parts] if parts + + @children[part] + end + + def <<(input) + meth = Method.new + + meth.klass = input[:klass] + meth.method_name = input[:method_name] + meth.type = input[:type] + meth.orig_type = input[:orig_type] + meth.source_code = input[:source_code] + meth.comments = input[:comments] + + @methods << meth + end + + end + +end diff --git a/lib/rdl/reporting/sorbet.rb b/lib/rdl/reporting/sorbet.rb new file mode 100644 index 00000000..615df46a --- /dev/null +++ b/lib/rdl/reporting/sorbet.rb @@ -0,0 +1,222 @@ +require 'parlour' + +module RDL::Reporting::Sorbet + + def to_sorbet_type(typ) + RDL::Logging.debug :reporting, typ + + case typ + when RDL::Type::StructuralType + RDL::Globals.types[:dyn] + + when RDL::Type::VarType + return 'self' if typ.to_s == 'self' + RDL::Globals.types[:dyn] + + when RDL::Type::BotType + RDL::Globals.types[:dyn] + + when RDL::Type::UnionType + type = typ.canonical + if type.is_a? RDL::Type::UnionType + types = type.types.map { |x| to_sorbet_type(x) } + RDL::Type::UnionType.new(*types) + else + type + end + + when RDL::Type::IntersectionType + type = typ.canonical + if type.is_a? RDL::Type::IntersectionType + types = type.types.map { |x| to_sorbet_type(x) } + RDL::Type::IntersectionType.new(*types) + else + type + end + + when RDL::Type::SingletonType + typ.nominal + + when RDL::Type::FiniteHashType + c = typ.canonical + types = c.elts.values.map { |v| to_sorbet_type(v) } + + return RDL::Globals.types[:dyn] if types.member? RDL::Globals.types[:dyn] + + base_type = RDL::Globals.types[:hash] + key_type = RDL::Globals.types[:symbol] + val_type = RDL::Type::UnionType.new types + + RDL::Types::GenericType.new base_type, key_type, val_type + + else + typ + + end + end + + def to_sorbet_string(typ, header, in_hash: false) + RDL::Logging.debug :reporting, "Processing #{header}..." + + unless typ + RDL::Logging.error :reporting, "Given nil instead of RDL Type!" + # return 'T.untyped' + raise "Given nil instead of RDL Type!" + end + + typ = to_sorbet_type(typ) + + case typ + when 'self' + return 'T.self_type' + + when RDL::Type::DynamicType + 'T.untyped' + + when RDL::Type::NominalType + case typ.name + when "Set" + 'T::Set[T.untyped]' + else + typ.name + end + + when RDL::Type::UnionType + type = typ.canonical + if type.is_a? RDL::Type::UnionType + types = type.types.map { |x| to_sorbet_string(x, header, in_hash: in_hash) } + "T.any(#{types.join(', ')})" + else + to_sorbet_string(type, header, in_hash: in_hash) + end + + when RDL::Type::IntersectionType + type = typ.canonical + if type.is_a? RDL::Type::IntersectionType + types = type.types.map { |x| to_sorbet_string(x, header, in_hash: in_hash) } + "T.all(#{types.join(', ')})" + else + to_sorbet_string(type, header, in_hash: in_hash) + end + + when RDL::Type::OptionalType + return "T.nilable(#{to_sorbet_string(typ.type, header, in_hash: in_hash)})" if in_hash + to_sorbet_string(typ.type, header, in_hash: in_hash) + + when RDL::Type::VarargType + to_sorbet_string(typ.type, header, in_hash: in_hash) + + when RDL::Type::GenericType + case typ.base.name + when "Hash" + k, v = typ.params + "T::Hash[#{to_sorbet_string(k, header, in_hash: in_hash)}, #{to_sorbet_string(v, header, in_hash: in_hash)}]" + + when "Array" + t, = typ.params + "T::Array[#{to_sorbet_string(t, header, in_hash: in_hash)}]" + + when "Set" + t, = typ.params + "T::Set[#{to_sorbet_string(t, header, in_hash: in_hash)}]" + + else + c = typ.canonical + b = to_sorbet_string(c.base, header, in_hash: in_hash) + types = c.params.map { |x| to_sorbet_string(x, header, in_hash: in_hash) } + "#{b}[#{types.join(', ')}]" + + end + + when RDL::Type::TupleType + "T::Array[#{to_sorbet_string RDL::Type::UnionType.new(*typ.params), header, in_hash: in_hash}]" + + else + RDL::Logging.warning :reporting, "Unmatched class #{typ.class}" + 'T.unknown' + + end + end + + def gen_sorbet(generator) + @methods.each do |method| + m = method.type + + header = RDL::Util.pp_klass_method(full_name, method.method_name) + + parameters = m.args.map do |typ| + RDL::Type::VarType.no_print_XXX! + + case typ + when RDL::Type::VarType + raise "no solution!" unless typ.solution + RDL::Logging.trace :reporting, "#{header}: #{typ.name}" + Parlour::RbiGenerator::Parameter.new(typ.name.to_s, type: to_sorbet_string(typ.solution, header)) + + when RDL::Type::FiniteHashType + typ.solution.elts.map do |kv| + RDL::Logging.trace :reporting, "#{header}: HASH #{kv[0]} : #{kv[1]}" + default = 'nil' if kv[1].optional? + Parlour::RbiGenerator::Parameter.new("#{kv[0]}:", type: to_sorbet_string(kv[1], header, in_hash: true), default: default) + end + + when RDL::Type::VarargType + Parlour::RbiGenerator::Parameter.new("*#{typ.type.name}", type: to_sorbet_string(typ.solution, header)) + + when RDL::Type::OptionalType + Parlour::RbiGenerator::Parameter.new("#{typ.type.name}", + type: to_sorbet_string(typ.solution, header), + default: 'nil') + # default: 'T.unsafe(nil)') + + else + # Parlour::RbiGenerator::Parameter.new(typ.name.to_s, type: to_sorbet_string(typ.solution)) + RDL::Logging.log :reporting, :error, "Attempting to map #{typ} (a #{typ.class})" + end + end + + block_arg = Proc.new { to_sorbet_string(m.block.solution, header) } if m.block.solution + + unless m.ret.solution.is_a?(RDL::Type::SingletonType) && + m.ret.solution.nominal.name == "NilClass" + ret_type = to_sorbet_string(m.ret.solution, header) + end + + raise "nil ret_type: #{m.ret.solution.class}" if ret_type == 'NilClass' + + meth = generator.create_method(method.method_name.to_s, + parameters: parameters.flatten, + return_type: ret_type, + &block_arg) + + RDL::Type::VarType.print_XXX! + meth.add_comment "RDL Type: #{m.solution}" + end + + @children.each_key do |key| + child = @children[key] + klass = RDL::Util.to_class(child.full_name) + is_mod = !klass.is_a?(Class) + + klass_name = child.full_name.split('::').last + + if is_mod + generator.create_module(klass_name) do |mod| + child.gen_sorbet(mod) + end + else + generator.create_class(klass_name) do |klass| + child.gen_sorbet(klass) + end + end + end + + end + + def to_sorbet(path) + generator = Parlour::RbiGenerator.new + gen_sorbet(generator.root) + + IO.write(path, generator.rbi) + end +end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index b1b9e98c..e4334bb4 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -284,6 +284,7 @@ def self._infer(klass, meth) return end types = RDL::Globals.info.get(klass, meth, :type) + if types == [] or types.nil? ## in this case, have to create new arg/ret VarTypes for this method meth_type = make_unknown_method_type(klass, meth) diff --git a/lib/rdl/types/optional.rb b/lib/rdl/types/optional.rb index 9f763142..b81fea89 100644 --- a/lib/rdl/types/optional.rb +++ b/lib/rdl/types/optional.rb @@ -1,7 +1,7 @@ module RDL::Type class OptionalType < Type attr_reader :type - attr_accessor :solution # to store the solution from inference + attr_accessor :solution def initialize(type) raise RuntimeError, "Attempt to create optional type with non-type" unless type.is_a? Type diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 4cb2d19a..1b9451cf 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -7,6 +7,11 @@ class TypeError < StandardError; end class Type @@contract_cache = {} + def name + RDL::Logging.log :typecheck, :error, "Attempted to access name field for #{self.class}" + '_NAME_ERROR' + end + def solution @solution end diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index c779250c..025509cb 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -55,12 +55,18 @@ def initialize(name_or_hash) # [+ ast +] is the AST where the bound originates from, used for error messages. # [+ new_cons +] is a Hash>. When provided, can be used to roll back constraints in case an error pops up. def add_and_propagate_upper_bound(typ, ast, new_cons = {}) + return if self.equal?(typ) if !@ubounds.any? { |t, a| t == typ } @ubounds << [typ, ast] new_cons[self] = new_cons[self] ? new_cons[self] | [[:upper, typ, ast]] : [[:upper, typ, ast]] end @lbounds.each { |lower_t, a| + if typ.is_a?(VarType) && !typ.lbounds + RDL::Logging.debug_error :inference, "Nil found in lbounds... Continuing" + next + end + if lower_t.is_a?(VarType) && lower_t.to_infer lower_t.add_and_propagate_upper_bound(typ, ast, new_cons) unless lower_t.ubounds.any? { |t, _| t == typ } else @@ -88,6 +94,16 @@ def add_and_propagate_lower_bound(typ, ast, new_cons = {}) end RDL::Logging.log :typecheck, :trace, 'ubounds.each' @ubounds.each { |upper_t, a| + if upper_t.is_a?(VarType) && !upper_t.lbounds + RDL::Logging.debug_error :inference, "Nil found in upper_t.lbounds... Continuing" + next + end + + if typ.is_a?(VarType) && !typ.ubounds + RDL::Logging.debug_error :inference, "Nil found in ubounds... Continuing" + next + end + RDL::Logging.log :typecheck, :trace, "ubound: #{upper_t}" if upper_t.is_a?(VarType) upper_t.add_and_propagate_lower_bound(typ, ast, new_cons) unless upper_t.lbounds.any? { |t, _| t == typ } diff --git a/lib/rdl/types/vararg.rb b/lib/rdl/types/vararg.rb index 5bd6aaa7..6b8bd109 100644 --- a/lib/rdl/types/vararg.rb +++ b/lib/rdl/types/vararg.rb @@ -1,7 +1,7 @@ module RDL::Type class VarargType < Type attr_reader :type - attr_accessor :solution # to store the solution from inference + attr_accessor :solution @@cache = {} @@ -25,6 +25,10 @@ def initialize(type) super() end + # def solution + # VarargType.new @type.solution + # end + def to_s if @type.instance_of? UnionType "*(#{@type.to_s})" diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 9e252893..62c3d51d 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -876,7 +876,10 @@ def self.do_infer(sym, render_report: true) RDL::Globals.to_infer[sym] = Set.new RDL::Typecheck.resolve_constraints - RDL::Typecheck.extract_solutions render_report + report = RDL::Typecheck.extract_solutions + + report.to_csv 'infer_data_new.csv' if render_report + report.to_sorbet 'infer_data.rbi' if render_report time = Time.now - time diff --git a/rdl.gemspec b/rdl.gemspec index 028f33ef..a219e9d2 100644 --- a/rdl.gemspec +++ b/rdl.gemspec @@ -23,5 +23,6 @@ EOF s.add_runtime_dependency 'sql-parser', '~>0.0.2' s.add_runtime_dependency 'method_source' s.add_runtime_dependency 'colorize', '~>0.8', '>= 0.8.1' - s.add_development_dependency 'coderay', '~>1.2', '>= 1.1.2' + s.add_runtime_dependency 'parlour', '~>2.1.0', '>= 2.1.0' + s.add_development_dependency 'coderay', '~>1.1', '>= 1.1.2' end diff --git a/test/test_infer.rb b/test/test_infer.rb index 24df1153..a72e08f8 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -146,14 +146,17 @@ def repeat_n(n) end should_have_type :repeat_n, '(Numeric) -> String' + # Note: The last type in the unions below comes from requiring `sorbet` (via + # requiring `parlour`) to render RBI files. The Structural -> Nominal + # heuristic picks up that this might be a valid type this case. def note(reason, args, ast) Diagnostic.new :note, reason, args, ast.loc.expression end - should_have_type :note, '(a, b, Parser::AST::Node or Parser::Source::Comment) -> Diagnostic' + should_have_type :note, '(a, b, Parser::AST::Node or Parser::Source::Comment or T::Private::Methods::DeclarationBlock) -> Diagnostic' def print_note(reason, args, ast) puts note(reason, args, ast).render end - should_have_type :print_note, '(a, b, Parser::AST::Node or Parser::Source::Comment) -> nil', + should_have_type :print_note, '(a, b, Parser::AST::Node or Parser::Source::Comment or T::Private::Methods::DeclarationBlock) -> nil', depends_on: [:note] end From e721f94dfbf6206c4c491cb769f03325b3da0f56 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 11 Jun 2020 10:22:05 -0400 Subject: [PATCH 075/124] fix for Array#+ type so it handles ActiveRecord::Relation when Rails is defined --- lib/types/core/array.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index b3f6fe07..8e9b5bac 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -125,7 +125,7 @@ def Array.plus_input(targs) when RDL::Type::TupleType return targs[0] when RDL::Type::GenericType, RDL::Type::VarType - parse_string = defined?(Rails) && targs[0].is_a?(RDL::Type::VarType) ? "Array or ActiveRecord::Relation" : "Array" + parse_string = defined?(Rails) && (targs[0].is_a?(RDL::Type::VarType) || (targs[0].is_a?(RDL::Type::GenericType) && targs[0].base.to_s == "ActiveRecord_Relation")) ? "Array or ActiveRecord_Relation" : "Array" x = RDL::Globals.parser.scan_str "#T #{parse_string}" x else From d1d69c5fbd627a9172ea3d16828ff6a616310dbe Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 11 Jun 2020 10:26:15 -0400 Subject: [PATCH 076/124] small type check bug fix --- lib/rdl/typecheck.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index e4334bb4..d83bb884 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -1,3 +1,4 @@ +# coding: utf-8 module RDL::Typecheck class StaticTypeError < StandardError; end @@ -1646,7 +1647,7 @@ def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false) elsif choice_hash.values.uniq.size == 1 ts = [choice_hash.values[0]] ## only one type resulted, no need for ChoiceType else - env, ts = [RDL::Type::ChoiceType.new(choice_hash, [trecv] + trecv.connecteds)] + ts = [RDL::Type::ChoiceType.new(choice_hash, [trecv] + trecv.connecteds)] end else env, ts = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union) ### XXX fix From fb39926b807e365a8ac684d3f6416f4edc28aac9 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 11 Jun 2020 12:12:59 -0400 Subject: [PATCH 077/124] small fix to rails comp type --- lib/types/rails/active_record/comp_types.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index a325613d..102265ae 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -42,7 +42,7 @@ def Object.try_output(trec, targs) case targs[0] when RDL::Type::SingletonType meth_types = RDL::Typecheck.lookup({}, klass.to_s, targs[0].val, nil, make_unknown: false)#RDL::Globals.info.get(klass, targs[0].val, :type) - meth_types = meth_types[0] if meth_types + #meth_types = meth_types[0] if meth_types ret_type = meth_types ? RDL::Type::UnionType.new(*meth_types.map { |mt| mt.ret } ).canonical : RDL::Globals.types[:top] else return RDL::Globals.types[:top] From ec3b65757f05f77d56fdd3956a42c6d2f55787b4 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 11 Jun 2020 15:50:07 -0400 Subject: [PATCH 078/124] fix methods used in extraction report --- lib/rdl/constraint.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index b4c375df..d533fecb 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -92,7 +92,6 @@ def self.extract_var_sol(var, category) new_cons = {} begin if typ - #puts "Attempting to apply heuristic solution #{typ} to #{var}" typ = typ.canonical var.add_and_propagate_upper_bound(typ, nil, new_cons) var.add_and_propagate_lower_bound(typ, nil, new_cons) @@ -108,7 +107,6 @@ def self.extract_var_sol(var, category) RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" @new_constraints = true if !new_cons.empty? RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? - return typ #sol = typ end @@ -357,9 +355,9 @@ def self.extract_solutions() block_string = block_sol ? " { #{block_sol} }" : nil RDL::Logging.log :inference, :trace, "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" - # meth_sol = RDL::Type::MethodType.new arg_sols, block_sol, ret_sol + meth_sol = RDL::Type::MethodType.new arg_sols, block_sol, ret_sol - typ_sols[[klass.to_s, name.to_sym]] = tmeth + typ_sols[[klass.to_s, name.to_sym]] = meth_sol elsif name.to_s == "splat_param" else ## Instance/Class (also some times splat parameter) variables: @@ -372,7 +370,7 @@ def self.extract_solutions() #typ.solution = var_sol RDL::Logging.log :inference, :trace, "Extracted solution for #{klass} variable #{name} is #{var_sol}." - typ_sols[[klass.to_s, name.to_sym]] = typ + typ_sols[[klass.to_s, name.to_sym]] = var_sol end rescue => e RDL::Logging.log :inference, :debug_error, "Error while exctracting solution for #{RDL::Util.pp_klass_method(klass, name)}: #{e}; continuing..." From 13eec83602447fa58b2d1df993561e0e60a78b4e Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 11 Jun 2020 15:50:28 -0400 Subject: [PATCH 079/124] small fix in sorbet type reporting --- lib/rdl/reporting/sorbet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/reporting/sorbet.rb b/lib/rdl/reporting/sorbet.rb index 615df46a..bf539f21 100644 --- a/lib/rdl/reporting/sorbet.rb +++ b/lib/rdl/reporting/sorbet.rb @@ -45,7 +45,7 @@ def to_sorbet_type(typ) base_type = RDL::Globals.types[:hash] key_type = RDL::Globals.types[:symbol] - val_type = RDL::Type::UnionType.new types + val_type = RDL::Type::UnionType.new *types RDL::Types::GenericType.new base_type, key_type, val_type From 1efc6dabc61c7f7e90790b628d83d251e4769ad3 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 11 Jun 2020 19:37:28 -0400 Subject: [PATCH 080/124] fix env issue in tc_block --- lib/rdl/typecheck.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index d83bb884..cfa63405 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -2293,6 +2293,7 @@ def self.tc_block(scope, env, tblock, block, inst) tblock = tblock.instantiate(inst) if block[0].is_a?(RDL::Type::MethodType) || block[0].is_a?(RDL::Type::VarType) error :bad_block_arg_type, [block[0], tblock], block[1], block: true unless RDL::Type::Type.leq(block[0], tblock, inst, false, ast: block[1])#block[0] <= tblock + ret_env = env elsif block[0].is_a?(RDL::Type::NominalType) && block[0].name == 'Proc' error :proc_block_arg_type, [tblock], block[1], block: true elsif tblock.is_a?(RDL::Type::VarType) From bba51795d00842e7e253c222515ad742cbfe32eb Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 12 Jun 2020 10:48:36 -0400 Subject: [PATCH 081/124] re-fix method reporting --- lib/rdl/constraint.rb | 12 ++++++------ lib/rdl/reporting/sorbet.rb | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index d533fecb..cd4a9aa3 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -261,7 +261,7 @@ def self.make_extraction_report(typ_sols) if orig_typ.nil? #puts "Original type not found for #{klass}##{meth}." #puts "Inferred type is: #{typ}" - elsif orig_typ.to_s == typ + elsif orig_typ.to_s == typ.solution.to_s #puts "Type for #{klass}##{meth} was correctly inferred, as: " #puts typ if orig_typ.is_a?(RDL::Type::MethodType) @@ -279,7 +279,7 @@ def self.make_extraction_report(typ_sols) end else RDL::Logging.log :inference, :debug, "Difference encountered for #{klass}##{meth}." - RDL::Logging.log :inference, :debug, "Inferred: #{typ}" + RDL::Logging.log :inference, :debug, "Inferred: #{typ.solution}" RDL::Logging.log :inference, :debug, "Original: #{orig_typ}" if orig_typ.is_a?(RDL::Type::MethodType) total_potential += orig_typ.args.size + 1 ## 1 for ret @@ -355,9 +355,9 @@ def self.extract_solutions() block_string = block_sol ? " { #{block_sol} }" : nil RDL::Logging.log :inference, :trace, "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" - meth_sol = RDL::Type::MethodType.new arg_sols, block_sol, ret_sol + #meth_sol = RDL::Type::MethodType.new arg_sols, block_sol, ret_sol - typ_sols[[klass.to_s, name.to_sym]] = meth_sol + typ_sols[[klass.to_s, name.to_sym]] = tmeth elsif name.to_s == "splat_param" else ## Instance/Class (also some times splat parameter) variables: @@ -367,10 +367,10 @@ def self.extract_solutions() ## otherwise use upper bound. ## Can improve later if desired. var_sol = extract_var_sol(typ, :var) - #typ.solution = var_sol + typ.solution = var_sol RDL::Logging.log :inference, :trace, "Extracted solution for #{klass} variable #{name} is #{var_sol}." - typ_sols[[klass.to_s, name.to_sym]] = var_sol + typ_sols[[klass.to_s, name.to_sym]] = typ end rescue => e RDL::Logging.log :inference, :debug_error, "Error while exctracting solution for #{RDL::Util.pp_klass_method(klass, name)}: #{e}; continuing..." diff --git a/lib/rdl/reporting/sorbet.rb b/lib/rdl/reporting/sorbet.rb index bf539f21..b24cbf54 100644 --- a/lib/rdl/reporting/sorbet.rb +++ b/lib/rdl/reporting/sorbet.rb @@ -45,9 +45,9 @@ def to_sorbet_type(typ) base_type = RDL::Globals.types[:hash] key_type = RDL::Globals.types[:symbol] - val_type = RDL::Type::UnionType.new *types + val_type = RDL::Type::UnionType.new(*types) - RDL::Types::GenericType.new base_type, key_type, val_type + RDL::Type::GenericType.new base_type, key_type, val_type else typ From 062f3f71e130f239af8a068fd2c8914177b95af9 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 12 Jun 2020 11:04:44 -0400 Subject: [PATCH 082/124] fix env handling for map case" --- lib/rdl/typecheck.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index cfa63405..87c46e67 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -1006,7 +1006,7 @@ def self._tc(scope, env, e) if map_case && trecv.is_a?(RDL::Type::GenericType) #raise "Expected GenericType, got #{trecv}." unless trecv.is_a?(RDL::Type::GenericType) trecv.is_a?(RDL::Type::GenericType) - envi, ti_map_case = tc_send(sscope, { self: trecv.params[0] }, ti_map_case, :to_proc, [], nil, e_map_case) + _, ti_map_case = tc_send(sscope, Env.new({ self: trecv.params[0] }), ti_map_case, :to_proc, [], nil, e_map_case) map_block_type = RDL::Type::MethodType.new([trecv.params[0]], nil, ti_map_case.canonical.ret) block = [map_block_type, e_map_case] end From 86679daa34fab33973a5e0fdfc3dc05e7b611437 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 12 Jun 2020 11:38:31 -0400 Subject: [PATCH 083/124] fix sorbet reporting of hash value optional types, and also of non-MethodType blocks --- lib/rdl/constraint.rb | 2 +- lib/rdl/reporting/sorbet.rb | 2 +- lib/rdl/types/method.rb | 15 +++++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index cd4a9aa3..68f76ce5 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -375,7 +375,7 @@ def self.extract_solutions() rescue => e RDL::Logging.log :inference, :debug_error, "Error while exctracting solution for #{RDL::Util.pp_klass_method(klass, name)}: #{e}; continuing..." raise e unless RDL::Config.instance.continue_on_errors - end + end } break if !@new_constraints end diff --git a/lib/rdl/reporting/sorbet.rb b/lib/rdl/reporting/sorbet.rb index b24cbf54..0e057870 100644 --- a/lib/rdl/reporting/sorbet.rb +++ b/lib/rdl/reporting/sorbet.rb @@ -39,7 +39,7 @@ def to_sorbet_type(typ) when RDL::Type::FiniteHashType c = typ.canonical - types = c.elts.values.map { |v| to_sorbet_type(v) } + types = c.elts.values.map { |v| v.is_a?(RDL::Type::OptionalType) ? to_sorbet_type(v.type) : to_sorbet_type(v) } return RDL::Globals.types[:dyn] if types.member? RDL::Globals.types[:dyn] diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index ce78eddb..135c6875 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -15,7 +15,8 @@ class MethodType < Type # [+args+] List of types of the arguments of the procedure (use [] for no args). # [+block+] The type of the block passed to this method, if it takes one. # [+ret+] The type that the procedure returns. - def initialize(args, block, ret) + # [+sol+] Whether or not this is being used as a type inference solution + def initialize(args, block, ret, sol=false) # First check argument types have form (any number of required # or optional args, at most one vararg, any number of named arguments) state = :required @@ -41,10 +42,12 @@ def initialize(args, block, ret) } @args = *args - if block.instance_of? OptionalType - raise "Block must be MethodType" unless block.type.is_a? MethodType or block.type.is_a?(VarType) - else - raise "Block must be MethodType" unless (not block) or (block.instance_of? MethodType) or block.instance_of?(VarType) + unless sol + if block.instance_of? OptionalType + raise "Block must be MethodType, got #{block}" unless block.type.is_a? MethodType or block.type.is_a?(VarType) + else + raise "Block must be MethodType, got #{block}" unless (not block) or (block.instance_of? MethodType) or block.instance_of?(VarType) + end end @block = block @@ -61,7 +64,7 @@ def solution block_sol = @block.solution ret_sol = @ret.solution - self.class.new arg_sols, block_sol, ret_sol + self.class.new arg_sols, block_sol, ret_sol, true end # TODO: Check blk From cbc027117713592e8f5b4b1ff4b76409b1be8736 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Fri, 12 Jun 2020 20:57:24 -0400 Subject: [PATCH 084/124] minor --- lib/rdl/reporting/reporting.rb | 4 ++-- lib/rdl/reporting/sorbet.rb | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/rdl/reporting/reporting.rb b/lib/rdl/reporting/reporting.rb index cc3adf9b..de7cb7b3 100644 --- a/lib/rdl/reporting/reporting.rb +++ b/lib/rdl/reporting/reporting.rb @@ -6,7 +6,7 @@ class InferenceReport include RDL::Reporting::CSV include RDL::Reporting::Sorbet - class Method + class MethodInfo attr_accessor :klass, :method_name, :type, :orig_type, :source_code, :comments end @@ -37,7 +37,7 @@ def [](className) end def <<(input) - meth = Method.new + meth = MethodInfo.new meth.klass = input[:klass] meth.method_name = input[:method_name] diff --git a/lib/rdl/reporting/sorbet.rb b/lib/rdl/reporting/sorbet.rb index 615df46a..1dc85ab1 100644 --- a/lib/rdl/reporting/sorbet.rb +++ b/lib/rdl/reporting/sorbet.rb @@ -120,6 +120,10 @@ def to_sorbet_string(typ, header, in_hash: false) t, = typ.params "T::Set[#{to_sorbet_string(t, header, in_hash: in_hash)}]" + when "Enumerator" + t, = typ.params + "T::Enumerator[#{to_sorbet_string(t, header, in_hash: in_hash)}]" + else c = typ.canonical b = to_sorbet_string(c.base, header, in_hash: in_hash) @@ -133,7 +137,7 @@ def to_sorbet_string(typ, header, in_hash: false) else RDL::Logging.warning :reporting, "Unmatched class #{typ.class}" - 'T.unknown' + 'T.untyped' end end From 6dcfe772e984a72d574b4ce4d4d426185d52893f Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 18 Jun 2020 14:15:42 -0400 Subject: [PATCH 085/124] add nowrap to PrettyPrint --- lib/types/core/prettyprint.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/types/core/prettyprint.rb b/lib/types/core/prettyprint.rb index df1ec4b5..a947f798 100644 --- a/lib/types/core/prettyprint.rb +++ b/lib/types/core/prettyprint.rb @@ -1,3 +1,5 @@ +RDL.nowrap :PrettyPrint + RDL.type :PrettyPrint, 'self.format', '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> String' RDL.type :PrettyPrint, 'self.singleline_format', '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> PrettyPrint' RDL.type :PrettyPrint, :initialize, '(?String output, ?Integer maxwidth, ?String newline) { (PrettyPrint) -> String } -> PrettyPrint' From ef638c96bf3e1d6fee7bc73102505c09809d818a Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 18 Jun 2020 16:27:52 -0400 Subject: [PATCH 086/124] additional type for Enumerable#all? --- lib/types/core/enumerable.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/types/core/enumerable.rb b/lib/types/core/enumerable.rb index d45b4020..d819aa9f 100644 --- a/lib/types/core/enumerable.rb +++ b/lib/types/core/enumerable.rb @@ -5,6 +5,7 @@ RDL.type :Enumerable, :all?, '() -> %bool' RDL.type :Enumerable, :all?, '() { (t) -> %bool } -> %bool' RDL.type :Enumerable, :all?, '() { (k, v) -> %bool } -> %bool' +RDL.type :Enumerable, :all?, '([ ===: (%any) -> %bool]) -> %bool' RDL.type :Enumerable, :any?, '() -> %bool' RDL.type :Enumerable, :any?, '() { (t) -> %any } -> %bool' # RDL.type :Enumerable, :chunk, '(XXXX : *XXXX)' # TODO From f9d5ee22d072bbc780c67ffbce0c4cc7d7e5a6da Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 19 Jun 2020 14:42:04 -0400 Subject: [PATCH 087/124] fix ileft for structral type cases in leq, and add test case --- lib/rdl/types/type.rb | 4 ++-- lib/types/core/hash.rb | 2 +- test/test_infer.rb | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 1b9451cf..efb775d8 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -315,7 +315,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con tlm = RDL::Typecheck.compute_types(tlm, lklass, left, t.args) end new_dcs = [] - if leq(tlm.instantiate(base_inst), t, nil, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + if leq(tlm.instantiate(base_inst), t, nil, true, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) ret = true if types.size > 1 && !new_dcs.empty? ## method has intersection type, and vartype constraints were created new_dcs.each { |t1, t2| @@ -422,7 +422,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con blk_typ = tlm.block.is_a?(RDL::Type::MethodType) ? tlm.block.args + [tlm.block.ret] : [tlm.block] tlm = RDL::Typecheck.compute_types(tlm, klass, left, t.args) if (tlm.args + blk_typ + [tlm.ret]).any? { |t| t.is_a? ComputedType } new_dcs = [] - if leq(tlm.instantiate(base_inst.merge({ self: left})), t, nil, ileft, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) + if leq(tlm.instantiate(base_inst.merge({ self: left})), t, nil, true, new_dcs, no_constraint: no_constraint, ast: ast, propagate: propagate, new_cons: new_cons, removed_choices: removed_choices) ret = true if types.size > 1 && !new_dcs.empty? ## method has intersection type, and vartype constraints were new_dcs.each { |t1, t2| diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index 01314211..b4f077d5 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -267,7 +267,7 @@ def Hash.delete_output(trec, targs, block) RDL.type :Hash, :fetch, '(``any_or_k(trec)``, ``targs[1] ? targs[1] : RDL::Globals.types[:top]``) -> ``RDL::Type::UnionType.new(targs[1] ? targs[1] : RDL::Globals.types[:top], output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { (``any_or_k(trec)``) -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' RDL.type :Hash, :fetch, '(``any_or_k(trec)``) { () -> u } -> ``RDL::Type::UnionType.new(RDL::Globals.parser.scan_str("#T u"), output_type(trec, targs, :fetch, :promoted_val, "v", nil_default: true))``' -RDL.type :Hash, :first, '() -> ``output_type(trec, targs, :first, :default_or_promoted_val, "v", nil_default: true)``' +RDL.type :Hash, :first, '() -> ``output_type(trec, targs, :first, "[k, v]", nil_default: true)``' RDL.type :Hash, :member?, '(%any) -> ``output_type(trec, targs, :member?, "%bool")``' RDL.type :Hash, :has_key?, '(%any) -> ``output_type(trec, targs, :has_key?, "%bool")``' RDL.type :Hash, :key?, '(%any) -> ``output_type(trec, targs, :key?, "%bool")``' diff --git a/test/test_infer.rb b/test/test_infer.rb index a72e08f8..bd5e8e86 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -39,6 +39,7 @@ def setup RDL.type :Object, :===, '(%any other) -> %bool', wrap: false RDL.type :Object, :clone, '() -> self', wrap: false RDL.type :NilClass, :&, '(%any obj) -> false', wrap: false + RDL.type :Hash, :merge, '(Hash) -> Hash', wrap: false ### Uncomment below to see test names. Useful for hanging tests. # puts "Start #{@NAME}" @@ -159,4 +160,14 @@ def print_note(reason, args, ast) end should_have_type :print_note, '(a, b, Parser::AST::Node or Parser::Source::Comment or T::Private::Methods::DeclarationBlock) -> nil', depends_on: [:note] + + def compares_struct_with_parametric_method(options = {}) + options.merge({ "test" => 42 }) + 42 + end + should_have_type :compares_struct_with_parametric_method, "(?[ merge: (Hash) -> a]) -> Integer" + # Not concerned with specific inferred types here. + # Want to test that constraint resolution does not fail when using Hash#merge's type, + # which includes type variables + end From 21062d024e02915cf081e35d8ef995dbf141ae3a Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 22 Jun 2020 09:31:37 -0400 Subject: [PATCH 088/124] fix to Array#each type --- lib/types/core/array.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 8e9b5bac..6ba94e63 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -281,7 +281,11 @@ def Array.each_arg(trec, num) end else if trec.params[0].is_a?(RDL::Type::TupleType) - return trec.params[0].params[num] + if trec.params[0].params.size > num + return trec.params[0].params[num] + else + return RDL::Globals.types[:bot] + end else return promoted_or_t(trec) end From 0c31eb5922c353d82fd12eebf75b7673254a857e Mon Sep 17 00:00:00 2001 From: Aleksandr Fedchin Date: Sun, 28 Jun 2020 17:23:13 -0400 Subject: [PATCH 089/124] small fix to matching_classes (#103) --- lib/rdl/heuristics.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 642d0819..cd59b5af 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -17,10 +17,7 @@ def self.matching_classes(meth_names) return @meth_cache[meth_names] if @meth_cache.key? meth_names RDL::Logging.log :heuristics, :debug, "Checking matching classes for #{meth_names}" - matching_classes = ObjectSpace.each_object(Class).select { |c| - class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) - (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods - matching_classes += ObjectSpace.each_object(Module).select { |c| + matching_classes = ObjectSpace.each_object(Module).select { |c| class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods @@ -41,6 +38,10 @@ def self.struct_to_nominal(var_type) ## TODO: special handling for arrays/hashes/generics? ## TODO: special handling for Rails models? see Bree's `active_record_match?` method #raise "No matching classes found for structural types with methods #{meth_names}." if matching_classes.empty? + RDL::Logging.log :heuristics, + :debug, + "Struct_to_nominal heuristsic for %s in method %s:%s yields %d matching classes with methods: %s" % + [var_type.name, var_type.cls, var_type.meth, matching_classes.size, meth_names*","] return if matching_classes.size > 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } union = RDL::Type::UnionType.new(*nom_sing_types).canonical From 58f92d08a9494f21491362d302eda075056abf76 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 16 Jul 2020 11:31:21 -0400 Subject: [PATCH 090/124] fix ordering of union/intersection types by basing it on string name --- lib/rdl/types/intersection.rb | 4 ++-- lib/rdl/types/union.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rdl/types/intersection.rb b/lib/rdl/types/intersection.rb index ba105984..5da70031 100644 --- a/lib/rdl/types/intersection.rb +++ b/lib/rdl/types/intersection.rb @@ -22,7 +22,7 @@ def self.new(*types) ts << t end } - ts.sort! { |a,b| a.object_id <=> b.object_id } + ts.sort! { |a,b| a.to_s <=> b.to_s } ts.uniq! return RDL::Globals.types[:bot] if ts.size == 0 @@ -59,7 +59,7 @@ def canonicalize! end end @types.delete(nil) - @types.sort! { |a, b| a.object_id <=> b.object_id } # canonicalize order + @types.sort! { |a, b| a.to_s <=> b.to_s } # canonicalize order @types.uniq! @canonical = @types[0] if @types.size == 1 @canonicalized = true diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index 5c553134..078d5c21 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -53,7 +53,7 @@ def canonicalize! end end @types.delete(nil) # eliminate any "deleted" elements - @types.sort! { |a, b| a.object_id <=> b.object_id } # canonicalize order + @types.sort! { |a, b| a.to_s <=> b.to_s } # canonicalize order @types.map { |t| t.canonical } @types.uniq! @canonical = @types[0] if @types.size == 1 From abd88f78b6cf8eef2df3ec441abb1a81cef62c09 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 30 Jul 2020 12:17:36 -0400 Subject: [PATCH 091/124] bug fixes for twin network queries --- lib/rdl/boot.rb | 1 + lib/rdl/constraint.rb | 6 ++- lib/rdl/heuristics.rb | 95 +++++++++++++++++++++---------------------- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 85290633..7703a0ca 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -5,6 +5,7 @@ #require 'method_source' require 'colorize' require 'rake' +require 'net/http' module RDL end diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 8b7e0317..6bdc9972 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,6 +1,6 @@ require 'csv' -module << RDL::Typecheck +class << RDL::Typecheck ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. attr_accessor :type_names_map end @@ -167,7 +167,9 @@ def self.extract_var_sol(var, category) end if sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) - @type_names_map[sol] = @type_names_map[sol] | [var.name.to_sym] + name = (var.category == :ret) ? var.meth : var.name + puts "About to add #{sol} => #{name}" + @type_names_map[sol] = @type_names_map[sol] | [name.to_s] end return sol diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 71eb5465..676d75aa 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -54,61 +54,58 @@ def self.struct_to_nominal(var_type) end -end - - -def self.twin_network_guess(var) - return unless (var_type.category == :arg) || (var_type.category == :var) ## this rule only applies to args and (instance/class/global) variables - name1 = var_type.name - sols = [] - - - uri = URI "http://127.0.0.1:5000/" - RDL::Typecheck.type_names_map.each { |t, names| - params = { words: [name1] + names } - uri.query = URI.encode_www_form(params) - res = Net::HTTP.get_response(uri) - puts "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" - - sim_score = res.body.to_f - if sim_score > 0.8 - puts "Twin network found #{name1} and list #{names} have average similarity score of #{sim_score}." - puts "Adding #{t} as a potential solution." - sols << t - else - puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}." - end - } + def self.twin_network_guess(var_type) + return unless (var_type.category == :arg) || (var_type.category == :var) ## this rule only applies to args and (instance/class/global) variables + name1 = var_type.category == :ret ? var_type.meth.to_s : var_type.name.to_s + sols = [] + + + uri = URI "http://127.0.0.1:5000/" + RDL::Typecheck.type_names_map.each { |t, names| + params = { words: [name1] + names } + uri.query = URI.encode_www_form(params) + res = Net::HTTP.get_response(uri) + puts "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" + + sim_score = res.body.to_f + if sim_score > 0.8 + puts "Twin network found #{name1} and list #{names} have average similarity score of #{sim_score}." + puts "Adding #{t} as a potential solution." + sols << t + else + puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}." + end + } - ## TODO: Is creating UnionType the right way to go? - return RDL::Type::UnionType.new(*sols).canonical - + ## TODO: Is creating UnionType the right way to go? + return RDL::Type::UnionType.new(*sols).canonical + =begin - params = { in1: name1, in2: name2 } - uri.query = URI.encode_www_form(params) - - res = Net::HTTP.get_response(uri) - if res.msg != "OK" - puts "Failed to make request to twin network server. Received response #{res.body}." - return nil - end - - sim_score = res.body.to_f - if sim_score > 0.8 - puts "Twin network found #{name1} and #{name2} have similarity score of #{sim_score}." - puts "Attempting to apply Integer as solution." - ## TODO: once we replace "count" above, also have to replace Integer as solution. - return RDL::Globals.types[:integer] - else - puts "Twin network found insufficient similarity score of #{sim_score} between #{name1} and #{name2}." - return nil - end + params = { in1: name1, in2: name2 } + uri.query = URI.encode_www_form(params) + + res = Net::HTTP.get_response(uri) + if res.msg != "OK" + puts "Failed to make request to twin network server. Received response #{res.body}." + return nil + end + + sim_score = res.body.to_f + if sim_score > 0.8 + puts "Twin network found #{name1} and #{name2} have similarity score of #{sim_score}." + puts "Attempting to apply Integer as solution." + ## TODO: once we replace "count" above, also have to replace Integer as solution. + return RDL::Globals.types[:integer] + else + puts "Twin network found insufficient similarity score of #{sim_score} between #{name1} and #{name2}." + return nil + end =end -end - + end +end class << RDL::Heuristic From 7b45daca2f24853e2fede8c745b43d1e23532a04 Mon Sep 17 00:00:00 2001 From: Jeff Foster Date: Wed, 19 Aug 2020 17:44:32 -0400 Subject: [PATCH 092/124] fix printing of singleton classes in sorbet --- lib/rdl/constraint.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 68f76ce5..83a39771 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -301,8 +301,15 @@ def self.make_extraction_report(typ_sols) # end # csv << [klass, meth, typ, orig_typ, code] #, comment - report[klass] << { klass: klass, method_name: meth, type: typ, - orig_type: orig_typ, source_code: code } + if RDL::Util.has_singleton_marker(klass) + report_klass = RDL::Util.remove_singleton_marker(klass) + report_meth = ("self." + meth.to_s).to_sym + else + report_klass = klass + report_meth = meth + end + report[report_klass] << { klass: report_klass, method_name: report_meth, type: typ, + orig_type: orig_typ, source_code: code } # if typ.include?("XXX") From a03544fc750f7cdb321aa8d0b96e7035a9ef4488 Mon Sep 17 00:00:00 2001 From: Aleksandr Fedchin Date: Wed, 19 Aug 2020 17:49:18 -0400 Subject: [PATCH 093/124] Matching classes speed-up and more logging (#104) * Matching classes speed-up and more logging * bug with meth_names.empty? fixed * Fix to no longer use hash --- lib/rdl/heuristics.rb | 49 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index cd59b5af..dd081276 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -4,6 +4,18 @@ class RDL::Heuristic @meth_cache = {} + @meth_to_cls_map = Hash.new {|hash, m| hash[m] = Set.new} # maps a method to a set of classes with that method + @str_to_cls_map = {} # this is needed to avoid calling the hash function on classes since hash can be overridden. + # RDL::Util.to_class does not always work adequately for deprecetaed modules/methods, hence the need for this map. + + def self.init_meth_to_cls # to be called before the first call to struct_to_nominal + ObjectSpace.each_object(Module).each do |c| + @str_to_cls_map[c.to_s] = c + class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) + class_methods.each {|m| @meth_to_cls_map[m] = @meth_to_cls_map[m].add(c.to_s)} + end + end + def self.add(name, &blk) raise RuntimeError, "Expected heuristic name to be Symbol, given #{name}." unless name.is_a? Symbol raise RuntimeError, "Expected block to be provided for heuristic." if blk.nil? @@ -14,13 +26,28 @@ def self.matching_classes(meth_names) meth_names.delete(:initialize) meth_names.delete(:new) - return @meth_cache[meth_names] if @meth_cache.key? meth_names - RDL::Logging.log :heuristics, :debug, "Checking matching classes for #{meth_names}" + # meth_names = meth_names.sort # otherwise cache works almost only when meth_names.size == 1 + if @meth_cache.key? meth_names + RDL::Logging.log :heuristic, :trace, "Cache used to find %d matching classes" % @meth_cache[meth_names].size + return @meth_cache[meth_names] + end - matching_classes = ObjectSpace.each_object(Module).select { |c| - class_methods = c.instance_methods | RDL::Globals.info.get_methods_from_class(c.to_s) - (meth_names - class_methods).empty? } ## will only be empty if meth_names is a subset of c.instance_methods + init_meth_to_cls if @meth_to_cls_map.empty? # initialize @meth_to_cls on first call to matching_classes + + # matching_classes = meth_names.map {|m| @meth_to_cls_map[m]}.reduce(:&).to_a # faster but does not allow debugging + matching_classes = @meth_to_cls_map[meth_names[0]] + meth_names[1..-1].each_with_index do |m, index| + tmp = matching_classes.intersection(@meth_to_cls_map[m]) + if tmp.empty? && !matching_classes.empty? + RDL::Logging.log :heuristic, :trace, + "Found %d matching classes for methods: %s, but none of these classes have method %s" % + [matching_classes.size, meth_names[0..index] * ", ", m] + end + matching_classes = tmp + end + matching_classes = matching_classes.map {|c| @str_to_cls_map[c]} + RDL::Logging.log :heuristic, :trace, "Overall, found %d matching classes" % matching_classes.size @meth_cache[meth_names] = matching_classes matching_classes end @@ -31,20 +58,24 @@ def self.struct_to_nominal(var_type) return unless var_type.ubounds.any? { |t, loc| t.is_a?(RDL::Type::StructuralType) } ## upper bounds must include struct type(s) struct_types = var_type.ubounds.select { |t, loc| t.is_a?(RDL::Type::StructuralType) } struct_types.map! { |t, loc| t } + RDL::Logging.log :heuristic, :trace, "Found %d upper bounds of structural type" % struct_types.size return if struct_types.empty? meth_names = struct_types.map { |st| st.methods.keys }.flatten.uniq + meth_names.delete(:initialize) + meth_names.delete(:new) + return if meth_names.empty? + RDL::Logging.log :heuristic, :trace, "Corresponding methods are: %s" % (meth_names*", ") matching_classes = matching_classes(meth_names) matching_classes.reject! { |c| c.to_s.start_with?("# 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } + RDL::Logging.log :heuristic, :trace, "These are: %s" % (nom_sing_types*", ") union = RDL::Type::UnionType.new(*nom_sing_types).canonical + RDL::Logging.log :heuristic, :trace, "The union of which is canonicalized to %s" % union #struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds From 4a9ca62aeb86877f3299adb2fddb9e1af093d983 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 21 Sep 2020 16:22:07 -0400 Subject: [PATCH 094/124] tweak Hash#merge to return Hash for VarType cases --- lib/types/core/hash.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/types/core/hash.rb b/lib/types/core/hash.rb index b4f077d5..5ba1d74c 100644 --- a/lib/types/core/hash.rb +++ b/lib/types/core/hash.rb @@ -343,6 +343,10 @@ def Hash.merge_output(trec, targs, mutate=false) when RDL::Type::GenericType ret = (if mutate then "Hash" else "Hash" end) return RDL::Globals.parser.scan_str "#T #{ret}" + when RDL::Type::VarType + ## Return Hash for fresh vars x and y. + return RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new(cls: targs[0].cls, meth: targs[0].meth, category: :hash_param_key, name: "hash_param_key_#{targs[0].name}"), + RDL::Type::VarType.new(cls: targs[0].cls, meth: targs[0].meth, category: :hash_param_val, name: "hash_param_val_#{targs[0].name}")) else ## targs[0] should just be hash here return RDL::Globals.types[:hash] @@ -375,6 +379,10 @@ def Hash.merge_output(trec, targs, mutate=false) else return RDL::Type::GenericType.new(arg0.base, key_union, value_union) end + when RDL::Type::VarType + ## Return Hash for fresh vars x and y. + return RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new(cls: targs[0].cls, meth: targs[0].meth, category: :hash_param_key, name: "hash_param_key_#{targs[0].name}"), + RDL::Type::VarType.new(cls: targs[0].cls, meth: targs[0].meth, category: :hash_param_val, name: "hash_param_val_#{targs[0].name}")) else ## targs[0] should just be Hash here return RDL::Globals.types[:hash] From 1790dcc2edb63341bc85b72c6d49dabc41737ea5 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 16 Oct 2020 10:01:21 -0400 Subject: [PATCH 095/124] type prediction for args with codeBERT + cosine similarity --- lib/rdl/config.rb | 3 +- lib/rdl/constraint.rb | 228 ++++++++++++----- lib/rdl/heuristics.rb | 269 +++++++++++++++----- lib/rdl/reporting/sorbet.rb | 2 + lib/rdl/typecheck.rb | 26 +- lib/rdl/types/computed.rb | 2 +- lib/rdl/types/method.rb | 2 +- lib/rdl/types/type.rb | 8 + lib/types/rails/active_record/comp_types.rb | 12 +- lib/types/sequel/comp_types.rb | 3 +- 10 files changed, 418 insertions(+), 137 deletions(-) diff --git a/lib/rdl/config.rb b/lib/rdl/config.rb index 9704198a..6f6eaeec 100644 --- a/lib/rdl/config.rb +++ b/lib/rdl/config.rb @@ -8,7 +8,7 @@ class RDL::Config attr_accessor :report, :get_types, :guess_types attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types attr_accessor :type_defaults, :infer_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type - attr_accessor :use_precise_string, :number_mode, :use_unknown_types, :infer_empties + attr_accessor :use_precise_string, :bool_singletons, :number_mode, :use_unknown_types, :infer_empties attr_accessor :continue_on_errors attr_accessor :log_levels, :disable_log_colors attr_accessor :log_file, :log_file_levels @@ -36,6 +36,7 @@ def self.reset(c=RDL::Config.instance) c.check_comp_types = false ## this for dynamically checking that the result of a computed type still holds c.rerun_comp_types = false ## this is for dynamically checking that a type computation still evaluates to the same thing as it did at type checking time c.use_precise_string = false + c.bool_singletons = false c.number_mode = false c.use_unknown_types = false c.infer_empties = true ## if [] and {} should be typed as Array and Hash diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 6bdc9972..cc34ea7b 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,13 +1,18 @@ require 'csv' +$use_twin_network = true +$use_heuristics = false + class << RDL::Typecheck ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. attr_accessor :type_names_map + attr_accessor :type_vars_map end module RDL::Typecheck - @type_names_map = Hash.new [] + @type_names_map = Hash.new { |h, k| h[k] = [] }#[] + @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] def self.resolve_constraints RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." @@ -96,35 +101,53 @@ def self.extract_var_sol(var, category) start_time = Time.now RDL::Logging.log :heuristic, :debug, "Trying rule `#{name}` for variable #{var}." typ = rule.call(var) - new_cons = {} - begin - if typ + if typ.is_a?(Array) && (name == :twin_network) + new_cons = {} + begin + typ.each { |t| + t = t.canonical + var.add_and_propagate_upper_bound(t, nil, new_cons) + var.add_and_propagate_lower_bound(t, nil, new_cons) + RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" + puts "Successfully applied twin network! Solution #{t} for #{var}".blue + @new_constraints = true if !new_cons.empty? + RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? + return t + } + rescue RDL::Typecheck::StaticTypeError => e + undo_constraints(new_cons) + end + elsif typ.is_a?(RDL::Type::Type) + new_cons = {} + begin typ = typ.canonical var.add_and_propagate_upper_bound(typ, nil, new_cons) var.add_and_propagate_lower_bound(typ, nil, new_cons) =begin - new_cons.each { |var, bounds| - bounds.each { |u_or_l, t, _| - puts "1. Added #{u_or_l} bound constraint #{t} of kind #{t.class} to variable #{var}" - puts "It has upper bounds: " - var.ubounds.each { |t, _| puts t } - } - } + new_cons.each { |var, bounds| + bounds.each { |u_or_l, t, _| + puts "1. Added #{u_or_l} bound constraint #{t} of kind #{t.class} to variable #{var}" + puts "It has upper bounds: " + var.ubounds.each { |t, _| puts t } + } + } =end RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" @new_constraints = true if !new_cons.empty? RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? return typ - #sol = typ - end - rescue RDL::Typecheck::StaticTypeError => e - RDL::Logging.log :heuristic, :debug_error, "Attempted to apply heuristic rule #{name} to var #{var}" - RDL::Logging.log :heuristic, :trace, "... but got the following error: #{e}" - undo_constraints(new_cons) + #sol = typ + rescue RDL::Typecheck::StaticTypeError => e + RDL::Logging.log :heuristic, :debug_error, "Attempted to apply heuristic rule #{name} solution #{typ} to var #{var}" + RDL::Logging.log :heuristic, :trace, "... but got the following error: #{e}" + undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is - ensure - total_time = Time.now - start_time - RDL::Logging.log :hueristic, :debug, "Heuristic #{name} took #{total_time} to evaluate" + ensure + total_time = Time.now - start_time + RDL::Logging.log :hueristic, :debug, "Heuristic #{name} took #{total_time} to evaluate" + end + else + raise "Unexpected return value #{typ} from heuristic rule #{name}." unless typ.nil? end } @@ -166,10 +189,11 @@ def self.extract_var_sol(var, category) sol = var end - if sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) - name = (var.category == :ret) ? var.meth : var.name - puts "About to add #{sol} => #{name}" - @type_names_map[sol] = @type_names_map[sol] | [name.to_s] + if sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) || sol.is_a?(RDL::Type::TupleType) || sol.is_a?(RDL::Type::FiniteHashType) || sol.is_a?(RDL::Type::UnionType) + name = var.base_name#(var.category == :ret) ? var.meth : var.name + #puts "About to add #{sol} => #{name}" + @type_names_map[sol] = @type_names_map[sol] | [name.to_s] + @type_vars_map[sol] = @type_vars_map[sol] | [var] end return sol @@ -247,9 +271,50 @@ def self.extract_meth_sol(tmeth) return [arg_sols, block_sol, ret_sol] end + # Compares a single inferred type to original type. + # Returns "E" (exact match), "P" (match up to parameter), "T" (no match but got a type), or "N" (no type). + # [+ inf_type +] is the inferred type + # [+ orig_type +] is the original, gold standard type + def self.compare_single_type(inf_type, orig_type) + if inf_type.to_s == orig_type.to_s + return "E" + elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.to_s.include?("::") && inf_type.to_s.end_with?(orig_type.to_s) + ## needed for diferring scopes, e.g. TZInfo::Timestamp & Timestamp + puts "Treating #{inf_type} and #{orig_type} as equivalent due to suffix rule.".red + return "E" + elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.klass.ancestors.map { |a| a.to_s }.include?(orig_type.to_s) + return "E" + elsif inf_type.is_a?(RDL::Type::UnionType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.types.all? { |t| t.is_a?(RDL::Type::NominalType) && t.klass.ancestors.map { |a| a.to_s }.include?(orig_type.to_s) } + return "E" + elsif !inf_type.is_a?(RDL::Type::VarType) && (orig_type.is_a?(RDL::Type::TopType)) + return "E" + elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.params[0] == orig_type.params[0] && inf_type.array_type? && orig_type.array_type? + return "E" + elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.base.to_s == orig_type.base.to_s + return "P" + elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.base.to_s == orig_type.to_s + return "P" + elsif orig_type.is_a?(RDL::Type::GenericType) && inf_type.is_a?(RDL::Type::NominalType) && orig_type.base.to_s == inf_type.to_s + return "P" + elsif inf_type.array_type? && orig_type.array_type? + return "P" + elsif inf_type.hash_type? && orig_type.hash_type? + return "P" + elsif !inf_type.is_a?(RDL::Type::VarType) + return "T" + else + return "N" + end + end + def self.make_extraction_report(typ_sols) report = RDL::Reporting::InferenceReport.new + twin_csv = CSV.open("#{if !$use_twin_network then 'no_' end}twin_#{if $use_heuristics then 'heur_' end}infer_data.csv", 'wb') + twin_csv << ["Class", "Name", "Arg/Ret/Var", + "Inferred Type", "Original Type", "Exact (E) / Up to Parameter (P) / Got Type (T) / None (N)"] + compares = Hash.new 0 + ## Twin CSV format: [Class, Name, ] #return unless $orig_types # complete_types = [] @@ -260,48 +325,35 @@ def self.make_extraction_report(typ_sols) # } correct_types = 0 - total_potential = 0 meth_types = 0 + arg_types = 0 var_types = 0 typ_sols.each_pair { |km, typ| klass, meth = km - orig_typ = RDL::Globals.info.get(klass, meth, :orig_type) + next if orig_typ.nil? || typ.solution.nil? if orig_typ.is_a?(Array) raise "expected just one original type for #{klass}##{meth}" unless orig_typ.size == 1 orig_typ = orig_typ[0] end - if orig_typ.nil? - #puts "Original type not found for #{klass}##{meth}." - #puts "Inferred type is: #{typ}" - elsif orig_typ.to_s == typ.solution.to_s - #puts "Type for #{klass}##{meth} was correctly inferred, as: " - #puts typ - if orig_typ.is_a?(RDL::Type::MethodType) - correct_types += orig_typ.args.size + 1 ## 1 for ret - total_potential += orig_typ.args.size + 1 ## 1 for ret - meth_types += 1 - if !orig_typ.block.nil? - correct_types += orig_typ.block.args.size + 1 ## 1 for ret - total_potential += orig_typ.block.args.size + 1 ## 1 for ret - end - else - var_types += 1 - correct_types += 1 - total_potential += 1 - end + if orig_typ.is_a?(RDL::Type::MethodType) + meth_types += 1 + orig_typ.args.each_with_index { |orig_arg_typ, i | + inf_arg_type = typ.solution.args[i] + comp = inf_arg_type.nil? ? "N" : compare_single_type(inf_arg_type, orig_arg_typ) + compares[comp] += 1 + twin_csv << [klass, meth, "Arg", inf_arg_type.to_s, orig_arg_typ.to_s, comp] + arg_types +=1 + } + inf_ret_type = typ.solution.ret + comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) + compares[comp] += 1 + twin_csv << [klass, meth, "Ret", inf_ret_type.to_s, orig_typ.ret.to_s, comp] else - RDL::Logging.log :inference, :debug, "Difference encountered for #{klass}##{meth}." - RDL::Logging.log :inference, :debug, "Inferred: #{typ.solution}" - RDL::Logging.log :inference, :debug, "Original: #{orig_typ}" - if orig_typ.is_a?(RDL::Type::MethodType) - total_potential += orig_typ.args.size + 1 ## 1 for ret - total_potential += orig_typ.block.args.size + 1 if !orig_typ.block.nil? - meth_types += 1 - else - total_potential += 1 - var_types += 1 - end + comp = typ.solution.nil? ? "N" : compare_single_type(typ.solution, orig_typ) + compares[comp] += 1 + twin_csv << [klass, meth, "Var", typ.solution.to_s, orig_typ.to_s, comp] + var_types += 1 end if !meth.to_s.include?("@") && !meth.to_s.include?("$")#orig_typ.is_a?(RDL::Type::MethodType) @@ -327,13 +379,26 @@ def self.make_extraction_report(typ_sols) } RDL::Logging.log_header :inference, :info, "Extraction Complete" - RDL::Logging.log :inference, :info, "Total correct (that could be automatically inferred): #{correct_types}" + twin_csv << ["Total # E:", compares["E"]] + RDL::Logging.log :inference, :info, "Total exactly correct (E): #{compares["E"]}" + twin_csv << ["Total # P:", compares["P"]] + RDL::Logging.log :inference, :info, "Total correct up to parameter (P): #{compares["P"]}" + twin_csv << ["Total # T:", compares["T"]] + RDL::Logging.log :inference, :info, "Total not correct but got type for (T): #{compares["T"]}" + twin_csv << ["Total # N:", compares["N"]] + RDL::Logging.log :inference, :info, "Total no type for (N): #{compares["N"]}" + twin_csv << ["Total # method types:", meth_types] RDL::Logging.log :inference, :info, "Total # method types: #{meth_types}" + twin_csv << ["Total # arg types:", arg_types] + RDL::Logging.log :inference, :info, "Total # argument types: #{arg_types}" + twin_csv << ["Total # var types:", var_types] RDL::Logging.log :inference, :info, "Total # variable types: #{var_types}" - RDL::Logging.log :inference, :info, "Total # individual types: #{total_potential}" + twin_csv << ["Total # individual types:", var_types + meth_types + arg_types] + RDL::Logging.log :inference, :info, "Total # individual types: #{meth_types + arg_types + var_types}" rescue => e RDL::Logging.log :inference, :error, "Report Generation Error" RDL::Logging.log :inference, :debug_error, "... got #{e}" + puts "Got: #{e}" raise e unless RDL::Config.instance.continue_on_errors ensure return report @@ -341,10 +406,9 @@ def self.make_extraction_report(typ_sols) def self.extract_solutions() ## Go through once to come up with solution for all var types. - #until !@new_constraints RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" counter = 0; - + used_twin = false typ_sols = {} loop do counter += 1 @@ -386,17 +450,55 @@ def self.extract_solutions() typ_sols[[klass.to_s, name.to_sym]] = typ end rescue => e + puts "GOT HERE WITH #{e}" RDL::Logging.log :inference, :debug_error, "Error while exctracting solution for #{RDL::Util.pp_klass_method(klass, name)}: #{e}; continuing..." raise e unless RDL::Config.instance.continue_on_errors - end + end } - break if !@new_constraints + ## Trying: out here on last run, now try applying twin network to each pair of var types + if false #$use_twin_network && !@new_constraints && !used_twin + constrained_vars = [] + RDL::Globals.constrained_types.each { |klass, name| + typ = RDL::Globals.info.get(klass, name, :type) + ## First, collect *each individual VarType* into constrained_vars array + if typ.is_a?(Array) + typ[0].args.each { |a| + case a + when RDL::Type::VarType + constrained_vars << a + when RDL::Type::OptionalType, RDL::Type::VarargType + constrained_vars << a.type + when RDL::Type::FiniteHashType + a.elts.values.each { |v| + vt = v.optional_var_type? || v.vararg_var_type? ? v.type : v + constrained_vars << vt + } + else + raise "Expected type variable, got #{a}." + end + } + #constrained_vars = constrained_vars + typ[0].args + constrained_vars << typ[0].ret + elsif name.to_s == "splat_param" + else + constrained_vars << typ + end + } + pairs_enum = constrained_vars.combination(2) + RDL::Heuristic.twin_network_constraints(pairs_enum) + used_twin = true + end + break if !@new_constraints end - + rescue => e + puts "RECEIVED ERROR #{e}" ensure return make_extraction_report(typ_sols) end + def self.set_new_constraints + @new_constraints = true + end end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 676d75aa..168a6833 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -4,6 +4,9 @@ class RDL::Heuristic @meth_cache = {} + @twin_cache = Hash.new { |h, k| h[k] = {} } + @bert_cache = Hash.new { |h, k| h[k] = {} } + def self.add(name, &blk) raise RuntimeError, "Expected heuristic name to be Symbol, given #{name}." unless name.is_a? Symbol raise RuntimeError, "Expected block to be provided for heuristic." if blk.nil? @@ -56,30 +59,60 @@ def self.struct_to_nominal(var_type) def self.twin_network_guess(var_type) - return unless (var_type.category == :arg) || (var_type.category == :var) ## this rule only applies to args and (instance/class/global) variables - name1 = var_type.category == :ret ? var_type.meth.to_s : var_type.name.to_s - sols = [] + return unless (var_type.category == :arg) || (var_type.category == :var) || (var_type.category == :ret) ## this rule only applies to args and (instance/class/global) variables + name1 = var_type.base_name#var_type.category == :ret ? var_type.meth.to_s : var_type.name.to_s + #sols = [] + sols = {} uri = URI "http://127.0.0.1:5000/" + RDL::Typecheck.type_names_map.each { |t, names| + + sum = 0 + count = 0 + names.each { |name| + count += 1 + if @twin_cache[name1][name] + sum += @twin_cache[name1][name] + else + params = { words: [name1, name], method: "twin" } + uri.query = URI.encode_www_form(params) + res = Net::HTTP.get_response(uri) + raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" + sum += res.body.to_f + @twin_cache[name1][name] = res.body.to_f + end + } + sim_score = sum / count + +=begin +## Below was query approach before implementing caching. params = { words: [name1] + names } uri.query = URI.encode_www_form(params) + #puts "SENDING QUERY OF SIZE #{names.size + 1}: #{names}" res = Net::HTTP.get_response(uri) + #puts "RECEIVED: #{res}" puts "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" sim_score = res.body.to_f +=end if sim_score > 0.8 - puts "Twin network found #{name1} and list #{names} have average similarity score of #{sim_score}." - puts "Adding #{t} as a potential solution." - sols << t + #puts "Twin network found #{name1} and list #{names} have average similarity score of #{sim_score}.".green + #puts "Adding #{t} as a potential solution." + #sols << t + sols[sim_score] = t else - puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}." + #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red end } + #puts "Done querying all of type_names_map".green + + ## return list of types that are sorted from highest similarity score to lowest + return sols.sort.map { |sim_score, t| t }.reverse ## TODO: Is creating UnionType the right way to go? - return RDL::Type::UnionType.new(*sols).canonical + #return RDL::Type::UnionType.new(*sols).canonical =begin params = { in1: name1, in2: name2 } @@ -105,6 +138,119 @@ def self.twin_network_guess(var_type) end + + def self.twin_network_constraints(pairs_enum) + uri = URI "http://127.0.0.1:5000/" + sols = {} + pairs_enum.each { |var1, var2| + name1 = var1.base_name + name2 = var2.base_name + + if @twin_cache[name1][name2] + sols[[var1, var2]] = @twin_cache[name1][name2] + else + params = { words: [name1, name2], method: "twin" } + uri.query = URI.encode_www_form(params) + res = Net::HTTP.get_response(uri) + raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" + sols[[var1, var2]] = res.body.to_f + @twin_cache[name1][name2] = res.body.to_f + end + } + sorted = sols.sort_by { |k, v| v }.reverse + sorted.each { |vars, score| + #puts "Score: #{score}. Vars: [#{vars[0]}, #{vars[1]}" + var1, var2 = vars + if score > 0.9 + new_cons = {} + begin + var1.add_and_propagate_upper_bound(var2, nil, new_cons) + var1.add_and_propagate_lower_bound(var2, nil, new_cons) + RDL::Typecheck.set_new_constraints if !new_cons.empty? + rescue => e + RDL::Typecheck.undo_constraints(new_cons) + end + end + } + + + end + + def self.bert_model_guess(var_type) + uri = URI "http://127.0.0.1:5000/" + if (var_type.category == :arg) + sols = {} + + begin_loc1, end_loc1 = get_arg_loc(var_type) ## get start location of arg in source code + + + RDL::Typecheck.type_vars_map.each { |t, vars| + sum = 0 + count = 0 + raise "Got here for #{t}" if vars.empty? + vars.each { |var2| + next if (var_type == var2) || !(var2.category == :arg) + count += 1 + if @bert_cache[var_type][var2] + puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red + sum += @bert_cache[var_type][var2] + else + source1 = RDL::Typecheck.get_ast(var_type.cls, var_type.meth).loc.expression.source + source2 = RDL::Typecheck.get_ast(var2.cls, var2.meth).loc.expression.source + begin_loc2, end_loc2 = get_arg_loc(var2) + puts "Querying for vars #{var_type.base_name} and #{var2.base_name}.".red + puts "Sanity check: #{source1[begin_loc1..end_loc1]} and #{source2[begin_loc2..end_loc2]}" + params = { sources: [source1, source2], var1_locs: [begin_loc1, end_loc1], var2_locs: [begin_loc2, end_loc2], method: "bert"} + uri.query = URI.encode_www_form(params) + res = Net::HTTP.get_response(uri) + raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" + sum += res.body.to_f + @bert_cache[var_type][var2] = res.body.to_f + @bert_cache[var2][var_type] = res.body.to_f + puts "Received similarity score of #{res.body.to_f} for vars #{var_type.cls}##{var_type.meth}##{var_type.base_name} and #{var2.cls}##{var2.meth}##{var2.base_name}".red + end + } + sim_score = (count == 0) ? 0 : sum / count + puts "Received overall sim_score average of #{sim_score} for var #{var_type.cls}##{var_type.meth}##{var_type.base_name} and type #{t}".green if sim_score != 0 + + + if sim_score > 0.9 + sols[sim_score] = t + else + #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red + end + } + + ## return list of types that are sorted from highest similarity score to lowest + return sols.sort.map { |sim_score, t| t }.reverse + + + else + puts "not yet implemented" + end + end + + def self.get_arg_loc(var_type) + ast = RDL::Typecheck.get_ast(var_type.cls, var_type.meth) + begin_pos = ast.loc.expression.begin_pos + + if ast.type == :def + meth_name, args, body = *ast + elsif ast.type == :defs + _, meth_name, args, body = *ast + else + raise RuntimeError, "Unexpected ast type #{ast.type}" + end + + args.children.each { |c| + if (c.children[0].to_s == var_type.base_name) + ## Found the arg corresponding to var_type + return [c.loc.expression.begin_pos - begin_pos, c.loc.expression.end_pos - begin_pos - 1] ## translate it so that 0 is first position + end + } + + end + end @@ -134,62 +280,63 @@ def model_set_type end end +if $use_heuristics + if defined? Rails + RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } + RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } + end -if defined? Rails - RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } - RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } -end - -RDL::Heuristic.add(:struct_to_nominal) { |var| t1 = Time.now; g = RDL::Heuristic.struct_to_nominal(var); $stn = $stn + (Time.now - t1); g } -RDL::Heuristic.add(:int_names) { |var| if var.base_name.end_with?("id") || (var.base_name.end_with? "num") || (var.base_name.end_with? "count") then RDL::Globals.types[:integer] end } -RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") || (var.base_name.end_with? "nums") || (var.base_name.end_with? "counts") then RDL::Globals.parser.scan_str "#T Array" end } -RDL::Heuristic.add(:predicate_method) { |var| if var.base_name.end_with?("?") then RDL::Globals.types[:bool] end } -RDL::Heuristic.add(:string_name) { |var| if var.base_name.end_with?("name") then RDL::Globals.types[:string] end } -RDL::Heuristic.add(:hash_access) { |var| - old_var = var - var = var.type if old_var.is_a?(RDL::Type::OptionalType) - types = [] - var.ubounds.reject { |t, ast| t.is_a?(RDL::Type::VarType) }.each { |t, ast| - if t.is_a?(RDL::Type::IntersectionType) - types = types + t.types - else - types << t - end - } - if !types.empty? && types.all? { |t| t.is_a?(RDL::Type::StructuralType) && t.methods.all? { |meth, typ| ((meth == :[]) || (meth == :[]=)) && typ.args[0].is_a?(RDL::Type::SingletonType) && typ.args[0].val.is_a?(Symbol) } } - hash_typ = {} - types.each { |struct| - struct.methods.each { |meth, typ| - if meth == :[] - value_type = typ.ret#typ.ret.is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.ret, :arg).canonical : typ.ret.canonical - elsif meth == :[]= - value_type = typ.args[1]#typ.args[1].is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.args[1], :arg).canonical : typ.args[1].canonical - else - raise "Method should be one of :[] or :[]=, got #{meth}." - end - if value_type.is_a?(RDL::Type::UnionType) - RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical - elsif value_type.is_a?(RDL::Type::IntersectionType) - RDL::Type::IntersectionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical - else - value_type = RDL::Typecheck.extract_var_sol(value_type, :arg) - end - #value_type = value_type.drop_vars!.canonical if (value_type.is_a?(RDL::Type::UnionType) || value_type.is_a?(RDL::Type::IntersectionType)) && (!value_type.types.all? { |t| t.is_a?(RDL::Type::VarType) }) - hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: - } + RDL::Heuristic.add(:struct_to_nominal) { |var| t1 = Time.now; g = RDL::Heuristic.struct_to_nominal(var); $stn = $stn + (Time.now - t1); g } + RDL::Heuristic.add(:int_names) { |var| if var.base_name.end_with?("id") || (var.base_name.end_with? "num") || (var.base_name.end_with? "count") then RDL::Globals.types[:integer] end } + RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") || (var.base_name.end_with? "nums") || (var.base_name.end_with? "counts") then RDL::Globals.parser.scan_str "#T Array" end } + RDL::Heuristic.add(:predicate_method) { |var| if var.base_name.end_with?("?") then RDL::Globals.types[:bool] end } + RDL::Heuristic.add(:string_name) { |var| if var.base_name.end_with?("name") then RDL::Globals.types[:string] end } + RDL::Heuristic.add(:hash_access) { |var| + old_var = var + var = var.type if old_var.is_a?(RDL::Type::OptionalType) + types = [] + var.ubounds.reject { |t, ast| t.is_a?(RDL::Type::VarType) }.each { |t, ast| + if t.is_a?(RDL::Type::IntersectionType) + types = types + t.types + else + types << t + end } - #var.ubounds.delete_if { |t| t.is_a?(RDL::Type::StructuralType) } #= [] ## might have to change this later, in particular to take advantage of comp types when performing solution extraction - fht = RDL::Type::FiniteHashType.new(hash_typ, nil) - if old_var.is_a?(RDL::Type::OptionalType) - RDL::Type::OptionalType.new(fht) - else - fht + if !types.empty? && types.all? { |t| t.is_a?(RDL::Type::StructuralType) && t.methods.all? { |meth, typ| ((meth == :[]) || (meth == :[]=)) && typ.args[0].is_a?(RDL::Type::SingletonType) && typ.args[0].val.is_a?(Symbol) } } + hash_typ = {} + types.each { |struct| + struct.methods.each { |meth, typ| + if meth == :[] + value_type = typ.ret#typ.ret.is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.ret, :arg).canonical : typ.ret.canonical + elsif meth == :[]= + value_type = typ.args[1]#typ.args[1].is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.args[1], :arg).canonical : typ.args[1].canonical + else + raise "Method should be one of :[] or :[]=, got #{meth}." + end + if value_type.is_a?(RDL::Type::UnionType) + RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical + elsif value_type.is_a?(RDL::Type::IntersectionType) + RDL::Type::IntersectionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical + else + value_type = RDL::Typecheck.extract_var_sol(value_type, :arg) + end + #value_type = value_type.drop_vars!.canonical if (value_type.is_a?(RDL::Type::UnionType) || value_type.is_a?(RDL::Type::IntersectionType)) && (!value_type.types.all? { |t| t.is_a?(RDL::Type::VarType) }) + hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: + } + } + #var.ubounds.delete_if { |t| t.is_a?(RDL::Type::StructuralType) } #= [] ## might have to change this later, in particular to take advantage of comp types when performing solution extraction + fht = RDL::Type::FiniteHashType.new(hash_typ, nil) + if old_var.is_a?(RDL::Type::OptionalType) + RDL::Type::OptionalType.new(fht) + else + fht + end end - end -} - + } +end ### For rules involving :include?, :==, :!=, etc. we would need to track the exact receiver/args used in the method call, and somehow store these in the bounds created for a var type. - -RDL::Heuristic.add(:twin_network) { |var| RDL::Heuristic.twin_network_guess(var) } +if $use_twin_network + RDL::Heuristic.add(:twin_network) { |var| RDL::Heuristic.bert_model_guess(var) } +end diff --git a/lib/rdl/reporting/sorbet.rb b/lib/rdl/reporting/sorbet.rb index 13d5ea8f..d4599db4 100644 --- a/lib/rdl/reporting/sorbet.rb +++ b/lib/rdl/reporting/sorbet.rb @@ -135,6 +135,8 @@ def to_sorbet_string(typ, header, in_hash: false) when RDL::Type::TupleType "T::Array[#{to_sorbet_string RDL::Type::UnionType.new(*typ.params), header, in_hash: in_hash}]" + when RDL::Type::TopType + "T.nilable(BasicObject)" else RDL::Logging.warning :reporting, "Unmatched class #{typ.class}" 'T.untyped' diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 87c46e67..8f2dc906 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -596,14 +596,26 @@ def self._tc(scope, env, e) when :nil [env, RDL::Globals.types[:nil]] when :true - [env, RDL::Globals.types[:true]] + if RDL::Config.instance.bool_singletons + [env, RDL::Globals.types[:true]] + else + [env, RDL::Globals.types[:bool]] + end when :false - [env, RDL::Globals.types[:false]] + if RDL::Config.instance.bool_singletons + [env, RDL::Globals.types[:false]] + else + [env, RDL::Globals.types[:bool]] + end when :str, :string t = RDL::Config.instance.use_precise_string ? RDL::Type::PreciseStringType.new(e.children[0]) : RDL::Globals.types[:string] [env, t] when :complex, :rational # constants - [env, RDL::Type::NominalType.new(e.children[0].class)] + if RDL::Config.instance.number_mode + [env, RDL::Type::NominalType.new(Integer)] + else + [env, RDL::Type::SingletonType.new(e.children[0].class)] + end when :int, :float if RDL::Config.instance.number_mode [env, RDL::Type::NominalType.new(Integer)] @@ -1399,7 +1411,13 @@ def self.to_type(val, as_key=false) case val when Symbol if as_key then val else RDL::Type::SingletonType.new(val) end - when TrueClass, FalseClass, Class, Module + when TrueClass, FalseClass + if RDL::Config.instance.bool_singletons + RDL::Type::SingletonType.new(val) + else + RDL::Globals.types[:bool] + end + when Class, Module RDL::Type::SingletonType.new(val) when Complex, Rational, Integer, Float if RDL::Config.instance.number_mode# && !(val.class == Integer) diff --git a/lib/rdl/types/computed.rb b/lib/rdl/types/computed.rb index f2a65924..59331c0a 100644 --- a/lib/rdl/types/computed.rb +++ b/lib/rdl/types/computed.rb @@ -11,7 +11,7 @@ def initialize(code) def compute(bind) res = bind.eval(@code) - raise RuntimeError, "Expected ComputedType to evaluate to type, instead got #{res}." unless res.is_a?(Type) + raise RuntimeError, "Expected ComputedType to evaluate to type, instead got #{res} for code #{@code}." unless res.is_a?(Type) res end diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index 135c6875..20a09ad3 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -37,7 +37,7 @@ def initialize(args, block, ret, sol=false) else raise "Attempt to create method type with non-type arg" unless arg.is_a? Type # raise "Required arguments not allowed after varargs" if state == :vararg # actually they are allowed! - raise "Required arguments not allowed after named arguments" if state == :hash + #raise "Required arguments not allowed after named arguments" if state == :hash end } @args = *args diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index efb775d8..004028e8 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -57,6 +57,14 @@ def kind_of_var_input? var_type? || optional_var_type? || fht_var_type? || vararg_var_type? end + def array_type? + is_a?(TupleType) || (is_a?(GenericType) && ((base == RDL::Globals.types[:array]) || ((defined? ActiveRecord_Relation) && (base.klass == ActiveRecord_Relation)))) || (self == RDL::Globals.types[:array]) + end + + def hash_type? + is_a?(FiniteHashType) || (is_a?(GenericType) && (base == RDL::Globals.types[:hash])) || (self == RDL::Globals.types[:hash]) + end + # default behavior, override in appropriate subclasses def canonical; return self; end def optional?; return false; end diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index 102265ae..3fb82b36 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -242,7 +242,8 @@ class ActiveRecord::QueryMethods::WhereChain module ActiveRecord::Delegation extend RDL::Annotate - type :+, '(%any) -> ``DBType.plus_output_type(trec, targs)``', wrap: false + #type :+, '(%any) -> ``DBType.plus_output_type(trec, targs)``', wrap: false + type :+, '(ActiveRecord_Relation) -> ``DBType.plus_output_type(trec, targs)``', wrap: false end @@ -364,7 +365,7 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false, include_assocs: case param when RDL::Type::GenericType ## should be JoinTable - raise "unexpected type #{trec}" unless param.base.klass == JoinTable + raise "1. rec_to_schema unexpected type #{trec}" unless param.base.klass == JoinTable 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 type_hash = table_name_to_schema_type(base_name, check_col, takes_array, include_assocs: include_assocs).elts pp1 = param.params[1] @@ -384,7 +385,7 @@ def self.rec_to_schema_type(trec, check_col, takes_array=false, include_assocs: ], "Hash", force: true) type_hash = type_hash.merge(joined_hash) else - raise "unexpected type #{trec}" + raise "2. rec_to_schema unexpected type #{trec}" end return RDL::Type::FiniteHashType.new(type_hash, nil) when RDL::Type::NominalType @@ -660,7 +661,7 @@ def self.joins_output(trec, targs) raise "unexpected parameter type in #{trec}" end else - raise "unexpected type #{trec}" + raise "joins_output unexpected type #{trec}" end jt = RDL::Type::GenericType.new(RDL::Type::NominalType.new(JoinTable), rec_to_nominal(trec), joined) ret = RDL::Type::GenericType.new(RDL::Type::NominalType.new(ActiveRecord_Relation), jt) @@ -688,7 +689,8 @@ def self.plus_output_type(trec, targs) when RDL::Type::VarType return RDL::Globals.types[:array] else - raise "unexpected type #{t}" + #raise "plus unexpected type #{t} with #{trec} and #{targs[0]}" + return RDL::Globals.types[:bot] end } RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), RDL::Type::UnionType.new(*typs)) diff --git a/lib/types/sequel/comp_types.rb b/lib/types/sequel/comp_types.rb index 6c84a98e..8d0ce823 100644 --- a/lib/types/sequel/comp_types.rb +++ b/lib/types/sequel/comp_types.rb @@ -487,7 +487,8 @@ def self.schema_arg_type(trec, targs, meth) return arg0 else return arg0 if (meth==:where) && targs[0] <= RDL::Globals.types[:string] - raise "TODO WITH #{trec} AND #{targs} AND #{meth}" + #raise "TODO WITH #{trec} AND #{targs} AND #{meth}" + return RDL::Globals.types[:bot] end when RDL::Type::NominalType raise "TODO" From eb478e1b2c483adb291ec9a916662c3fc9a1b3e1 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 6 Nov 2020 09:59:40 -0500 Subject: [PATCH 096/124] add BERT guesses for instance vars, rets, and add some caching and refactoring --- lib/rdl/constraint.rb | 7 +- lib/rdl/heuristics.rb | 134 +++++++++++++++++++++++++++++--------- lib/rdl/typecheck.rb | 26 +++++++- lib/rdl/types/computed.rb | 4 ++ lib/rdl/types/var.rb | 13 +++- lib/types/core/array.rb | 7 +- 6 files changed, 156 insertions(+), 35 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index cc34ea7b..1bb98b59 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -276,9 +276,11 @@ def self.extract_meth_sol(tmeth) # [+ inf_type +] is the inferred type # [+ orig_type +] is the original, gold standard type def self.compare_single_type(inf_type, orig_type) + inf_type = inf_type.type if inf_type.optional? || inf_type.vararg? + orig_type = orig_type.type if orig_type.optional? || orig_type.vararg? if inf_type.to_s == orig_type.to_s return "E" - elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.to_s.include?("::") && inf_type.to_s.end_with?(orig_type.to_s) + elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.to_s.end_with?("::" + orig_type.to_s) ## inf_type.to_s.include?("::") ## needed for diferring scopes, e.g. TZInfo::Timestamp & Timestamp puts "Treating #{inf_type} and #{orig_type} as equivalent due to suffix rule.".red return "E" @@ -491,8 +493,9 @@ def self.extract_solutions() break if !@new_constraints end rescue => e - puts "RECEIVED ERROR #{e}" + puts "RECEIVED ERROR #{e} from #{e.backtrace}" ensure + puts "MAKING EXTRACTION REPORT" return make_extraction_report(typ_sols) end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 168a6833..b9111d29 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -6,6 +6,9 @@ class RDL::Heuristic @twin_cache = Hash.new { |h, k| h[k] = {} } @bert_cache = Hash.new { |h, k| h[k] = {} } + @vectorized_vars = {} ## Maps VarType object IDs to true/false, indicating whether or not they have been vectorized already + + $use_only_param_position = false ## whether or not to use just an arg's parameter position, or use all of its uses in a method, when running on BERT model def self.add(name, &blk) raise RuntimeError, "Expected heuristic name to be Symbol, given #{name}." unless name.is_a? Symbol @@ -76,7 +79,7 @@ def self.twin_network_guess(var_type) if @twin_cache[name1][name] sum += @twin_cache[name1][name] else - params = { words: [name1, name], method: "twin" } + params = { words: [name1, name], action: "twin" } uri.query = URI.encode_www_form(params) res = Net::HTTP.get_response(uri) raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" @@ -140,7 +143,6 @@ def self.twin_network_guess(var_type) def self.twin_network_constraints(pairs_enum) - uri = URI "http://127.0.0.1:5000/" sols = {} pairs_enum.each { |var1, var2| name1 = var1.base_name @@ -149,10 +151,7 @@ def self.twin_network_constraints(pairs_enum) if @twin_cache[name1][name2] sols[[var1, var2]] = @twin_cache[name1][name2] else - params = { words: [name1, name2], method: "twin" } - uri.query = URI.encode_www_form(params) - res = Net::HTTP.get_response(uri) - raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" + res = send_query(params) sols[[var1, var2]] = res.body.to_f @twin_cache[name1][name2] = res.body.to_f end @@ -176,45 +175,85 @@ def self.twin_network_constraints(pairs_enum) end - def self.bert_model_guess(var_type) + def self.send_query(params) uri = URI "http://127.0.0.1:5000/" + uri.query = URI.encode_www_form(params) + res = Net::HTTP.get_response(uri) + raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" + return res + end + + def self.vectorize_var(var_type) + return if @vectorized_vars[var_type.object_id] ## already vectorized and cached server side + puts "About to vectorize var #{var_type}" if (var_type.category == :arg) + ast = RDL::Typecheck.get_ast(var_type.cls, var_type.meth) + locs = get_var_loc(ast, var_type) + source = ast.loc.expression.source + puts "Querying for var #{var_type.base_name}" + puts "Sanity check: " + locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } + params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "arg" } + send_query(params) + elsif (var_type.category == :var) + var_type.meths_using_var.each { |klass, meth| + ast = RDL::Typecheck.get_ast(klass, meth) + locs = get_var_loc(ast, var_type) + source = ast.loc.expression.source + puts "Querying for var #{var_type.name} in method #{klass}##{meth}" + puts "Sanity check: " + locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } + params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "var", average: false } + send_query(params) + } + send_query({ action: "bert_vectorize", object_id: var_type.object_id, category: "var", average: true }) + elsif (var_type.category == :ret) + ast = RDL::Typecheck.get_ast(var_type.cls, var_type.meth) + begin_pos = ast.loc.expression.begin_pos + locs = [ast.loc.name.begin_pos - begin_pos, ast.loc.name.end_pos - begin_pos-1] ## for now, let's just try using method name + source = ast.loc.expression.source + puts "Querying for return #{var_type}" + puts "Sanity check: #{source[locs[0]..locs[1]]}" + params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "ret" } + send_query(params) + else + puts "not implemented yet" + end + @vectorized_vars[var_type.object_id] = true + end + + def self.bert_model_guess(var_type) + if (var_type.category == :arg) || (var_type.category == :var) || (var_type.category == :ret) sols = {} - - begin_loc1, end_loc1 = get_arg_loc(var_type) ## get start location of arg in source code + vectorize_var(var_type) RDL::Typecheck.type_vars_map.each { |t, vars| sum = 0 count = 0 raise "Got here for #{t}" if vars.empty? vars.each { |var2| - next if (var_type == var2) || !(var2.category == :arg) + next if (var_type == var2) || !(var_type.category == var2.category)#|| !((var2.category == :arg) || (var2.category == :var) || (var2.category == :ret)) + #next if ((var_type.category == :ret) || (var2.category == :ret)) && !(var_type.category == var2.category) # only compare rets with rets count += 1 if @bert_cache[var_type][var2] puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red sum += @bert_cache[var_type][var2] else - source1 = RDL::Typecheck.get_ast(var_type.cls, var_type.meth).loc.expression.source - source2 = RDL::Typecheck.get_ast(var2.cls, var2.meth).loc.expression.source - begin_loc2, end_loc2 = get_arg_loc(var2) - puts "Querying for vars #{var_type.base_name} and #{var2.base_name}.".red - puts "Sanity check: #{source1[begin_loc1..end_loc1]} and #{source2[begin_loc2..end_loc2]}" - params = { sources: [source1, source2], var1_locs: [begin_loc1, end_loc1], var2_locs: [begin_loc2, end_loc2], method: "bert"} - uri.query = URI.encode_www_form(params) - res = Net::HTTP.get_response(uri) - raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" + vectorize_var(var2) + puts "About to ask for similarity of #{var_type} and #{var2}" + res = send_query({ action: "get_similarity", id1: var_type.object_id, id2: var2.object_id }) sum += res.body.to_f @bert_cache[var_type][var2] = res.body.to_f @bert_cache[var2][var_type] = res.body.to_f - puts "Received similarity score of #{res.body.to_f} for vars #{var_type.cls}##{var_type.meth}##{var_type.base_name} and #{var2.cls}##{var2.meth}##{var2.base_name}".red + puts "Received similarity score of #{res.body.to_f} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}".red end } sim_score = (count == 0) ? 0 : sum / count - puts "Received overall sim_score average of #{sim_score} for var #{var_type.cls}##{var_type.meth}##{var_type.base_name} and type #{t}".green if sim_score != 0 + puts "Received overall sim_score average of #{sim_score} for var #{var_type.cls}##{var_type.meth}##{var_type.name} and type #{t}".green if sim_score != 0 - if sim_score > 0.9 + if sim_score > 0.8 sols[sim_score] = t else #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red @@ -230,9 +269,9 @@ def self.bert_model_guess(var_type) end end - def self.get_arg_loc(var_type) - ast = RDL::Typecheck.get_ast(var_type.cls, var_type.meth) + def self.get_var_loc(ast, var_type) begin_pos = ast.loc.expression.begin_pos + locs = [] if ast.type == :def meth_name, args, body = *ast @@ -242,13 +281,48 @@ def self.get_arg_loc(var_type) raise RuntimeError, "Unexpected ast type #{ast.type}" end - args.children.each { |c| - if (c.children[0].to_s == var_type.base_name) - ## Found the arg corresponding to var_type - return [c.loc.expression.begin_pos - begin_pos, c.loc.expression.end_pos - begin_pos - 1] ## translate it so that 0 is first position - end - } + if (var_type.category == :arg) + args.children.each { |c| + if (c.children[0].to_s == var_type.base_name) + ## Found the arg corresponding to var_type + locs << (c.loc.expression.begin_pos - begin_pos) ## translate it so that 0 is first position + locs << (c.loc.expression.end_pos - begin_pos - 1) + return locs if $use_only_param_position + end + } + end + search_ast_for_var_locs(body, var_type.name, locs, begin_pos) + + raise "Expected even number of locations." unless (locs.length % 2) == 0 + raise "Did not find var #{var_type} anywhere in ast #{ast}." if locs.length == 0 + return locs.sort ## locs will be sorted Array, where every two ints designate the beginning/end of an occurence of the arg in var_type + + end + + ## Recursively earches ast [Parser::AST::Node] for uses of local variable called var_name [String]. + ## Adds begining/end locations to locs Array, translating first by amount in + ## begin_pos [Integer]. + def self.search_ast_for_var_locs(ast, var_name, locs, begin_pos) + if !ast.is_a?(Parser::AST::Node) + return ## reached non-node of AST, stop recursing here. + elsif (ast.type == :lvar) || (ast.type == :ivar) || (ast.type == :cvar) || (ast.type == :gvar) + if (ast.children[0].to_s == var_name.to_s) + locs << (ast.loc.expression.begin_pos - begin_pos) + locs << (ast.loc.expression.end_pos - begin_pos - 1) + return + else + return + end + elsif (ast.type == :lvasgn) || (ast.type == :ivasgn) || (ast.type == :cvasgn) || (ast.type == :gvasgn) + if (ast.children[0].to_s == var_name.to_s) + locs << (ast.loc.name.begin_pos - begin_pos) + locs << (ast.loc.name.end_pos - begin_pos - 1) + end + search_ast_for_var_locs(ast.children[1], var_name, locs, begin_pos) + else + ast.children.each { |c| search_ast_for_var_locs(c, var_name, locs, begin_pos) } + end end end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 8f2dc906..2e01cf87 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -1407,6 +1407,28 @@ def self._tc(scope, env, e) end end + def self.find_ret_sites(scope, env, e) + case e.type + when :nil, :true, :false, :str, :string, :complex, :rational, :int, :float, :sym, :dstr, :xstr, :dsym, :regexp, :array, :hash, :irange, :erange, :self, :lvar, :ivar, :cvar, :gvar, :lvasgn, :ivasgn, :cvasgn, :gvasgn, :masgn, :op_asgn, :and_asgn, :or_asgn, :match_with_lvasgn, :nth_ref, :back_ref, :const, :defined?, :send, :csend, :yield, :and, :or + [e] + when :block ## TODO: check on this + [e] + when :if + rets = [] + rets += find_ret_sites(scope, env, e.children[1]) if !e.children[1].nil? + rets += find_ret_sites(scope, env, e.children[2]) if !e.children[2].nil? + raise "Expected some return sites, got none, for expression #{e}." if rets.empty? + return rets + when :case + rets = [] + e.children[1..-2].each { |c| rets += find_ret_sites(scope, env, c.children[-1]) } + rets += find_ret_sites(scope, env, e.children[-1]) if e.children[-1] + raise "Expected some return sites, got none, for expression #{e}." if rets.empty? + return rets + + end + end + def self.to_type(val, as_key=false) case val when Symbol @@ -1461,6 +1483,7 @@ def self.tc_var(scope, env, kind, name, e) else error :untyped_var, [kind_text, name, klass], e end + type.add_method_use(scope[:klass], scope[:meth]) if type.is_a?(RDL::Type::VarType) [env, type.canonical] else raise RuntimeError, "unknown kind #{kind}" @@ -1498,6 +1521,7 @@ def self.tc_vasgn(scope, env, kind, name, tright, e) end error :vasgn_incompat, [tright.to_s, tleft.to_s], e unless RDL::Type::Type.leq(tright, tleft, inst={}, true, ast: e) tright.instantiate(inst) + tleft.add_method_use(scope[:klass], scope[:meth]) if tleft.is_a?(RDL::Type::VarType) [env, tright.canonical] when :send meth = e.children[1] # note method name include =! @@ -1855,7 +1879,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, if @var_cache.has_key?(e.object_id) ## cache is based on syntactic location of method call ret_type = @var_cache[e.object_id] else - ret_type = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :ret, name: "ret") + ret_type = RDL::Type::VarType.new(cls: trecv, meth: meth, category: :call_ret, name: "ret") @var_cache[e.object_id] = ret_type end end diff --git a/lib/rdl/types/computed.rb b/lib/rdl/types/computed.rb index 59331c0a..13d71a59 100644 --- a/lib/rdl/types/computed.rb +++ b/lib/rdl/types/computed.rb @@ -32,6 +32,10 @@ def instantiate(inst) def widen self end + + def copy + return self + end def <=(other) ## TODO diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index 025509cb..ab6c2194 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -1,7 +1,7 @@ module RDL::Type class VarType < Type attr_reader :name, :cls, :meth, :category, :to_infer - attr_accessor :lbounds, :ubounds, :solution + attr_accessor :lbounds, :ubounds, :solution, :meths_using_var @@cache = {} @@print_XXX = false @@ -44,6 +44,7 @@ def initialize(name_or_hash) @name = name_or_hash[:name] ## might be nil if category is :ret @meth = name_or_hash[:meth] ## might be nil if ccategory is :var @category = name_or_hash[:category] + @meths_using_var = [] if @category == :var else raise "Unexpected argument #{name_or_hash} to RDL::Type::VarType.new." end @@ -163,6 +164,16 @@ def base_name if @category == :ret then @meth.to_s else @name.to_s.delete("@") end end + ## This is for global/class/instance variables. + ## When we observe these vars in a method, we keep track of + ## which class/method we saw it in. + def add_method_use(klass, meth) + raise "Expected category to be :var, got {@category}" unless @category == :var + klass = klass.to_s + meth = meth.to_s + @meths_using_var = @meths_using_var << [klass, meth] unless @meths_using_var.include?([klass, meth]) + end + def ==(other) return false if other.nil? other = other.canonical diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 6ba94e63..95feca54 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -20,7 +20,12 @@ def Array.output_type(trec, targs, meth_name, default1, default2=default1, use_s when RDL::Type::TupleType if targs.empty? || targs.all? { |t| t.is_a?(RDL::Type::SingletonType) } vals = RDL.type_cast((if use_sing_val then targs.map { |t| RDL.type_cast(t, "RDL::Type::SingletonType").val } else targs end), "Array<%any>", force: true) - res = RDL.type_cast(trec.params.send(meth_name, *vals), "Object", force: true) + begin + res = RDL.type_cast(trec.params.send(meth_name, *vals), "Object", force: true) + rescue ArgumentError => e + puts "GOT ERROR #{e} FOR METHOD #{meth_name} CALLED ON TREC #{trec} AND ARGS #{targs}" + return RDL::Globals.types[:bot] + end if !res && nil_false_default if default1 == :promoted_param trec.promote.params[0] From e90badc82b908f0559c97be8292930a52bb1a13e Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Tue, 22 Dec 2020 10:35:10 -0500 Subject: [PATCH 097/124] latest updates --- lib/rdl/constraint.rb | 67 +++++++--- lib/rdl/heuristics.rb | 131 +++++++++++++++++--- lib/rdl/typecheck.rb | 74 ++++++++--- lib/rdl/types/method.rb | 2 +- lib/rdl/types/nominal.rb | 2 +- lib/rdl/types/type.rb | 5 +- lib/rdl/types/union.rb | 3 +- lib/rdl/wrap.rb | 2 + lib/types/core/array.rb | 24 +--- lib/types/core/enumerator.rb | 3 +- lib/types/core/integer.rb | 10 +- lib/types/core/kernel.rb | 2 + lib/types/core/range.rb | 3 + lib/types/core/string.rb | 5 +- lib/types/rails/active_record/comp_types.rb | 2 +- 15 files changed, 253 insertions(+), 82 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 1bb98b59..d00c7ea9 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,7 +1,7 @@ require 'csv' -$use_twin_network = true -$use_heuristics = false +$use_twin_network = false +$use_heuristics = true class << RDL::Typecheck ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. @@ -61,7 +61,7 @@ def self.resolve_constraints } end - def self.extract_var_sol(var, category) + def self.extract_var_sol(var, category, add_sol_to_graph = true) #raise "Expected VarType, got #{var}." unless var.is_a?(RDL::Type::VarType) return var.canonical unless var.is_a?(RDL::Type::VarType) if category == :arg @@ -98,6 +98,7 @@ def self.extract_var_sol(var, category) RDL::Logging.log_header :heuristic, :debug, "Beginning Heuristics..." RDL::Heuristic.rules.each { |name, rule| + next if (@counter == 1) && (name == :twin_network) start_time = Time.now RDL::Logging.log :heuristic, :debug, "Trying rule `#{name}` for variable #{var}." typ = rule.call(var) @@ -106,8 +107,10 @@ def self.extract_var_sol(var, category) begin typ.each { |t| t = t.canonical - var.add_and_propagate_upper_bound(t, nil, new_cons) - var.add_and_propagate_lower_bound(t, nil, new_cons) + if add_sol_to_graph + var.add_and_propagate_upper_bound(t, nil, new_cons) + var.add_and_propagate_lower_bound(t, nil, new_cons) + end RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" puts "Successfully applied twin network! Solution #{t} for #{var}".blue @new_constraints = true if !new_cons.empty? @@ -121,8 +124,11 @@ def self.extract_var_sol(var, category) new_cons = {} begin typ = typ.canonical - var.add_and_propagate_upper_bound(typ, nil, new_cons) - var.add_and_propagate_lower_bound(typ, nil, new_cons) + if add_sol_to_graph + puts "Attempting to apply solution #{typ} for #{var}" if var.base_name == "section_id" + var.add_and_propagate_upper_bound(typ, nil, new_cons) + var.add_and_propagate_lower_bound(typ, nil, new_cons) + end =begin new_cons.each { |var, bounds| bounds.each { |u_or_l, t, _| @@ -140,6 +146,10 @@ def self.extract_var_sol(var, category) rescue RDL::Typecheck::StaticTypeError => e RDL::Logging.log :heuristic, :debug_error, "Attempted to apply heuristic rule #{name} solution #{typ} to var #{var}" RDL::Logging.log :heuristic, :trace, "... but got the following error: #{e}" + if var.base_name == "section_id" + puts "Attempted to apply heuristic rule #{name} solution #{typ} to var #{var}" + puts "... but got the following error: #{e}" + end undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is ensure @@ -162,7 +172,7 @@ def self.extract_var_sol(var, category) return sol if sol.is_a?(RDL::Type::VarType) ## don't add var type as solution sol = sol.canonical var.add_and_propagate_upper_bound(sol, nil, new_cons) - var.add_and_propagate_lower_bound(sol, nil, new_cons) + var.add_and_propagate_lower_bound(sol, nil, new_cons) unless sol.is_a?(RDL::Type::StructuralType) || (sol.is_a?(RDL::Type::IntersectionType) && sol.types.any? { |t| t.is_a?(RDL::Type::StructuralType) } ) =begin new_cons.each { |var, bounds| bounds.each { |u_or_l, t, _| @@ -172,7 +182,7 @@ def self.extract_var_sol(var, category) =end @new_constraints = true if !new_cons.empty? RDL::Logging.log :inference, :trace, "New Constraints branch B" if !new_cons.empty? - + #raise "Adding Solution Array or Number to variable #{var}".red if (sol.to_s == "(Array or Number)") && var.to_s != "{ { DashboardSection# var: @row }#[] call_ret: ret }" if sol.is_a?(RDL::Type::GenericType) new_params = sol.params.map { |p| if p.is_a?(RDL::Type::VarType) && !p.to_infer then p else extract_var_sol(p, category) end } sol = RDL::Type::GenericType.new(sol.base, *new_params) @@ -280,13 +290,13 @@ def self.compare_single_type(inf_type, orig_type) orig_type = orig_type.type if orig_type.optional? || orig_type.vararg? if inf_type.to_s == orig_type.to_s return "E" - elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.to_s.end_with?("::" + orig_type.to_s) ## inf_type.to_s.include?("::") + elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && abbreviated_class?(orig_type.to_s, inf_type.to_s) ## needed for diferring scopes, e.g. TZInfo::Timestamp & Timestamp puts "Treating #{inf_type} and #{orig_type} as equivalent due to suffix rule.".red return "E" - elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.klass.ancestors.map { |a| a.to_s }.include?(orig_type.to_s) + elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } return "E" - elsif inf_type.is_a?(RDL::Type::UnionType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.types.all? { |t| t.is_a?(RDL::Type::NominalType) && t.klass.ancestors.map { |a| a.to_s }.include?(orig_type.to_s) } + elsif inf_type.is_a?(RDL::Type::UnionType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.types.all? { |t| t.is_a?(RDL::Type::NominalType) && t.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } } return "E" elsif !inf_type.is_a?(RDL::Type::VarType) && (orig_type.is_a?(RDL::Type::TopType)) return "E" @@ -309,12 +319,18 @@ def self.compare_single_type(inf_type, orig_type) end end + def self.abbreviated_class?(abbrev, original) + abbrev = abbrev.to_s + original = original.to_s + original.end_with?("::" + abbrev) + end + def self.make_extraction_report(typ_sols) report = RDL::Reporting::InferenceReport.new twin_csv = CSV.open("#{if !$use_twin_network then 'no_' end}twin_#{if $use_heuristics then 'heur_' end}infer_data.csv", 'wb') - twin_csv << ["Class", "Name", "Arg/Ret/Var", - "Inferred Type", "Original Type", "Exact (E) / Up to Parameter (P) / Got Type (T) / None (N)"] + twin_csv << ["Class", "Method Name", "Arg/Ret/Var", "Variable Name", + "Inferred Type", "Original Type", "Exact (E) / Up to Parameter (P) / Got Type (T) / None (N)", "Source Code"] compares = Hash.new 0 ## Twin CSV format: [Class, Name, ] #return unless $orig_types @@ -340,21 +356,32 @@ def self.make_extraction_report(typ_sols) end if orig_typ.is_a?(RDL::Type::MethodType) meth_types += 1 + ast = RDL::Typecheck.get_ast(klass, meth) + code = ast.loc.expression.source orig_typ.args.each_with_index { |orig_arg_typ, i | inf_arg_type = typ.solution.args[i] comp = inf_arg_type.nil? ? "N" : compare_single_type(inf_arg_type, orig_arg_typ) compares[comp] += 1 - twin_csv << [klass, meth, "Arg", inf_arg_type.to_s, orig_arg_typ.to_s, comp] + if typ.args[i].nil? + name = nil + elsif (typ.args[i].optional? || typ.args[i].vararg?) + name = typ.args[i].type.base_name + elsif typ.args[i].is_a?(RDL::Type::FiniteHashType) + name = typ.args[i].to_s + else + name = typ.args[i].base_name + end + twin_csv << [klass, meth, "Arg", name, inf_arg_type.to_s, orig_arg_typ.to_s, comp, code] arg_types +=1 } inf_ret_type = typ.solution.ret comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) compares[comp] += 1 - twin_csv << [klass, meth, "Ret", inf_ret_type.to_s, orig_typ.ret.to_s, comp] + twin_csv << [klass, meth, "Ret", "", inf_ret_type.to_s, orig_typ.ret.to_s, comp, code] else comp = typ.solution.nil? ? "N" : compare_single_type(typ.solution, orig_typ) compares[comp] += 1 - twin_csv << [klass, meth, "Var", typ.solution.to_s, orig_typ.to_s, comp] + twin_csv << [klass, meth, "Var", meth, typ.solution.to_s, orig_typ.to_s, comp, ""] var_types += 1 end @@ -409,15 +436,15 @@ def self.make_extraction_report(typ_sols) def self.extract_solutions() ## Go through once to come up with solution for all var types. RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" - counter = 0; + @counter = 0; used_twin = false typ_sols = {} loop do - counter += 1 + @counter += 1 @new_constraints = false typ_sols = {} - RDL::Logging.log :inference, :info, "[#{counter}] Running solution extraction..." + RDL::Logging.log :inference, :info, "[#{@counter}] Running solution extraction..." RDL::Globals.constrained_types.each { |klass, name| begin diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index b9111d29..6cfbcd43 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -40,7 +40,7 @@ def self.struct_to_nominal(var_type) return if struct_types.empty? meth_names = struct_types.map { |st| st.methods.keys }.flatten.uniq matching_classes = matching_classes(meth_names) - matching_classes.reject! { |c| c.to_s.start_with?("# 0.9 + puts message.green + sols[sim_score] = t + else + puts message.red + #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red + end } +=begin + ## TRYING COMPARE WITH SINGLE VAR DOWN HERE. sim_score = (count == 0) ? 0 : sum / count puts "Received overall sim_score average of #{sim_score} for var #{var_type.cls}##{var_type.meth}##{var_type.name} and type #{t}".green if sim_score != 0 @@ -258,6 +327,7 @@ def self.bert_model_guess(var_type) else #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red end +=end } ## return list of types that are sorted from highest similarity score to lowest @@ -269,6 +339,34 @@ def self.bert_model_guess(var_type) end end + def self.get_ret_sites(ast) + begin_pos = ast.loc.expression.begin_pos + locs = [] + + if ast.type == :def + meth_name, args, body = *ast + elsif ast.type == :defs + _, meth_name, args, body = *ast + else + raise RuntimeError, "Unexpected ast type #{ast.type}" + end + + return [] if body.nil? + + ret_sites = RDL::Typecheck.find_ret_sites(body) + + ret_sites.each { |exp, _| + if (exp.type == :send) || (exp.type == :csend) + locs << exp.loc.selector.begin_pos - begin_pos + locs << exp.loc.selector.end_pos - begin_pos + else + locs << (exp.loc.expression.begin_pos - begin_pos) + locs << (exp.loc.expression.end_pos - begin_pos - 1) + end + } + return locs + end + def self.get_var_loc(ast, var_type) begin_pos = ast.loc.expression.begin_pos locs = [] @@ -285,8 +383,8 @@ def self.get_var_loc(ast, var_type) args.children.each { |c| if (c.children[0].to_s == var_type.base_name) ## Found the arg corresponding to var_type - locs << (c.loc.expression.begin_pos - begin_pos) ## translate it so that 0 is first position - locs << (c.loc.expression.end_pos - begin_pos - 1) + locs << (c.loc.name.begin_pos - begin_pos) ## translate it so that 0 is first position + locs << (c.loc.name.end_pos - begin_pos - 1) return locs if $use_only_param_position end } @@ -365,6 +463,8 @@ def model_set_type RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") || (var.base_name.end_with? "nums") || (var.base_name.end_with? "counts") then RDL::Globals.parser.scan_str "#T Array" end } RDL::Heuristic.add(:predicate_method) { |var| if var.base_name.end_with?("?") then RDL::Globals.types[:bool] end } RDL::Heuristic.add(:string_name) { |var| if var.base_name.end_with?("name") then RDL::Globals.types[:string] end } + + RDL::Heuristic.add(:hash_access) { |var| old_var = var var = var.type if old_var.is_a?(RDL::Type::OptionalType) @@ -388,11 +488,11 @@ def model_set_type raise "Method should be one of :[] or :[]=, got #{meth}." end if value_type.is_a?(RDL::Type::UnionType) - RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical + RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg, false) }).drop_vars.canonical elsif value_type.is_a?(RDL::Type::IntersectionType) RDL::Type::IntersectionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical else - value_type = RDL::Typecheck.extract_var_sol(value_type, :arg) + value_type = RDL::Typecheck.extract_var_sol(value_type, :arg, false) end #value_type = value_type.drop_vars!.canonical if (value_type.is_a?(RDL::Type::UnionType) || value_type.is_a?(RDL::Type::IntersectionType)) && (!value_type.types.all? { |t| t.is_a?(RDL::Type::VarType) }) hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: @@ -407,6 +507,7 @@ def model_set_type end end } + end ### For rules involving :include?, :==, :!=, etc. we would need to track the exact receiver/args used in the method call, and somehow store these in the bounds created for a var type. diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 2e01cf87..7ab38c16 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -302,7 +302,7 @@ def self._infer(klass, meth) block_type = meth_type.block ret_vartype = meth_type.ret - raise "Expected VarTypes in MethodType to be inferred, got #{meth_type}." unless (arg_types + [block_type] + [ret_vartype]).all? { |t| !t.nil? && (t.kind_of_var_input? || (meth == :initialize)) } + raise "Expected VarTypes in MethodType to be inferred, got #{meth_type}." unless (arg_types + [block_type] + [ret_vartype]).all? { |t| !t.nil? && (t.kind_of_var_input? || (t.is_a?(RDL::Type::OptionalType) && t.type.is_a?(RDL::Type::GenericType) && (t.type.base == RDL::Globals.types[:hash])) || (meth == :initialize)) } end if ast.type == :def @@ -530,7 +530,7 @@ def self.args_hash(scope, env, type, args, ast, kind) targs[kw] = tkw.type env = env.merge(Env.new(kw => tkw.type)) elsif arg.type == :kwrestarg - error :type_args_no_kws, [kind], e, block: (kind == 'block') unless targ.is_a?(RDL::Type::FiniteHashType) + error :type_args_no_kws, [kind], arg, block: (kind == 'block') unless targ.is_a?(RDL::Type::FiniteHashType) error :type_args_no_kw_rest, [kind], arg, block: (kind == 'block') if targ.rest.nil? targs[arg.children[0]] = RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Globals.types[:symbol], targ.rest) kw_rest_matched = true @@ -559,7 +559,11 @@ def self.get_super_owner(slf, m) when RDL::Type::SingletonType if slf.nominal.name == 'Class' trecv_owner = get_super_owner_from_class(slf.val.singleton_class, m) - RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(trecv_owner)) + if trecv_owner.to_s == "BasicObject" + RDL::Type::NominalType.new(trecv_owner) + else + RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(trecv_owner)) + end else raise Exception, "self is singleton class but nominal is not Class, it is #{slf.nominal.name}" end @@ -1407,25 +1411,63 @@ def self._tc(scope, env, e) end end - def self.find_ret_sites(scope, env, e) + # Returns Hash, + # where return site expression maps to a boolean indicating whether the expression was returned + # with a `return` expression (true) or not (false). + def self.find_ret_sites(e) case e.type - when :nil, :true, :false, :str, :string, :complex, :rational, :int, :float, :sym, :dstr, :xstr, :dsym, :regexp, :array, :hash, :irange, :erange, :self, :lvar, :ivar, :cvar, :gvar, :lvasgn, :ivasgn, :cvasgn, :gvasgn, :masgn, :op_asgn, :and_asgn, :or_asgn, :match_with_lvasgn, :nth_ref, :back_ref, :const, :defined?, :send, :csend, :yield, :and, :or - [e] + when :nil, :true, :false, :str, :string, :complex, :rational, :int, :float, :sym, :dstr, :xstr, :dsym, :regexp, :array, :hash, :irange, :erange, :self, :lvar, :ivar, :cvar, :gvar, :lvasgn, :ivasgn, :cvasgn, :gvasgn, :masgn, :op_asgn, :and_asgn, :or_asgn, :match_with_lvasgn, :nth_ref, :back_ref, :const, :defined?, :send, :csend, :yield, :and, :or, :break, :redo, :next, :retry, :super, :zsuper + return {e => false} when :block ## TODO: check on this - [e] + return {e.children[0] => false } when :if - rets = [] - rets += find_ret_sites(scope, env, e.children[1]) if !e.children[1].nil? - rets += find_ret_sites(scope, env, e.children[2]) if !e.children[2].nil? + rets = {} + rets = rets.merge(find_ret_sites(e.children[1])) if !e.children[1].nil? + rets = rets.merge(find_ret_sites(e.children[2])) if !e.children[2].nil? raise "Expected some return sites, got none, for expression #{e}." if rets.empty? return rets when :case - rets = [] - e.children[1..-2].each { |c| rets += find_ret_sites(scope, env, c.children[-1]) } - rets += find_ret_sites(scope, env, e.children[-1]) if e.children[-1] + rets = {} + e.children[1..-2].each { |c| rets = rets.merge(find_ret_sites(c.children[-1])) } + rets = rets.merge(find_ret_sites(e.children[-1])) if e.children[-1] raise "Expected some return sites, got none, for expression #{e}." if rets.empty? return rets - + when :while, :until, :while_post, :until_post + if e.children[1] + sites = find_ret_sites(e.children[1]) + sites.delete_if { |exp, used_ret| !used_ret } + if sites.empty? + return { e => false} + else + return sites + end + else + return { e => false } + end + when :nsure + return find_ret_sites(e.children[0]) + when :rescue + rets = {} + e.children[1..-2].each { |resbody| + rets = rets.merge(find_ret_sites(resbody)) + } + return rets + when :resbody + if e.children[2] + return find_ret_sites(e.children[2]) + else + return { e => false } + end + when :return + { e => true } + when :begin, :kwbegin # sequencing + rets = {} + e.children[0..-2].each { |c| rets = rets.merge(find_ret_sites(c)) } + rets.delete_if { |exp, used_ret| !used_ret } + rets = rets.merge(find_ret_sites(e.children[-1])) + return rets + else + error :unsupported_expression, [e.type, e], e end end @@ -1875,6 +1917,8 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, ret_type = RDL::Globals.types[:string] elsif meth == :to_i ret_type = RDL::Globals.types[:integer] + elsif meth == :== + ret_type = RDL::Globals.types[:bool] else if @var_cache.has_key?(e.object_id) ## cache is based on syntactic location of method call ret_type = @var_cache[e.object_id] @@ -2488,6 +2532,8 @@ def self.make_unknown_method_type(klass, meth) ## all method types will be given a variable type for blocks anyway, so no need to add a new param here when :keyrest raise "Not currently supported, for method #{meth} of class #{klass}" + #hash_type = RDL::Type::GenericType.new(RDL::Globals.types[:hash], RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: "#{var_name}_key"), RDL::Type::VarType.new(cls: klass, meth: meth, category: :arg, name: "#{var_name}_param")) + #arg_types << RDL::Type::OptionalType.new(hash_type) else raise "Unexpected parameter type #{param[0]}." end diff --git a/lib/rdl/types/method.rb b/lib/rdl/types/method.rb index 20a09ad3..d89611ab 100644 --- a/lib/rdl/types/method.rb +++ b/lib/rdl/types/method.rb @@ -24,7 +24,7 @@ def initialize(args, block, ret, sol=false) arg = arg.type if arg.instance_of? RDL::Type::AnnotatedArgType case arg when OptionalType - raise "Optional arguments not allowed after varargs" if state == :vararg + #raise "Optional arguments not allowed after varargs" if state == :vararg #raise "Optional arguments not allowed after named arguments" if state == :hash state = :optional when VarargType diff --git a/lib/rdl/types/nominal.rb b/lib/rdl/types/nominal.rb index 5ad8a266..51f5943e 100644 --- a/lib/rdl/types/nominal.rb +++ b/lib/rdl/types/nominal.rb @@ -10,7 +10,7 @@ class << self def self.new(name) name = name.to_s - name = "Integer" if RDL::Config.instance.number_mode && ["Float", "Rational", "Complex", "BigDecimal"].include?(name) + name = "Integer" if RDL::Config.instance.number_mode && ["Float", "Rational", "Complex", "BigDecimal", "Numeric"].include?(name) t = @@cache[name] return t if t t = self.__new__ name diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 004028e8..6c534095 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -58,11 +58,11 @@ def kind_of_var_input? end def array_type? - is_a?(TupleType) || (is_a?(GenericType) && ((base == RDL::Globals.types[:array]) || ((defined? ActiveRecord_Relation) && (base.klass == ActiveRecord_Relation)))) || (self == RDL::Globals.types[:array]) + is_a?(TupleType) || (is_a?(GenericType) && ((base == RDL::Globals.types[:array]) || ((defined? ActiveRecord_Relation) && (base.klass == ActiveRecord_Relation)))) || (self == RDL::Globals.types[:array]) || (is_a?(UnionType) && types.all? { |t| t.array_type? }) end def hash_type? - is_a?(FiniteHashType) || (is_a?(GenericType) && (base == RDL::Globals.types[:hash])) || (self == RDL::Globals.types[:hash]) + is_a?(FiniteHashType) || (is_a?(GenericType) && (base == RDL::Globals.types[:hash])) || (self == RDL::Globals.types[:hash]) || (is_a?(UnionType) && types.all? { |t| t.hash_type? }) end # default behavior, override in appropriate subclasses @@ -95,6 +95,7 @@ def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_con right = right.type if right.is_a? NonNullType left = left.canonical right = right.canonical + #puts "About to try #{left} <= #{right} with #{inst} and #{ileft}" return true if left.equal?(right) # top and bottom diff --git a/lib/rdl/types/union.rb b/lib/rdl/types/union.rb index 078d5c21..22894020 100644 --- a/lib/rdl/types/union.rb +++ b/lib/rdl/types/union.rb @@ -73,7 +73,8 @@ def drop_vars! end def drop_vars - return self if @types.all? { |t| t.is_a? VarType } ## when all are VarTypes, we have nothing concrete to reduce to, so don't want to drop vars + return RDL::Globals.types[:bot] if @types.all? { |t| t.is_a? VarType } ## when all are VarTypes, we have nothing concrete to reduce to, so don't want to drop vars + #return self if @types.all? { |t| t.is_a? VarType } ## when all are VarTypes, we have nothing concrete to reduce to, so don't want to drop vars new_types = [] for i in 0..(@types.length-1) if @types[i].is_a?(IntersectionType) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 62c3d51d..7d0b45cd 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -874,6 +874,8 @@ def self.do_infer(sym, render_report: true) } RDL::Globals.to_infer[sym] = Set.new + #RDL::Heuristic.visualize_bert(:ret) + RDL::Typecheck.resolve_constraints report = RDL::Typecheck.extract_solutions diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index 95feca54..b2dc3f5f 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -297,25 +297,6 @@ def Array.each_arg(trec, num) end end -def Array.each_arg2(trec) - case trec - when RDL::Type::TupleType - if trec.params.all? { |t| t.is_a?(RDL::Type::TupleType) } - second_params = RDL::Type::UnionType.new(*trec.params.map { |t| t.params[1] }) - second_params = second_params.canonical if second_params.is_a?(RDL::Type::UnionType) - return second_params - else - return RDL::Type::OptionalType.new(promoted_or_t(trec)) - end - else - if trec.params[0].is_a?(RDL::Type::TupleType) - return trec.params[0].params[1] - else - return RDL::Type::OptionalType.new(promoted_or_t(trec)) - end - end -end - RDL.type :Array, :each_index, '() { (Integer) -> %any } -> self' RDL.type :Array, :each_index, '() -> Enumerator' @@ -437,6 +418,8 @@ def Array.reverse_output(trec) RDL.type :Array, :rotate!, '(?Integer) -> ``promote_tuple!(trec)``' RDL.type :Array, :sample, '() -> ``promoted_or_t(trec)``' RDL.type :Array, :sample, '(Integer) -> ``promote_tuple(trec)``' +RDL.type :Array, :sample, '({ random: [ rand: () -> Float ] }) -> ``promote_tuple(trec)``' +RDL.type :Array, :sample, '(Integer, { random: [ rand: () -> Float ] }) -> ``promote_tuple(trec)``' RDL.type :Array, :select, '() { (``promoted_or_t(trec)``) -> %bool } -> ``promote_tuple(trec)``' RDL.type :Array, :select, '() -> ``RDL::Type::GenericType.new(RDL::Type::NominalType.new(Enumerator), promoted_or_t(trec))``' RDL.type :Array, :select!, '() { (``promoted_or_t(trec)``) -> %bool } -> ``promote_tuple!(trec)``' @@ -444,7 +427,9 @@ def Array.reverse_output(trec) RDL.type :Array, :shift, '() -> ``promote_tuple!(trec); RDL::Globals.parser.scan_str "#T t"``' RDL.type :Array, :shift, '(Integer) -> ``promote_tuple!(trec)``' RDL.type :Array, :shuffle, '() -> ``promote_tuple(trec)``' +RDL.type :Array, :shuffle, '({ random: [ rand: () -> Float ] }) -> ``promote_tuple(trec)``' RDL.type :Array, :shuffle!, '() -> ``promote_tuple!(trec)``' +RDL.type :Array, :shuffle!, '({ random: [ rand: () -> Float ] }) -> ``promote_tuple!(trec)``' RDL.rdl_alias :Array, :size, :length RDL.rdl_alias :Array, :slice, :[] RDL.type :Array, :slice!, '(Range) -> ``promote_tuple!(trec)``' @@ -550,6 +535,7 @@ def Array.reverse_output(trec) RDL.type :Array, :initialize, '(Integer) -> self' RDL.type :Array, :initialize, '(Integer, t) -> self' RDL.type :Array, :initialize, '(Array) -> self' +RDL.type :Array, :initialize, '(Integer) { (?Integer) -> t } -> self' RDL.type :Array, :insert, '(Integer, *t) -> Array' RDL.type :Array, :inspect, '() -> String' RDL.type :Array, :join, '(?String) -> String' diff --git a/lib/types/core/enumerator.rb b/lib/types/core/enumerator.rb index f884a54c..6e002ae6 100644 --- a/lib/types/core/enumerator.rb +++ b/lib/types/core/enumerator.rb @@ -20,5 +20,6 @@ RDL.type :Enumerator, :peek_values, '() -> Array' RDL.type :Enumerator, :rewind, '() -> self' RDL.type :Enumerator, :size, '() -> Integer or Float or nil' -RDL.rdl_alias :Enumerator, :with_index, :each_with_index +RDL.type :Enumerator, :with_index, "(?Integer) -> self" +RDL.type :Enumerator, :with_index, "(?Integer) { (t, Integer) -> %any } -> %any" RDL.rdl_alias :Enumerator, :with_object, :each_with_object diff --git a/lib/types/core/integer.rb b/lib/types/core/integer.rb index 1c6d2564..f1ebeb43 100644 --- a/lib/types/core/integer.rb +++ b/lib/types/core/integer.rb @@ -137,7 +137,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :fdiv, '(Complex) -> ``sing_or_type(trec, targs, :fdiv, "Complex")``' RDL.pre(:Integer, :fdiv) { |x| if (x.real.is_a?(BigDecimal)||x.imaginary.is_a?(BigDecimal)) then (if x.real.is_a?(Float) then (x.real!=Float::INFINITY && !(x.real.nan?)) elsif(x.imaginary.is_a?(Float)) then x.imaginary!=Float::INFINITY && !(x.imaginary.nan?) else true end) else true end && if (x.real.is_a?(Rational) && x.imaginary.is_a?(Float)) then !x.imaginary.nan? else true end} -RDL.type :Integer, :to_s, '() -> String' +RDL.type :Integer, :to_s, '(?Integer) -> String' RDL.type :Integer, :inspect, '() -> String' RDL.type :Integer, :magnitude, '() -> Integer r {{ r>=0 }}' ## TODO @@ -163,7 +163,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :eql?, '(Object) -> ``sing_or_type(trec, targs, :eql?, "%bool")``' RDL.type :Integer, :hash, '() -> Integer' RDL.type :Integer, :ceil, '() -> ``sing_or_type(trec, targs, :ceil, "Integer")``' -RDL.type :Integer, :chr, '(Encoding) -> String' +RDL.type :Integer, :chr, '(?Encoding) -> String' RDL.type :Integer, :coerce, '(%numeric) -> [%real, %real]' RDL.pre(:Integer, :coerce) { |x| if x.is_a?(Complex) then x.imaginary==0 else true end} RDL.type :Integer, :conj, '() -> ``sing_or_type(trec, targs, :conj, "Integer")``' @@ -335,7 +335,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :fdiv, '(Complex) -> Complex' RDL.pre(:Integer, :fdiv) { |x| if (x.real.is_a?(BigDecimal)||x.imaginary.is_a?(BigDecimal)) then (if x.real.is_a?(Float) then (x.real!=Float::INFINITY && !(x.real.nan?)) elsif(x.imaginary.is_a?(Float)) then x.imaginary!=Float::INFINITY && !(x.imaginary.nan?) else true end) else true end && if (x.real.is_a?(Rational) && x.imaginary.is_a?(Float)) then !x.imaginary.nan? else true end} -RDL.type :Integer, :to_s, '() -> String' +RDL.type :Integer, :to_s, '(?Integer) -> String' RDL.type :Integer, :inspect, '() -> String' RDL.type :Integer, :magnitude, '() -> Integer r {{ r>=0 }}' @@ -361,7 +361,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :eql?, '(Object) -> %bool' RDL.type :Integer, :hash, '() -> Integer' RDL.type :Integer, :ceil, '() -> Integer' -RDL.type :Integer, :chr, '(Encoding) -> String' +RDL.type :Integer, :chr, '(?Encoding) -> String' RDL.type :Integer, :coerce, '(%numeric) -> [%real, %real]' RDL.pre(:Integer, :coerce) { |x| if x.is_a?(Complex) then x.imaginary==0 else true end} RDL.type :Integer, :conj, '() -> Integer' @@ -406,6 +406,6 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :to_int, '() -> Integer' RDL.type :Integer, :to_r, '() -> Rational' RDL.type :Integer, :truncate, '() -> Integer' -RDL.type :Integer, :upto, '(Integer) { (Integer) -> %any } -> Integer' +RDL.type :Integer, :upto, '(Integer) { (?Integer) -> %any } -> Integer' RDL.type :Integer, :upto, '(Integer) -> Enumerator' RDL.type :Integer, :zero?, '() -> %bool' diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index 0f9f37b9..bf90d2f5 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -1,6 +1,7 @@ RDL.nowrap :Kernel RDL.type :Kernel, 'self.Array', '([to_ary: () -> Array]) -> Array' +RDL.type :Kernel, 'self.Array', "(Range) -> Array" RDL.type :Kernel, 'self.Array', '([to_a: () -> Array]) -> Array' RDL.type :Kernel, 'self.===', "(%any) -> %bool" RDL.type :Kernel, 'self.Complex', '(Numeric x, Numeric y) -> Complex' @@ -61,6 +62,7 @@ # RDL.type :Kernel, 'self.print', '(*[to_s : () -> String] -> nil' RDL.type :Kernel, 'self.printf', '(?IO, ?String, *%any) -> nil' RDL.type :Kernel, :proc, '() {(*%any) -> %any} -> Proc' # TODO more precise +RDL.type :Kernel, :public_send, '(Symbol or String, *%any args) -> %any', wrap: false RDL.type :Kernel, 'self.putc', '(Integer) -> Integer' RDL.type :Kernel, 'self.puts', '(*[to_s : () -> String]) -> nil' RDL.type :Kernel, 'self.raise', '() -> %bot' diff --git a/lib/types/core/range.rb b/lib/types/core/range.rb index de5e993c..933d47cb 100644 --- a/lib/types/core/range.rb +++ b/lib/types/core/range.rb @@ -19,6 +19,7 @@ RDL.type :Range, :first, '(Integer n) -> Array' RDL.type :Range, :hash, '() -> Integer' RDL.type :Range, :include?, '(%any obj) -> %bool' +RDL.type :Range, :initialize, "(x, x) -> self" RDL.type :Range, :inspect, '() -> String' RDL.type :Range, :last, '() -> t' RDL.type :Range, :last, '(Integer n) -> Array' @@ -34,4 +35,6 @@ RDL.type :Range, :size, '() -> Integer or nil' RDL.type :Range, :step, '(?Integer n) { (t) -> %any } -> self' RDL.type :Range, :step, '(?Integer n) -> Enumerator' +RDL.type :Range, :to_a, '() -> Array' +RDL.type :Range, :to_ary, '() -> Array' RDL.type :Range, :to_s, '() -> String' diff --git a/lib/types/core/string.rb b/lib/types/core/string.rb index 548eac60..fa26f47a 100644 --- a/lib/types/core/string.rb +++ b/lib/types/core/string.rb @@ -350,13 +350,14 @@ def String.mutate_output(trec, meth) RDL.type :String, :slice!, '(String) -> ``string_promote!(trec)``' RDL.type :String, :split, '(?(Regexp or String), ?Integer) -> ``output_type(trec, targs, :split, "Array")``' RDL.type :String, :split, '(?Integer) -> ``output_type(trec, targs, :split, "Array")``' -RDL.type :String, :squeeze, '() -> ``output_type(trec, targs, :squeeze, "String")``' -RDL.type :String, :squeeze!, '() -> ``mutate_output(trec, :squeeze!)``' +RDL.type :String, :squeeze, '(?String) -> ``output_type(trec, targs, :squeeze, "String")``' +RDL.type :String, :squeeze!, '(?String) -> ``mutate_output(trec, :squeeze!)``' RDL.type :String, :start_with?, '(* String) -> ``output_type(trec, targs, :start_with?, "%bool")``' RDL.type :String, :strip, '() -> ``output_type(trec, targs, :strip, "String")``' RDL.type :String, :strip!, '() -> ``mutate_output(trec, :strip!)``' RDL.type :String, :sub, '(Regexp or String, String or Hash) -> ``output_type(trec, targs, :sub, "String")``', wrap: false # Can't wrap these, since they mess with $1 etc RDL.type :String, :sub, '(Regexp or String) {(String) -> %any} -> ``output_type(trec, targs, :sub, "String")``', wrap: false +RDL.type :String, :sub, '(Regexp or String) {() -> %any} -> ``output_type(trec, targs, :sub, "String")``', wrap: false RDL.type :String, :sub!, '(Regexp or String, String) -> ``string_promote!(trec)``', wrap: false RDL.type :String, :sub!, '(Regexp or String) {(String) -> %any} -> ``string_promote!(trec)``', wrap: false RDL.type :String, :succ, '() -> ``output_type(trec, targs, :succ, "String")``' diff --git a/lib/types/rails/active_record/comp_types.rb b/lib/types/rails/active_record/comp_types.rb index 3fb82b36..a65d9130 100644 --- a/lib/types/rails/active_record/comp_types.rb +++ b/lib/types/rails/active_record/comp_types.rb @@ -674,7 +674,7 @@ def self.plus_output_type(trec, targs) [trec, targs[0]].each { |t| case t when RDL::Type::GenericType - raise "Expected ActiveRecord_Relation, got #{t} for #{trec} and #{targs}." unless (t.base.name == "ActiveRecord_Relation") + raise "Expected ActiveRecord_Relation or Array, got #{t} for #{trec} and #{targs}." unless (t.base.name == "ActiveRecord_Relation") or (t.base.name == "Array") param0 = t.params[0] case param0 when RDL::Type::GenericType From 5234486a81319c5122525de60ec52f664d7b8621 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 22 Jan 2021 13:53:06 -0500 Subject: [PATCH 098/124] many updates, just before working on batch queries --- lib/rdl/class_indexer.rb | 9 +-- lib/rdl/constraint.rb | 110 ++++++++++++++++++++++++------------- lib/rdl/heuristics.rb | 61 +++++++++++--------- lib/rdl/typecheck.rb | 19 +++++-- lib/rdl/types/nominal.rb | 2 +- lib/rdl/types/type.rb | 3 +- lib/types/core/array.rb | 2 +- lib/types/core/dir.rb | 2 +- lib/types/core/integer.rb | 3 +- lib/types/core/kernel.rb | 1 + lib/types/core/pathname.rb | 1 + lib/types/core/rational.rb | 2 + lib/types/core/string.rb | 1 + lib/types/core/yaml.rb | 4 +- 14 files changed, 138 insertions(+), 82 deletions(-) diff --git a/lib/rdl/class_indexer.rb b/lib/rdl/class_indexer.rb index 91d964f1..c043bb4b 100644 --- a/lib/rdl/class_indexer.rb +++ b/lib/rdl/class_indexer.rb @@ -33,7 +33,6 @@ def add_method(method_name, line_num) def on_class(node) class_name = get_const_name(node.children[0])#node.children[0].children[1].to_s - if @current_class == "main" @current_class = class_name entered_class = class_name @@ -41,10 +40,9 @@ def on_class(node) @current_class << "::" + class_name entered_class = "::" + class_name end - node.children.each { |c| process(c) } - @current_class.sub!(entered_class, "") + @current_class.delete_suffix!(entered_class) reset_class if @current_class.empty? =begin @@ -58,7 +56,7 @@ def on_class(node) def on_module(node) module_name = get_const_name(node.children[0])#node.children[0].children[1].to_s - + if @current_class == "main" @current_class = module_name entered_class = module_name @@ -67,10 +65,9 @@ def on_module(node) @current_class << "::" + module_name entered_class = "::" + module_name end - node.children.each { |c| process(c) } - @current_class.sub!(entered_class, "") + @current_class.delete_suffix!(entered_class) reset_class if @current_class.empty? =begin if @current_class.include?("::") diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index d00c7ea9..876499a8 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,7 +1,8 @@ require 'csv' -$use_twin_network = false -$use_heuristics = true +$use_twin_network = true +$use_heuristics = false + class << RDL::Typecheck ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. @@ -13,6 +14,7 @@ module RDL::Typecheck @type_names_map = Hash.new { |h, k| h[k] = [] }#[] @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] + @failed_sol_cache = Hash.new { |h, k| h[k] = [] } def self.resolve_constraints RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." @@ -90,7 +92,7 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) else raise "Unexpected VarType category #{category}." end - if sol.is_a?(RDL::Type::UnionType) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) + if (sol.is_a?(RDL::Type::UnionType) && !(sol == RDL::Globals.types[:bool])) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) ## Try each rule. Return first non-nil result. ## If no non-nil results, return original solution. ## TODO: check constraints. @@ -103,31 +105,39 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) RDL::Logging.log :heuristic, :debug, "Trying rule `#{name}` for variable #{var}." typ = rule.call(var) if typ.is_a?(Array) && (name == :twin_network) - new_cons = {} - begin - typ.each { |t| - t = t.canonical - if add_sol_to_graph - var.add_and_propagate_upper_bound(t, nil, new_cons) - var.add_and_propagate_lower_bound(t, nil, new_cons) + typ.each { |t| + new_cons = {} + begin + t = t.canonical + next if @failed_sol_cache[var].include?(t) + if add_sol_to_graph + var.add_and_propagate_upper_bound(t, nil, new_cons) + var.add_and_propagate_lower_bound(t, nil, new_cons) + end + RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" + puts "Successfully applied twin network! Solution #{t} for #{var}".blue + @new_constraints = true if !new_cons.empty? + RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? + raise "2. got here with #{var} and #{t}" if t.to_s == "%bot" + @type_vars_map[t] = @type_vars_map[t] | [var] + return t + rescue RDL::Typecheck::StaticTypeError => e + if (var.meth == :get_rate) || (var.meth == :add_rate) + puts "Tried to apply solution #{t} to #{var}, but got error:" + puts e + end + @failed_sol_cache[var] << t + undo_constraints(new_cons) end - RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" - puts "Successfully applied twin network! Solution #{t} for #{var}".blue - @new_constraints = true if !new_cons.empty? - RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? - return t } - rescue RDL::Typecheck::StaticTypeError => e - undo_constraints(new_cons) - end elsif typ.is_a?(RDL::Type::Type) new_cons = {} begin typ = typ.canonical + next if @failed_sol_cache[var].include?(typ) if add_sol_to_graph - puts "Attempting to apply solution #{typ} for #{var}" if var.base_name == "section_id" var.add_and_propagate_upper_bound(typ, nil, new_cons) - var.add_and_propagate_lower_bound(typ, nil, new_cons) + var.add_and_propagate_lower_bound(typ, nil, new_cons) end =begin new_cons.each { |var, bounds| @@ -141,15 +151,14 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" @new_constraints = true if !new_cons.empty? RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? + raise "3.. got here with #{var} and #{typ} for #{name}" if typ.to_s == "%bot" + @type_vars_map[typ] = @type_vars_map[typ] | [var] return typ #sol = typ rescue RDL::Typecheck::StaticTypeError => e RDL::Logging.log :heuristic, :debug_error, "Attempted to apply heuristic rule #{name} solution #{typ} to var #{var}" RDL::Logging.log :heuristic, :trace, "... but got the following error: #{e}" - if var.base_name == "section_id" - puts "Attempted to apply heuristic rule #{name} solution #{typ} to var #{var}" - puts "... but got the following error: #{e}" - end + @failed_sol_cache[var] << typ undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is ensure @@ -171,8 +180,9 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) sol = var if sol == RDL::Globals.types[:bot] # just use var itself when result of solution extraction was %bot. return sol if sol.is_a?(RDL::Type::VarType) ## don't add var type as solution sol = sol.canonical - var.add_and_propagate_upper_bound(sol, nil, new_cons) - var.add_and_propagate_lower_bound(sol, nil, new_cons) unless sol.is_a?(RDL::Type::StructuralType) || (sol.is_a?(RDL::Type::IntersectionType) && sol.types.any? { |t| t.is_a?(RDL::Type::StructuralType) } ) + raise RDL::Typecheck::StaticTypeError if @failed_sol_cache.include?(sol) + var.add_and_propagate_upper_bound(sol, nil, new_cons) unless (sol == RDL::Globals.types[:nil]) + var.add_and_propagate_lower_bound(sol, nil, new_cons) unless sol.is_a?(RDL::Type::StructuralType) || (sol.is_a?(RDL::Type::IntersectionType) && sol.types.any? { |t| t.is_a?(RDL::Type::StructuralType) } )# || (sol == RDL::Globals.types[:object]) || (sol == RDL::Globals.types[:top]) =begin new_cons.each { |var, bounds| bounds.each { |u_or_l, t, _| @@ -193,15 +203,15 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) rescue RDL::Typecheck::StaticTypeError => e RDL::Logging.log :inference, :debug_error, "Attempted to apply solution #{sol} for var #{var}" RDL::Logging.log :inference, :trace, "... but got the following error: #{e}" - + @failed_sol_cache[var] << sol undo_constraints(new_cons) ## no new constraints in this case so we'll leave it as is sol = var end - if sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) || sol.is_a?(RDL::Type::TupleType) || sol.is_a?(RDL::Type::FiniteHashType) || sol.is_a?(RDL::Type::UnionType) + if (sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) || sol.is_a?(RDL::Type::TupleType) || sol.is_a?(RDL::Type::FiniteHashType) || sol.is_a?(RDL::Type::UnionType)) && !(sol == RDL::Globals.types[:object]) + raise "1. got here with #{var} and #{sol}" if sol.to_s == "%bot" name = var.base_name#(var.category == :ret) ? var.meth : var.name - #puts "About to add #{sol} => #{name}" @type_names_map[sol] = @type_names_map[sol] | [name.to_s] @type_vars_map[sol] = @type_vars_map[sol] | [var] end @@ -261,7 +271,13 @@ def self.extract_meth_sol(tmeth) } block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical else - block_sol = RDL::Type::MethodType.new(*extract_meth_sol(non_vartype_ubounds[0])) + if non_vartype_ubounds[0].is_a?(RDL::Type::NominalType) && (non_vartype_ubounds[0].to_s == "Proc") + block_sol = non_vartype_ubounds[0] + elsif !non_vartype_ubounds[0].is_a?(RDL::Type::MethodType) + block_sol = tmeth.block + else + block_sol = RDL::Type::MethodType.new(*extract_meth_sol(non_vartype_ubounds[0])) + end end tmeth.block.solution = block_sol @@ -288,6 +304,8 @@ def self.extract_meth_sol(tmeth) def self.compare_single_type(inf_type, orig_type) inf_type = inf_type.type if inf_type.optional? || inf_type.vararg? orig_type = orig_type.type if orig_type.optional? || orig_type.vararg? + inf_type = inf_type.canonical + orig_type = orig_type.canonical if inf_type.to_s == orig_type.to_s return "E" elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && abbreviated_class?(orig_type.to_s, inf_type.to_s) @@ -296,12 +314,18 @@ def self.compare_single_type(inf_type, orig_type) return "E" elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } return "E" + elsif inf_type.is_a?(RDL::Type::GenericType) && (orig_type == RDL::Globals.types[:object]) + return "E" + elsif [inf_type.to_s, orig_type.to_s].all? { |t| ["String", "Symbol", "(String or Symbol)", "(Symbol or String)"].include?(t) } + return "E" elsif inf_type.is_a?(RDL::Type::UnionType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.types.all? { |t| t.is_a?(RDL::Type::NominalType) && t.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } } return "E" elsif !inf_type.is_a?(RDL::Type::VarType) && (orig_type.is_a?(RDL::Type::TopType)) return "E" elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.params[0] == orig_type.params[0] && inf_type.array_type? && orig_type.array_type? return "E" + elsif inf_type.is_a?(RDL::Type::StructuralType) && orig_type.is_a?(RDL::Type::StructuralType) && (inf_type.methods.map { |m, _| m } == orig_type.methods.map { |m, _| m}) + return "E" elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.base.to_s == orig_type.base.to_s return "P" elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.base.to_s == orig_type.to_s @@ -344,6 +368,7 @@ def self.make_extraction_report(typ_sols) correct_types = 0 meth_types = 0 + ret_types = 0 arg_types = 0 var_types = 0 typ_sols.each_pair { |km, typ| @@ -374,10 +399,13 @@ def self.make_extraction_report(typ_sols) twin_csv << [klass, meth, "Arg", name, inf_arg_type.to_s, orig_arg_typ.to_s, comp, code] arg_types +=1 } - inf_ret_type = typ.solution.ret - comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) - compares[comp] += 1 - twin_csv << [klass, meth, "Ret", "", inf_ret_type.to_s, orig_typ.ret.to_s, comp, code] + unless (orig_typ.ret == RDL::Globals.types[:bot]) ## bot type is given to any returns for which we don't have a type + ret_types += 1 + inf_ret_type = typ.solution.ret + comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) + compares[comp] += 1 + twin_csv << [klass, meth, "Ret", "", inf_ret_type.to_s, orig_typ.ret.to_s, comp, code] + end else comp = typ.solution.nil? ? "N" : compare_single_type(typ.solution, orig_typ) compares[comp] += 1 @@ -416,14 +444,16 @@ def self.make_extraction_report(typ_sols) RDL::Logging.log :inference, :info, "Total not correct but got type for (T): #{compares["T"]}" twin_csv << ["Total # N:", compares["N"]] RDL::Logging.log :inference, :info, "Total no type for (N): #{compares["N"]}" - twin_csv << ["Total # method types:", meth_types] - RDL::Logging.log :inference, :info, "Total # method types: #{meth_types}" + #twin_csv << ["Total # method types:", meth_types] + #RDL::Logging.log :inference, :info, "Total # method types: #{meth_types}" + twin_csv << ["Total # return types:", ret_types] + RDL::Logging.log :inference, :info, "Total # return types: #{ret_types}" twin_csv << ["Total # arg types:", arg_types] RDL::Logging.log :inference, :info, "Total # argument types: #{arg_types}" twin_csv << ["Total # var types:", var_types] RDL::Logging.log :inference, :info, "Total # variable types: #{var_types}" twin_csv << ["Total # individual types:", var_types + meth_types + arg_types] - RDL::Logging.log :inference, :info, "Total # individual types: #{meth_types + arg_types + var_types}" + RDL::Logging.log :inference, :info, "Total # individual types: #{ret_types + arg_types + var_types}" rescue => e RDL::Logging.log :inference, :error, "Report Generation Error" RDL::Logging.log :inference, :debug_error, "... got #{e}" @@ -436,6 +466,11 @@ def self.make_extraction_report(typ_sols) def self.extract_solutions() ## Go through once to come up with solution for all var types. RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" + if $use_twin_network + uri = URI "http://127.0.0.1:5000/" + $http = Net::HTTP.new(uri.hostname, uri.port) + #$http.start + end @counter = 0; used_twin = false typ_sols = {} @@ -522,6 +557,7 @@ def self.extract_solutions() rescue => e puts "RECEIVED ERROR #{e} from #{e.backtrace}" ensure + #$http.finish puts "MAKING EXTRACTION REPORT" return make_extraction_report(typ_sols) end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 6cfbcd43..23d4b057 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -48,12 +48,11 @@ def self.struct_to_nominal(var_type) :debug, "Struct_to_nominal heuristsic for %s in method %s:%s yields %d matching classes with methods: %s" % [var_type.name, var_type.cls, var_type.meth, matching_classes.size, meth_names*","] - return if matching_classes.size > 10 ## in this case, just keep the struct types + return if (matching_classes.size == 0) || matching_classes.size > 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } union = RDL::Type::UnionType.new(*nom_sing_types).canonical #struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds - return union ## used to add and propagate here. Now that this is a heuristic, this should be done after running the rule. #var_type.add_and_propagate_upper_bound(union, nil) @@ -93,9 +92,7 @@ def self.twin_network_guess(var_type) ## Below was query approach before implementing caching. params = { words: [name1] + names } uri.query = URI.encode_www_form(params) - #puts "SENDING QUERY OF SIZE #{names.size + 1}: #{names}" res = Net::HTTP.get_response(uri) - #puts "RECEIVED: #{res}" puts "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" sim_score = res.body.to_f @@ -178,22 +175,28 @@ def self.twin_network_constraints(pairs_enum) def self.send_query(params) uri = URI "http://127.0.0.1:5000/" uri.query = URI.encode_www_form(params) - res = Net::HTTP.get_response(uri) + #res = Net::HTTP.get_response(uri) + puts "About to send query #{params}" if params[:action] == "get_similarity" + start = Time.now + #res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https', :read_timeout => 9000) { |http| res = http.request_get(uri) } + res = $http.request_get(uri) + endt = Time.now + puts "Total time taken: #{endt-start}" raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" return res end def self.vectorize_var(var_type) return true if @vectorized_vars[var_type.object_id] ## already vectorized and cached server side - puts "About to vectorize var #{var_type}" + #puts "About to vectorize var #{var_type}" if (var_type.category == :arg) ast = RDL::Typecheck.get_ast(var_type.cls, var_type.meth) return nil if ast.nil? locs = get_var_loc(ast, var_type) source = ast.loc.expression.source - puts "Querying for var #{var_type.base_name}" - puts "Sanity check: " - locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } + #puts "Querying for var #{var_type.base_name}" + #puts "Sanity check: " + #locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "arg" } send_query(params) elsif (var_type.category == :var) @@ -201,9 +204,9 @@ def self.vectorize_var(var_type) ast = RDL::Typecheck.get_ast(klass, meth) locs = get_var_loc(ast, var_type) source = ast.loc.expression.source - puts "Querying for var #{var_type.name} in method #{klass}##{meth}" - puts "Sanity check: " - locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } + #puts "Querying for var #{var_type.name} in method #{klass}##{meth}" + #puts "Sanity check: " + #locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "var", average: false } send_query(params) } @@ -215,10 +218,9 @@ def self.vectorize_var(var_type) locs = [ast.loc.name.begin_pos - begin_pos, ast.loc.name.end_pos - begin_pos-1] ## for now, let's just try using method name locs = locs + get_ret_sites(ast) source = ast.loc.expression.source - - puts "Querying for return #{var_type}" - puts "Sanity check: " - locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } + #puts "Querying for return #{var_type}" + #puts "Sanity check: " + #locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "ret" } send_query(params) else @@ -228,6 +230,11 @@ def self.vectorize_var(var_type) return true end + def self.vectorize_vars(vars) + vars = vars.keep_if { |v| !@vectorized_vars[v.object_id] && [:arg, :var, :ret].include?(v.category) } + return true if vars.empty? + end + ## [+ var_kind +] is either :arg, :ret, or :var. def self.visualize_bert(var_kind) var_id_list = [] @@ -288,10 +295,11 @@ def self.bert_model_guess(var_type) raise "Got here for #{t}" if vars.empty? vars.each { |var2| next if (var_type == var2) || !((var2.category == :arg) || (var2.category == :var) || (var2.category == :ret)) #!(var_type.category == var2.category) + #next if !(var_type.category == var2.category) next if ((var_type.category == :ret) || (var2.category == :ret)) && !(var_type.category == var2.category) # only compare rets with rets count += 1 if @bert_cache[var_type][var2] - #puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red + puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red #sum += @bert_cache[var_type][var2] sim_score = @bert_cache[var_type][var2] else @@ -300,19 +308,19 @@ def self.bert_model_guess(var_type) puts "Could not find AST for #{var2}".yellow next end - puts "About to ask for similarity of #{var_type} and #{var2}" - res = send_query({ action: "get_similarity", id1: var_type.object_id, id2: var2.object_id }) + puts "About to ask for similarity of #{var_type} and #{var2}" + res = send_query({ action: "get_similarity", id1: var_type.object_id, id2: var2.object_id, kind1: var_type.category.to_s, kind2: var2.category.to_s }) #sum += res.body.to_f sim_score = res.body.to_f @bert_cache[var_type][var2] = res.body.to_f @bert_cache[var2][var_type] = res.body.to_f end message = "Received similarity score of #{sim_score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" - if sim_score > 0.9 - puts message.green + if sim_score > 0.8 + puts message.green sols[sim_score] = t else - puts message.red + puts message.red #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red end } @@ -329,9 +337,8 @@ def self.bert_model_guess(var_type) end =end } - ## return list of types that are sorted from highest similarity score to lowest - return sols.sort.map { |sim_score, t| t }.reverse + return sols.sort.map { |sim_score, t| t }.reverse.uniq else @@ -357,8 +364,10 @@ def self.get_ret_sites(ast) ret_sites.each { |exp, _| if (exp.type == :send) || (exp.type == :csend) - locs << exp.loc.selector.begin_pos - begin_pos - locs << exp.loc.selector.end_pos - begin_pos + if exp.loc.selector + locs << exp.loc.selector.begin_pos - begin_pos + locs << exp.loc.selector.end_pos - begin_pos + end else locs << (exp.loc.expression.begin_pos - begin_pos) locs << (exp.loc.expression.end_pos - begin_pos - 1) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 7ab38c16..6506931a 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -215,7 +215,6 @@ def self.is_RDL(node) def self.get_ast(klass, meth) file, line = RDL::Globals.info.get(klass, meth, :source_location) - return nil if file.nil? # return nil if RDL::Config.instance.continue_on_errors # @@ -575,7 +574,7 @@ def self.get_super_owner(slf, m) end def self.get_super_owner_from_class(cls, m) - raise Exception, "cls #{cls} is not a Class" if cls.class != Class + raise Exception, "cls #{cls} is not a Class" if (cls.class != Class) cls.superclass.instance_method(m).owner end @@ -998,7 +997,7 @@ def self._tc(scope, env, e) raise RuntimeError, "impossible to pass block arg and literal block" if scope[:block] envi, ti = tc(sscope, envi, ei.children[0]) # convert using to_proc if necessary - if (e.children[1] == :map) || e.children[1] == :sum || e.children[1] == :keep_if + if (e.children[1] == :map) || e.children[1] == :sum || e.children[1] == :keep_if || e.children[1] == :all? ## block_pass calling map is a weird case: ## it takes a symbol representing method being called, ## where receiver is Array elements. @@ -1415,9 +1414,17 @@ def self._tc(scope, env, e) # where return site expression maps to a boolean indicating whether the expression was returned # with a `return` expression (true) or not (false). def self.find_ret_sites(e) + return {} if e.nil? case e.type - when :nil, :true, :false, :str, :string, :complex, :rational, :int, :float, :sym, :dstr, :xstr, :dsym, :regexp, :array, :hash, :irange, :erange, :self, :lvar, :ivar, :cvar, :gvar, :lvasgn, :ivasgn, :cvasgn, :gvasgn, :masgn, :op_asgn, :and_asgn, :or_asgn, :match_with_lvasgn, :nth_ref, :back_ref, :const, :defined?, :send, :csend, :yield, :and, :or, :break, :redo, :next, :retry, :super, :zsuper + when :nil, :true, :false, :str, :string, :complex, :rational, :int, :float, :sym, :dstr, :xstr, :dsym, :regexp, :array, :hash, :irange, :erange, :self, :lvar, :ivar, :cvar, :gvar, :lvasgn, :ivasgn, :cvasgn, :gvasgn, :masgn, :op_asgn, :and_asgn, :or_asgn, :match_with_lvasgn, :nth_ref, :back_ref, :const, :defined?, :yield, :and, :or, :break, :redo, :next, :retry, :super, :zsuper return {e => false} + when :send, :csend + if is_RDL(e.children[0]) && (e.children[1] == :type_cast) + ## for type casts, look at the expression within the cast + return find_ret_sites(e.children[2]) + else + return { e => false } + end when :block ## TODO: check on this return {e.children[0] => false } when :if @@ -1467,7 +1474,8 @@ def self.find_ret_sites(e) rets = rets.merge(find_ret_sites(e.children[-1])) return rets else - error :unsupported_expression, [e.type, e], e + return { e => false } + #error :unsupported_expression, [e.type, e], e end end @@ -1829,6 +1837,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, # Module mixin handle # Here a module method is calling a non-existent method; check for it in all mixees # TODO: Handle :extend + return [env, [RDL::Globals.types[:bot]]] unless RDL::Globals.module_mixees[klass] nts = RDL::Globals.module_mixees[klass].map { |k, kind| if kind == :include then RDL::Type::NominalType.new(k) end } return [env, [RDL::Globals.types[:bot]]] if nts.empty? # if module not mixed in, this call can't happen; so %bot ut = RDL::Type::UnionType.new(*nts) diff --git a/lib/rdl/types/nominal.rb b/lib/rdl/types/nominal.rb index 51f5943e..6cc45d39 100644 --- a/lib/rdl/types/nominal.rb +++ b/lib/rdl/types/nominal.rb @@ -10,7 +10,7 @@ class << self def self.new(name) name = name.to_s - name = "Integer" if RDL::Config.instance.number_mode && ["Float", "Rational", "Complex", "BigDecimal", "Numeric"].include?(name) + name = "Integer" if RDL::Config.instance.number_mode && ["Float", "Rational", "Complex", "BigDecimal", "BigDecimal::ROUND_MODE", "Numeric"].include?(name) t = @@cache[name] return t if t t = self.__new__ name diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index 6c534095..e71a6fab 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -58,7 +58,7 @@ def kind_of_var_input? end def array_type? - is_a?(TupleType) || (is_a?(GenericType) && ((base == RDL::Globals.types[:array]) || ((defined? ActiveRecord_Relation) && (base.klass == ActiveRecord_Relation)))) || (self == RDL::Globals.types[:array]) || (is_a?(UnionType) && types.all? { |t| t.array_type? }) + is_a?(TupleType) || (is_a?(GenericType) && ((base == RDL::Globals.types[:array]) || ((defined? ActiveRecord_Relation) && (base.klass == ActiveRecord_Relation)))) || (is_a?(NominalType) && (self == RDL::Globals.types[:array])) || (is_a?(UnionType) && types.all? { |t| t.array_type? }) end def hash_type? @@ -86,7 +86,6 @@ def vararg?; return false; end # if inst is non-nil and ileft, returns inst(self) <= other, possibly mutating inst to make this true # if inst is non-nil and !ileft, returns self <= inst(other), again possibly mutating inst def self.leq(left, right, inst=nil, ileft=true, deferred_constraints=nil, no_constraint: false, ast: nil, propagate: false, new_cons: {}, removed_choices: {}) - #propagate = false left = inst[left.name] if inst && ileft && left.is_a?(VarType) && !left.to_infer && inst[left.name] right = inst[right.name] if inst && !ileft && right.is_a?(VarType) && !right.to_infer && inst[right.name] left = left.type if left.is_a?(DependentArgType) || left.is_a?(AnnotatedArgType) diff --git a/lib/types/core/array.rb b/lib/types/core/array.rb index b2dc3f5f..4ccef451 100644 --- a/lib/types/core/array.rb +++ b/lib/types/core/array.rb @@ -22,7 +22,7 @@ def Array.output_type(trec, targs, meth_name, default1, default2=default1, use_s vals = RDL.type_cast((if use_sing_val then targs.map { |t| RDL.type_cast(t, "RDL::Type::SingletonType").val } else targs end), "Array<%any>", force: true) begin res = RDL.type_cast(trec.params.send(meth_name, *vals), "Object", force: true) - rescue ArgumentError => e + rescue => e#ArgumentError => e puts "GOT ERROR #{e} FOR METHOD #{meth_name} CALLED ON TREC #{trec} AND ARGS #{targs}" return RDL::Globals.types[:bot] end diff --git a/lib/types/core/dir.rb b/lib/types/core/dir.rb index b4911b2e..94ed5339 100644 --- a/lib/types/core/dir.rb +++ b/lib/types/core/dir.rb @@ -3,7 +3,7 @@ RDL.rdl_alias :Dir, :'self.[]', :'self.glob' RDL.type :Dir, 'self.chdir', '(?(String or Pathname)) -> 0' -RDL.type :Dir, 'self.chdir', '(?(String or Pathname)) { (String) -> u } -> u' +RDL.type :Dir, 'self.chdir', '(?(String or Pathname)) { (?String) -> u } -> u' RDL.type :Dir, 'self.chroot', '(String) -> 0' RDL.type :Dir, 'self.delete', '(String) -> 0' RDL.type :Dir, 'self.entries', '(String, ?Encoding) -> Array' diff --git a/lib/types/core/integer.rb b/lib/types/core/integer.rb index f1ebeb43..e8bbf90e 100644 --- a/lib/types/core/integer.rb +++ b/lib/types/core/integer.rb @@ -195,7 +195,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :remainder, '(Rational x {{ x!=0 }}) -> Rational r {{ r>=0 }}' ## TODO RDL.type :Integer, :remainder, '(BigDecimal x {{ x!=0 }}) -> ``sing_or_type(trec, targs, :gcd, "BigDecimal")``' RDL.type :Integer, :round, '() -> ``sing_or_type(trec, targs, :round, "Integer")``' -RDL.type :Integer, :round, '(%numeric) -> ``sing_or_type(trec, targs, :round, "%numeric")``' +RDL.type :Integer, :round, '(%numeric, ?%numeric) -> ``sing_or_type(trec, targs, :round, "%numeric")``' RDL.pre(:Integer, :round) { |x| x!=0 && if x.is_a?(Complex) then x.imaginary==0 && (if x.real.is_a?(Float)||x.real.is_a?(BigDecimal) then !x.real.infinite? && !x.real.nan? else true end) elsif x.is_a?(Float) then x!=Float::INFINITY && !x.nan? elsif x.is_a?(BigDecimal) then x!=BigDecimal::INFINITY && !x.nan? else true end} #Also, x must be in range [-2**31, 2**31]. RDL.type :Integer, :size, '() -> ``sing_or_type(trec, targs, :size, "Integer")``' RDL.type :Integer, :succ, '() -> ``sing_or_type(trec, targs, :succ, "Integer")``' @@ -203,6 +203,7 @@ def Numeric.sing_or_type(trec, targs, meth, type) RDL.type :Integer, :times, '() { () -> %any } -> Integer' RDL.type :Integer, :times, '() -> Enumerator' RDL.type :Integer, :to_c, '() -> Complex r {{ r.imaginary==0 }}' +RDL.type :Integer, :to_d, '(?Integer) -> BigDecimal' RDL.type :Integer, :to_f, '() -> ``sing_or_type(trec, targs, :to_f, "Float")``' RDL.type :Integer, :to_i, '() -> self' RDL.type :Integer, :to_int, '() -> self' diff --git a/lib/types/core/kernel.rb b/lib/types/core/kernel.rb index bf90d2f5..45bf3d07 100644 --- a/lib/types/core/kernel.rb +++ b/lib/types/core/kernel.rb @@ -64,6 +64,7 @@ RDL.type :Kernel, :proc, '() {(*%any) -> %any} -> Proc' # TODO more precise RDL.type :Kernel, :public_send, '(Symbol or String, *%any args) -> %any', wrap: false RDL.type :Kernel, 'self.putc', '(Integer) -> Integer' +RDL.type :Kernel, 'self.putc', '(String) -> String' RDL.type :Kernel, 'self.puts', '(*[to_s : () -> String]) -> nil' RDL.type :Kernel, 'self.raise', '() -> %bot' RDL.type :Kernel, 'raise', '() -> %bot' diff --git a/lib/types/core/pathname.rb b/lib/types/core/pathname.rb index 2906fe4d..393434a3 100644 --- a/lib/types/core/pathname.rb +++ b/lib/types/core/pathname.rb @@ -2,6 +2,7 @@ RDL.type :Pathname, 'self.getwd', '() -> Pathname' RDL.type :Pathname, 'self.glob', '(String p1, ?String p2) -> Array' +RDL.type :Pathname, 'self.glob', '(String p1, ?String p2) { (String) -> %any } -> Array' RDL.rdl_alias :Pathname, 'self.pwd', 'self.getwd' RDL.type :Pathname, :+, '(String or Pathname other) -> Pathname' RDL.rdl_alias :Pathname, :/, :+ diff --git a/lib/types/core/rational.rb b/lib/types/core/rational.rb index c9dc859f..e0c22606 100644 --- a/lib/types/core/rational.rb +++ b/lib/types/core/rational.rb @@ -171,6 +171,8 @@ RDL.type :Rational, :round, '(Integer) -> %numeric' +RDL.type :Rational, :to_d, "(Integer) -> BigDecimal" + RDL.type :Rational, :to_f, '() -> Float' RDL.pre(:Rational, :to_f) { self<=Float::MAX} diff --git a/lib/types/core/string.rb b/lib/types/core/string.rb index fa26f47a..47922dd5 100644 --- a/lib/types/core/string.rb +++ b/lib/types/core/string.rb @@ -75,6 +75,7 @@ def String.string_promote!(trec) RDL.type :String, 'self.try_convert', '(Object obj) -> String or nil new_string' RDL.type :String, :%, '(``targs[0]``) -> ``output_type(trec, targs, :%, "String")``' RDL.type :String, :*, '(Numeric) -> ``output_type(trec, targs, :*, "String")``' +RDL.type :String, :-@, "() -> ``output_type(trec, targs, :-@, 'String')``" def String.plus_output(trec, targs) if trec.is_a?(RDL::Type::PreciseStringType) && targs[0].is_a?(RDL::Type::PreciseStringType) diff --git a/lib/types/core/yaml.rb b/lib/types/core/yaml.rb index 3dbf364a..aa199a97 100644 --- a/lib/types/core/yaml.rb +++ b/lib/types/core/yaml.rb @@ -1,8 +1,8 @@ RDL.nowrap :YAML RDL.nowrap :Psych -RDL.type :YAML, 'self.load_file', '(String) -> Array' -RDL.type :YAML, 'self.load', '(String) -> Array' +RDL.type :YAML, 'self.load_file', '(String) -> Array or Hash' +RDL.type :YAML, 'self.load', '(String) -> Array or Hash' RDL.type :Psych, 'self.load_file', '(String) -> Array' RDL.type :Psych, 'self.load', '(String) -> Array' From 1e9fe8fc73f1e64ca48570eab04452dc247140c5 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Tue, 6 Apr 2021 18:11:04 -0400 Subject: [PATCH 099/124] latest updates --- lib/rdl/#constraint.rb# | 591 ++++++++++++++++++++++++++++++++++ lib/rdl/boot.rb | 6 + lib/rdl/constraint.rb | 75 +++-- lib/rdl/eval_deepsim.rb | 97 ++++++ lib/rdl/eval_deepsim.rb~ | 3 + lib/rdl/heuristics.rb | 256 +++++++++++++-- lib/rdl/reporting/sorbet.rb | 18 +- lib/rdl/typecheck.rb | 17 +- lib/rdl/types/bot.rb | 4 + lib/rdl/types/dynamic.rb | 4 + lib/rdl/types/generic.rb | 2 +- lib/rdl/types/intersection.rb | 4 + lib/rdl/types/nominal.rb | 4 + lib/rdl/types/singleton.rb | 4 + lib/rdl/types/structural.rb | 4 + lib/rdl/types/top.rb | 4 + lib/rdl/types/type.rb | 4 + lib/rdl/types/var.rb | 17 +- lib/rdl/types/vararg.rb | 4 + lib/rdl/util.rb | 22 ++ lib/rdl/wrap.rb | 97 ++++-- lib/types/core/object.rb | 3 +- rdl.gemspec | 2 +- 23 files changed, 1127 insertions(+), 115 deletions(-) create mode 100644 lib/rdl/#constraint.rb# create mode 100644 lib/rdl/eval_deepsim.rb create mode 100644 lib/rdl/eval_deepsim.rb~ diff --git a/lib/rdl/#constraint.rb# b/lib/rdl/#constraint.rb# new file mode 100644 index 00000000..85222eee --- /dev/null +++ b/lib/rdl/#constraint.rb# @@ -0,0 +1,591 @@ +require 'csv' + +$use_twin_network = false +$use_heuristics = true + + +class << RDL::Typecheck + ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. + attr_accessor :type_names_map + attr_accessor :type_vars_map + attr_accessor :failed_twin_sol_cache +end + +module RDL::Typecheck + + @type_names_map = Hash.new { |h, k| h[k] = [] }#[] + @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] + @failed_sol_cache = Hash.new { |h, k| h[k] = [] } + @failed_twin_sol_cache = Hash.new { |h, k| h[k] = [] } + + def self.empty_cache! + @type_names_map = Hash.new { |h, k| h[k] = [] }#[] + @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] + @failed_sol_cache = Hash.new { |h, k| h[k] = [] } + @failed_twin_sol_cache = Hash.new { |h, k| h[k] = [] } + end + + def self.resolve_constraints + RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." + RDL::Globals.constrained_types.each { |klass, name| + RDL::Logging.log :inference, :debug, "Resolving constraints from #{RDL::Util.pp_klass_method(klass, name)}" + typ = RDL::Globals.info.get(klass, name, :type) + ## If typ is an Array, then it's an array of method types + ## but for inference, we only use a single method type. + ## Otherwise, it's a single VarType for an instance/class var. + if typ.is_a?(Array) + var_types = name == :initialize ? typ[0].args + [typ[0].block] : typ[0].args + [typ[0].block, typ[0].ret] + else + var_types = [typ] + end + + var_types.each { |var_type| + begin + if var_type.var_type? || var_type.optional_var_type? || var_type.vararg_var_type? + var_type = var_type.type if var_type.optional_var_type? || var_type.vararg_var_type? + var_type.lbounds.each { |lower_t, ast| + RDL::Logging.log :typecheck, :trace, "#{lower_t} <= #{var_type}" + var_type.add_and_propagate_lower_bound(lower_t, ast) + } + var_type.ubounds.each { |upper_t, ast| + var_type.add_and_propagate_upper_bound(upper_t, ast) + } + elsif var_type.fht_var_type? + var_type.elts.values.each { |v| + vt = v.optional_var_type? || v.vararg_var_type? ? v.type : v + vt.lbounds.each { |lower_t, ast| + vt.add_and_propagate_lower_bound(lower_t, ast) + } + vt.ubounds.each { |upper_t, ast| + vt.add_and_propagate_upper_bound(upper_t, ast) + } + } + else + raise "Got unexpected type #{var_type}." + end + rescue => e + raise e unless RDL::Config.instance.continue_on_errors + + RDL::Logging.log :inference, :debug_error, "Caught error when resolving constraints for #{var_type}; skipping..." + end + } + } + end + + def self.extract_var_sol(var, category, add_sol_to_graph = true) + #raise "Expected VarType, got #{var}." unless var.is_a?(RDL::Type::VarType) + return var.canonical unless var.is_a?(RDL::Type::VarType) + if category == :arg + non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } + sol = non_vartype_ubounds.size == 1 ? non_vartype_ubounds[0] : RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::IntersectionType) ## could be, e.g., nominal type if only one type used to create intersection. + #return sol + elsif category == :ret + non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } + sol = RDL::Type::UnionType.new(*non_vartype_lbounds) + sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. + #return sol + elsif category == :var + if var.lbounds.empty? || (var.lbounds.size == 1 && var.lbounds[0][0] == RDL::Globals.types[:bot]) + ## use upper bounds in this case. + non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } + sol = RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + #return sol + else + ## use lower bounds + non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } + sol = RDL::Type::UnionType.new(*non_vartype_lbounds) + sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. + #return sol#RDL::Type::UnionType.new(*non_vartype_lbounds).canonical + end + else + raise "Unexpected VarType category #{category}." + end + if (sol.is_a?(RDL::Type::UnionType) && !(sol == RDL::Globals.types[:bool])) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) + ## Try each rule. Return first non-nil result. + ## If no non-nil results, return original solution. + ## TODO: check constraints. + heuristics_start_time = Time.now + RDL::Logging.log_header :heuristic, :debug, "Beginning Heuristics..." + + RDL::Heuristic.rules.each { |name, rule| + next if (@counter == 1) && (name == :twin_network) + start_time = Time.now + RDL::Logging.log :heuristic, :debug, "Trying rule `#{name}` for variable #{var}." + typ = rule.call(var) + if typ.is_a?(Array) && (name == :twin_network) + count = 0 + typ.each { |t| + new_cons = {} + count += 1 + begin + t = t.canonical + next if @failed_sol_cache[var].include?(t) + if add_sol_to_graph + var.add_and_propagate_upper_bound(t, nil, new_cons) + var.add_and_propagate_lower_bound(t, nil, new_cons) + end + RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" + puts "Successfully applied twin network! Solution #{t} for #{var}".blue unless $collecting_time_data + @new_constraints = true if !new_cons.empty? + RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? + raise "2. got here with #{var} and #{t}" if t.to_s == "%bot" + @type_vars_map[t] = @type_vars_map[t] | [var] + var.solution_source = "Twin" if (var.solution != t) + return t + rescue RDL::Typecheck::StaticTypeError => e + @failed_sol_cache[var] << t + @failed_twin_sol_cache[var] << t + undo_constraints(new_cons) + end + } + elsif typ.is_a?(RDL::Type::Type) + new_cons = {} + begin + typ = typ.canonical + next if @failed_sol_cache[var].include?(typ) + if add_sol_to_graph + var.add_and_propagate_upper_bound(typ, nil, new_cons) + var.add_and_propagate_lower_bound(typ, nil, new_cons) + end +=begin + new_cons.each { |var, bounds| + bounds.each { |u_or_l, t, _| + puts "1. Added #{u_or_l} bound constraint #{t} of kind #{t.class} to variable #{var}" + puts "It has upper bounds: " + var.ubounds.each { |t, _| puts t } + } + } +=end + RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" + @new_constraints = true if !new_cons.empty? + RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? + raise "3.. got here with #{var} and #{typ} for #{name}" if typ.to_s == "%bot" + @type_vars_map[typ] = @type_vars_map[typ] | [var] + var.solution_source = "Heur: #{name}" if (var.solution != typ) + return typ + #sol = typ + rescue RDL::Typecheck::StaticTypeError => e + RDL::Logging.log :heuristic, :debug_error, "Attempted to apply heuristic rule #{name} solution #{typ} to var #{var}" + RDL::Logging.log :heuristic, :trace, "... but got the following error: #{e}" + @failed_sol_cache[var] << typ + undo_constraints(new_cons) + ## no new constraints in this case so we'll leave it as is + ensure + total_time = Time.now - start_time + RDL::Logging.log :hueristic, :debug, "Heuristic #{name} took #{total_time} to evaluate" + end + else + raise "Unexpected return value #{typ} from heuristic rule #{name}." unless typ.nil? + end + } + + heuristics_total_time = Time.now - heuristics_start_time + RDL::Logging.log_header :heuristic, :debug, "Evaluated heuristics in #{heuristics_total_time}" + end + ## out here, none of the heuristics applied. + ## Try to use `sol` as solution -- there is a chance it will + begin + new_cons = {} + sol = var if sol == RDL::Globals.types[:bot] # just use var itself when result of solution extraction was %bot. + return sol if sol.is_a?(RDL::Type::VarType) ## don't add var type as solution + sol = sol.canonical + raise RDL::Typecheck::StaticTypeError if @failed_sol_cache.include?(sol) + var.add_and_propagate_upper_bound(sol, nil, new_cons) unless (sol == RDL::Globals.types[:nil]) + var.add_and_propagate_lower_bound(sol, nil, new_cons) unless sol.is_a?(RDL::Type::StructuralType) || (sol.is_a?(RDL::Type::IntersectionType) && sol.types.any? { |t| t.is_a?(RDL::Type::StructuralType) } )# || (sol == RDL::Globals.types[:object]) || (sol == RDL::Globals.types[:top]) +=begin + new_cons.each { |var, bounds| + bounds.each { |u_or_l, t, _| + puts "2. Added #{u_or_l} bound constraint #{t} to variable #{var}" + } + } +=end + @new_constraints = true if !new_cons.empty? + RDL::Logging.log :inference, :trace, "New Constraints branch B" if !new_cons.empty? + #raise "Adding Solution Array or Number to variable #{var}".red if (sol.to_s == "(Array or Number)") && var.to_s != "{ { DashboardSection# var: @row }#[] call_ret: ret }" + if sol.is_a?(RDL::Type::GenericType) + new_params = sol.params.map { |p| if p.is_a?(RDL::Type::VarType) && (!p.to_infer || (p == var)) then p else extract_var_sol(p, category) end } + sol = RDL::Type::GenericType.new(sol.base, *new_params) + elsif sol.is_a?(RDL::Type::TupleType) + new_params = sol.params.map { |t| extract_var_sol(t, category) } + sol = RDL::Type::TupleType.new(*new_params) + end + rescue RDL::Typecheck::StaticTypeError => e + RDL::Logging.log :inference, :debug_error, "Attempted to apply solution #{sol} for var #{var}" + RDL::Logging.log :inference, :trace, "... but got the following error: #{e}" + @failed_sol_cache[var] << sol + undo_constraints(new_cons) + ## no new constraints in this case so we'll leave it as is + sol = var + end + if (sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) || sol.is_a?(RDL::Type::TupleType) || sol.is_a?(RDL::Type::FiniteHashType) || (sol == RDL::Globals.types[:bool])) && !(sol == RDL::Globals.types[:object]) + raise "1. got here with #{var} and #{sol}" if sol.to_s == "%bot" + name = var.base_name#(var.category == :ret) ? var.meth : var.name + @type_names_map[sol] = @type_names_map[sol] | [name.to_s] + @type_vars_map[sol] = @type_vars_map[sol] | [var] + end + var.solution_source = "Constraints" if (var.solution != sol) + return sol + end + + # [+ cons +] is Hash of constraints to be undone. + def self.undo_constraints(cons) + cons.each_key { |var_type| + cons[var_type].each { |upper_or_lower, bound_t, ast| + if upper_or_lower == :upper + var_type.ubounds.delete([bound_t, ast]) + elsif upper_or_lower == :lower + var_type.lbounds.delete([bound_t, ast]) + end + } + } + end + + def self.extract_meth_sol(tmeth) + raise "Expected MethodType, got #{tmeth}." unless tmeth.is_a?(RDL::Type::MethodType) + ## ARG SOLUTIONS + arg_sols = tmeth.args.map { |a| + if a.optional_var_type? + soln = RDL::Type::OptionalType.new(extract_var_sol(a.type, :arg)) + elsif a.fht_var_type? + hash_sol = a.elts.transform_values { |v| + if v.is_a?(RDL::Type::OptionalType) + RDL::Type::OptionalType.new(extract_var_sol(v.type, :arg)) + else + extract_var_sol(v, :arg) + end + } + soln = RDL::Type::FiniteHashType.new(hash_sol, nil) + else + soln = extract_var_sol(a, :arg) + end + + a.solution = soln + soln + } + + ## BLOCK SOLUTION + if tmeth.block && !tmeth.block.ubounds.empty? + non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } + non_vartype_ubounds.reject! { |t| t.is_a?(RDL::Type::StructuralType) }#&& (t.methods.size == 1) && (t.methods.has_key?(:to_proc) || t.methods.has_key?(:call)) } + if non_vartype_ubounds.size == 0 + block_sol = tmeth.block + elsif non_vartype_ubounds.size > 1 + block_sols = [] + inter = RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical + typs = inter.is_a?(RDL::Type::IntersectionType) ? inter.types : [inter] + typs.each { |m| + #raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) + block_sols << (m.is_a?(RDL::Type::MethodType) ? RDL::Type::MethodType.new(*extract_meth_sol(m)) : m) + } + block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical + else + if non_vartype_ubounds[0].is_a?(RDL::Type::NominalType) && (non_vartype_ubounds[0].to_s == "Proc") + block_sol = non_vartype_ubounds[0] + elsif !non_vartype_ubounds[0].is_a?(RDL::Type::MethodType) + block_sol = tmeth.block + else + block_sol = RDL::Type::MethodType.new(*extract_meth_sol(non_vartype_ubounds[0])) + end + end + + tmeth.block.solution = block_sol + else + block_sol = nil + end + + ## RET SOLUTION + if tmeth.ret.to_s == "self" + ret_sol = tmeth.ret + else + ret_sol = tmeth.ret.is_a?(RDL::Type::VarType) ? extract_var_sol(tmeth.ret, :ret) : tmeth.ret + end + + tmeth.ret.solution = ret_sol + + return [arg_sols, block_sol, ret_sol] + end + + # Compares a single inferred type to original type. + # Returns "E" (exact match), "P" (match up to parameter), "T" (no match but got a type), or "N" (no type). + # [+ inf_type +] is the inferred type + # [+ orig_type +] is the original, gold standard type + def self.compare_single_type(inf_type, orig_type) + inf_type = inf_type.type if inf_type.optional? || inf_type.vararg? + orig_type = orig_type.type if orig_type.optional? || orig_type.vararg? + inf_type = inf_type.canonical + orig_type = orig_type.canonical + if inf_type.to_s == orig_type.to_s + return "E" + elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && abbreviated_class?(orig_type.to_s, inf_type.to_s) + ## needed for diferring scopes, e.g. TZInfo::Timestamp & Timestamp + return "E" + elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } + return "E" + elsif inf_type.is_a?(RDL::Type::GenericType) && (orig_type == RDL::Globals.types[:object]) + return "E" + elsif [inf_type.to_s, orig_type.to_s].all? { |t| ["String", "Symbol", "(String or Symbol)", "(Symbol or String)"].include?(t) } + return "E" + elsif inf_type.is_a?(RDL::Type::UnionType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.types.all? { |t| t.is_a?(RDL::Type::NominalType) && t.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } } + return "E" + elsif !inf_type.is_a?(RDL::Type::VarType) && (orig_type.is_a?(RDL::Type::TopType)) + return "E" + elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.params[0] == orig_type.params[0] && inf_type.array_type? && orig_type.array_type? + return "E" + elsif inf_type.is_a?(RDL::Type::StructuralType) && orig_type.is_a?(RDL::Type::StructuralType) && (inf_type.methods.map { |m, _| m } == orig_type.methods.map { |m, _| m}) + return "E" + elsif inf_type.is_a?(RDL::Type::SingletonType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.val.class.to_s == orig_type.name + return "E" + elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.base.to_s == orig_type.base.to_s + return "P" + elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.base.to_s == orig_type.to_s + return "P" + elsif orig_type.is_a?(RDL::Type::GenericType) && inf_type.is_a?(RDL::Type::NominalType) && orig_type.base.to_s == inf_type.to_s + return "P" + elsif inf_type.array_type? && orig_type.array_type? + return "P" + elsif inf_type.hash_type? && orig_type.hash_type? + return "P" + elsif !inf_type.is_a?(RDL::Type::VarType) + if inf_type.is_a?(RDL::Type::StructuralType) || (inf_type.is_a?(RDL::Type::IntersectionType) && inf_type.types.any? { |t| t.is_a?(RDL::Type::StructuralType) }) + return "TS" + else + return "T" + end + else + return "N" + end + end + + def self.abbreviated_class?(abbrev, original) + abbrev = abbrev.to_s + original = original.to_s + original.end_with?("::" + abbrev) + end + + + def self.make_extraction_report(typ_sols) + report = RDL::Reporting::InferenceReport.new + twin_csv = CSV.open("#{if !$use_twin_network then 'no_' end}twin_#{if $use_heuristics then 'heur_' end}_#{RDL::Heuristic.simscore_cutoff if $use_twin_network}_#{RDL::Heuristic.get_top_n if $use_twin_network}_infer_data.csv", 'wb') + twin_csv << ["Class", "Method Name", "Arg/Ret/Var", "Variable Name", + "Inferred Type", "Original Type", "Exact (E) / Up to Parameter (P) / Got Type (T) / None (N)", "Solution Source", "Source Code"] + compares = Hash.new 0 + ## Twin CSV format: [Class, Name, ] + #return unless $orig_types + + # complete_types = [] + # incomplete_types = [] + + # CSV.open("infer_data.csv", "wb") { |csv| + # csv << ["Class", "Method", "Inferred Type", "Original Type", "Source Code", "Comments"] + # } + + correct_types = 0 + meth_types = 0 + ret_types = 0 + arg_types = 0 + var_types = 0 + diff_struct_types = 0 + typ_sols.each_pair { |km, typ| + klass, meth = km + orig_typ = RDL::Globals.info.get(klass, meth, :orig_type) + next if orig_typ.nil? || typ.solution.nil? + if orig_typ.is_a?(Array) + raise "expected just one original type for #{klass}##{meth}" unless orig_typ.size == 1 + orig_typ = orig_typ[0] + end + if orig_typ.is_a?(RDL::Type::MethodType) + meth_types += 1 + ast = RDL::Typecheck.get_ast(klass, meth) + code = ast.loc.expression.source + orig_typ.args.each_with_index { |orig_arg_typ, i | + inf_arg_type = typ.solution.args[i] + comp = inf_arg_type.nil? ? "N" : compare_single_type(inf_arg_type, orig_arg_typ) + compares[comp] += 1 + if typ.args[i].nil? + name = nil + elsif (typ.args[i].optional? || typ.args[i].vararg?) + name = typ.args[i].type.base_name + sol_source = typ.args[i].type.solution_source + elsif typ.args[i].is_a?(RDL::Type::FiniteHashType) + name = typ.args[i].to_s + sol_source = "TODO: handle FHTs" + else + name = typ.args[i].base_name + sol_source = typ.args[i].solution_source + end + twin_csv << [klass, meth, "Arg", name, inf_arg_type.to_s, orig_arg_typ.to_s, comp, sol_source, code] + arg_types +=1 + } + unless (orig_typ.ret == RDL::Globals.types[:bot]) ## bot type is given to any returns for which we don't have a type + ret_types += 1 + inf_ret_type = typ.solution.ret + sol_source = typ.ret.solution_source + comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) + compares[comp] += 1 + twin_csv << [klass, meth, "Ret", "", inf_ret_type.to_s, orig_typ.ret.to_s, comp, sol_source, code] + end + else + comp = typ.solution.nil? ? "N" : compare_single_type(typ.solution, orig_typ) + compares[comp] += 1 + sol_source = typ.solution_source + twin_csv << [klass, meth, "Var", meth, typ.solution.to_s, orig_typ.to_s, comp, sol_source, ""] + var_types += 1 + end + + if !meth.to_s.include?("@") && !meth.to_s.include?("$")#orig_typ.is_a?(RDL::Type::MethodType) + ast = RDL::Typecheck.get_ast(klass, meth) + code = ast.loc.expression.source + # if RDL::Util.has_singleton_marker(klass) + # comment = RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)).method(meth).comment + # else + # comment = RDL::Util.to_class(klass).instance_method(meth).comment + # end + # csv << [klass, meth, typ, orig_typ, code] #, comment + + report[klass] << { klass: klass, method_name: meth, type: typ, + orig_type: orig_typ, source_code: code } + + + # if typ.include?("XXX") + # incomplete_types << [klass, meth, typ, orig_typ, code, comment] + # else + # complete_types << [klass, meth, typ, orig_typ, code, comment] + # end + end + } + + RDL::Logging.log_header :inference, :info, "Extraction Complete" + RDL::Logging.log :inference, :info, "SIMILARITY SCORE CUTOFF = #{RDL::Heuristic.simscore_cutoff}" + RDL::Logging.log :inference, :info, "USING TOP N=#{RDL::Heuristic.get_top_n} TYPES" + twin_csv << ["Total # E:", compares["E"]] + RDL::Logging.log :inference, :info, "Total exactly correct (E): #{compares["E"]}" + twin_csv << ["Total # P:", compares["P"]] + RDL::Logging.log :inference, :info, "Total correct up to parameter (P): #{compares["P"]}" + twin_csv << ["Total # T:", compares["T"]] + RDL::Logging.log :inference, :info, "Total not correct but got type for (T): #{compares["T"] + compares["TS"]}" + twin_csv << ["Total # TS:", compares["TS"]] + RDL::Logging.log :inference, :info, "Total number of T's that were structural types: #{compares["TS"]}" + twin_csv << ["Total # N:", compares["N"]] + RDL::Logging.log :inference, :info, "Total no type for (N): #{compares["N"]}" + #twin_csv << ["Total # method types:", meth_types] + #RDL::Logging.log :inference, :info, "Total # method types: #{meth_types}" + twin_csv << ["Total # return types:", ret_types] + RDL::Logging.log :inference, :info, "Total # return types: #{ret_types}" + twin_csv << ["Total # arg types:", arg_types] + RDL::Logging.log :inference, :info, "Total # argument types: #{arg_types}" + twin_csv << ["Total # var types:", var_types] + RDL::Logging.log :inference, :info, "Total # variable types: #{var_types}" + twin_csv << ["Total # individual types:", var_types + meth_types + arg_types] + RDL::Logging.log :inference, :info, "Total # individual types: #{ret_types + arg_types + var_types}" + rescue => e + RDL::Logging.log :inference, :error, "Report Generation Error" + RDL::Logging.log :inference, :debug_error, "... got #{e}" + puts "Got: #{e}" + raise e unless RDL::Config.instance.continue_on_errors + ensure + return report + end + + def self.extract_solutions() + ## Go through once to come up with solution for all var types. + RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" + if $use_twin_network + uri = URI "http://127.0.0.1:5000/" + $http = Net::HTTP.new(uri.hostname, uri.port) + $http.start + end + @counter = 0; + used_twin = false + typ_sols = {} + loop do + @counter += 1 + @new_constraints = false + typ_sols = {} + + RDL::Logging.log :inference, :info, "[#{@counter}] Running solution extraction..." + RDL::Globals.constrained_types.each { |klass, name| + begin + RDL::Logging.log :inference, :debug, "Extracting #{RDL::Util.pp_klass_method(klass, name)}" + RDL::Type::VarType.no_print_XXX! + typ = RDL::Globals.info.get(klass, name, :type) + if typ.is_a?(Array) + raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 + tmeth = typ[0] + + arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) + + block_string = block_sol ? " { #{block_sol} }" : nil + RDL::Logging.log :inference, :trace, "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" + + #meth_sol = RDL::Type::MethodType.new arg_sols, block_sol, ret_sol + + typ_sols[[klass.to_s, name.to_sym]] = tmeth + elsif name.to_s == "splat_param" + else + ## Instance/Class (also some times splat parameter) variables: + ## There is no clear answer as to what to do in this case. + ## Just need to pick something in between bounds (inclusive). + ## For now, plan is to just use lower bound when it's not empty/%bot, + ## otherwise use upper bound. + ## Can improve later if desired. + var_sol = extract_var_sol(typ, :var) + typ.solution = var_sol + RDL::Logging.log :inference, :trace, "Extracted solution for #{klass} variable #{name} is #{var_sol}." + typ_sols[[klass.to_s, name.to_sym]] = typ + end + rescue => e + puts "GOT HERE WITH #{e}" + RDL::Logging.log :inference, :debug_error, "Error while exctracting solution for #{RDL::Util.pp_klass_method(klass, name)}: #{e}; continuing..." + raise e unless RDL::Config.instance.continue_on_errors + end + } + ## Trying: out here on last run, now try applying twin network to each pair of var types + if false #$use_twin_network && !@new_constraints && !used_twin + constrained_vars = [] + RDL::Globals.constrained_types.each { |klass, name| + typ = RDL::Globals.info.get(klass, name, :type) + ## First, collect *each individual VarType* into constrained_vars array + if typ.is_a?(Array) + typ[0].args.each { |a| + case a + when RDL::Type::VarType + constrained_vars << a + when RDL::Type::OptionalType, RDL::Type::VarargType + constrained_vars << a.type + when RDL::Type::FiniteHashType + a.elts.values.each { |v| + vt = v.optional_var_type? || v.vararg_var_type? ? v.type : v + constrained_vars << vt + } + else + raise "Expected type variable, got #{a}." + end + } + #constrained_vars = constrained_vars + typ[0].args + constrained_vars << typ[0].ret + elsif name.to_s == "splat_param" + else + constrained_vars << typ + end + } + pairs_enum = constrained_vars.combination(2) + RDL::Heuristic.twin_network_constraints(pairs_enum) + used_twin = true + end + break if !@new_constraints + end + rescue => e + puts "RECEIVED ERROR #{e} from #{e.backtrace}" + ensure + $http.finish if $http + puts "MAKING EXTRACTION REPORT" + return make_extraction_report(typ_sols) + end + + def self.set_new_constraints + @new_constraints = true + end + + +end diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index 7703a0ca..c5d027be 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -65,6 +65,9 @@ module RDL::Globals ## TODO: add inst/class vars to this list? @constrained_types = [] + ## List of [klass, method] pairs to delete when we clear cache. + @types_to_delete = [] + # Map from symbols to Array where the Procs are called when those symbols are rdl_do_typecheck'd @to_do_at = Hash.new @@ -109,6 +112,7 @@ class << RDL::Globals # add accessors and readers for module variables attr_accessor :to_typecheck attr_accessor :to_infer attr_accessor :constrained_types + attr_accessor :types_to_delete attr_accessor :to_do_at attr_accessor :deferred attr_accessor :dep_types @@ -180,6 +184,7 @@ class << RDL::Globals require 'rdl/reporting/reporting.rb' require 'rdl/constraint.rb' require 'rdl/heuristics.rb' +require 'rdl/eval_deepsim.rb' #require_relative 'rdl/stats.rb' class << RDL::Globals @@ -203,6 +208,7 @@ def self.reset @to_infer = Hash.new @to_infer[:now] = Set.new @constrained_types = [] + @types_to_delete = [] @to_do_at = Hash.new @deferred = [] # @dep_types = [] diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 876499a8..b22ba633 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -8,6 +8,7 @@ class << RDL::Typecheck ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. attr_accessor :type_names_map attr_accessor :type_vars_map + attr_accessor :failed_twin_sol_cache end module RDL::Typecheck @@ -15,6 +16,14 @@ module RDL::Typecheck @type_names_map = Hash.new { |h, k| h[k] = [] }#[] @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] @failed_sol_cache = Hash.new { |h, k| h[k] = [] } + @failed_twin_sol_cache = Hash.new { |h, k| h[k] = [] } + + def self.empty_cache! + @type_names_map = Hash.new { |h, k| h[k] = [] }#[] + @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] + @failed_sol_cache = Hash.new { |h, k| h[k] = [] } + @failed_twin_sol_cache = Hash.new { |h, k| h[k] = [] } + end def self.resolve_constraints RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." @@ -105,8 +114,10 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) RDL::Logging.log :heuristic, :debug, "Trying rule `#{name}` for variable #{var}." typ = rule.call(var) if typ.is_a?(Array) && (name == :twin_network) + count = 0 typ.each { |t| - new_cons = {} + new_cons = {} + count += 1 begin t = t.canonical next if @failed_sol_cache[var].include?(t) @@ -115,18 +126,16 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) var.add_and_propagate_lower_bound(t, nil, new_cons) end RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" - puts "Successfully applied twin network! Solution #{t} for #{var}".blue + puts "Successfully applied twin network! Solution #{t} for #{var}".blue unless $collecting_time_data @new_constraints = true if !new_cons.empty? RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? raise "2. got here with #{var} and #{t}" if t.to_s == "%bot" @type_vars_map[t] = @type_vars_map[t] | [var] + var.solution_source = "Twin" if (var.solution != t) return t rescue RDL::Typecheck::StaticTypeError => e - if (var.meth == :get_rate) || (var.meth == :add_rate) - puts "Tried to apply solution #{t} to #{var}, but got error:" - puts e - end @failed_sol_cache[var] << t + @failed_twin_sol_cache[var] << t undo_constraints(new_cons) end } @@ -152,7 +161,8 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) @new_constraints = true if !new_cons.empty? RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? raise "3.. got here with #{var} and #{typ} for #{name}" if typ.to_s == "%bot" - @type_vars_map[typ] = @type_vars_map[typ] | [var] + @type_vars_map[typ] = @type_vars_map[typ] | [var] + var.solution_source = "Heur: #{name}" if (var.solution != typ) return typ #sol = typ rescue RDL::Typecheck::StaticTypeError => e @@ -175,7 +185,7 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) end ## out here, none of the heuristics applied. ## Try to use `sol` as solution -- there is a chance it will - begin + begin new_cons = {} sol = var if sol == RDL::Globals.types[:bot] # just use var itself when result of solution extraction was %bot. return sol if sol.is_a?(RDL::Type::VarType) ## don't add var type as solution @@ -194,7 +204,7 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) RDL::Logging.log :inference, :trace, "New Constraints branch B" if !new_cons.empty? #raise "Adding Solution Array or Number to variable #{var}".red if (sol.to_s == "(Array or Number)") && var.to_s != "{ { DashboardSection# var: @row }#[] call_ret: ret }" if sol.is_a?(RDL::Type::GenericType) - new_params = sol.params.map { |p| if p.is_a?(RDL::Type::VarType) && !p.to_infer then p else extract_var_sol(p, category) end } + new_params = sol.params.map { |p| if p.is_a?(RDL::Type::VarType) && (!p.to_infer || (p == var)) then p else extract_var_sol(p, category) end } sol = RDL::Type::GenericType.new(sol.base, *new_params) elsif sol.is_a?(RDL::Type::TupleType) new_params = sol.params.map { |t| extract_var_sol(t, category) } @@ -208,14 +218,13 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) ## no new constraints in this case so we'll leave it as is sol = var end - - if (sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) || sol.is_a?(RDL::Type::TupleType) || sol.is_a?(RDL::Type::FiniteHashType) || sol.is_a?(RDL::Type::UnionType)) && !(sol == RDL::Globals.types[:object]) + if (sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) || sol.is_a?(RDL::Type::TupleType) || sol.is_a?(RDL::Type::FiniteHashType) || (sol == RDL::Globals.types[:bool])) && !(sol == RDL::Globals.types[:object]) raise "1. got here with #{var} and #{sol}" if sol.to_s == "%bot" name = var.base_name#(var.category == :ret) ? var.meth : var.name @type_names_map[sol] = @type_names_map[sol] | [name.to_s] @type_vars_map[sol] = @type_vars_map[sol] | [var] end - + var.solution_source = "Constraints" if (var.solution != sol) return sol end @@ -266,8 +275,8 @@ def self.extract_meth_sol(tmeth) inter = RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical typs = inter.is_a?(RDL::Type::IntersectionType) ? inter.types : [inter] typs.each { |m| - raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) - block_sols << RDL::Type::MethodType.new(*extract_meth_sol(m)) + #raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) + block_sols << (m.is_a?(RDL::Type::MethodType) ? RDL::Type::MethodType.new(*extract_meth_sol(m)) : m) } block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical else @@ -310,7 +319,6 @@ def self.compare_single_type(inf_type, orig_type) return "E" elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && abbreviated_class?(orig_type.to_s, inf_type.to_s) ## needed for diferring scopes, e.g. TZInfo::Timestamp & Timestamp - puts "Treating #{inf_type} and #{orig_type} as equivalent due to suffix rule.".red return "E" elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } return "E" @@ -326,6 +334,8 @@ def self.compare_single_type(inf_type, orig_type) return "E" elsif inf_type.is_a?(RDL::Type::StructuralType) && orig_type.is_a?(RDL::Type::StructuralType) && (inf_type.methods.map { |m, _| m } == orig_type.methods.map { |m, _| m}) return "E" + elsif inf_type.is_a?(RDL::Type::SingletonType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.val.class.to_s == orig_type.name + return "E" elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.base.to_s == orig_type.base.to_s return "P" elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.base.to_s == orig_type.to_s @@ -337,7 +347,11 @@ def self.compare_single_type(inf_type, orig_type) elsif inf_type.hash_type? && orig_type.hash_type? return "P" elsif !inf_type.is_a?(RDL::Type::VarType) - return "T" + if inf_type.is_a?(RDL::Type::StructuralType) || (inf_type.is_a?(RDL::Type::IntersectionType) && inf_type.types.any? { |t| t.is_a?(RDL::Type::StructuralType) }) + return "TS" + else + return "T" + end else return "N" end @@ -352,9 +366,9 @@ def self.abbreviated_class?(abbrev, original) def self.make_extraction_report(typ_sols) report = RDL::Reporting::InferenceReport.new - twin_csv = CSV.open("#{if !$use_twin_network then 'no_' end}twin_#{if $use_heuristics then 'heur_' end}infer_data.csv", 'wb') + twin_csv = CSV.open("#{if !$use_twin_network then 'no_' end}twin_#{if $use_heuristics then 'heur_' end}_#{RDL::Heuristic.simscore_cutoff}_#{RDL::Heuristic.get_top_n}_infer_data.csv", 'wb') twin_csv << ["Class", "Method Name", "Arg/Ret/Var", "Variable Name", - "Inferred Type", "Original Type", "Exact (E) / Up to Parameter (P) / Got Type (T) / None (N)", "Source Code"] + "Inferred Type", "Original Type", "Exact (E) / Up to Parameter (P) / Got Type (T) / None (N)", "Solution Source", "Source Code"] compares = Hash.new 0 ## Twin CSV format: [Class, Name, ] #return unless $orig_types @@ -371,6 +385,7 @@ def self.make_extraction_report(typ_sols) ret_types = 0 arg_types = 0 var_types = 0 + diff_struct_types = 0 typ_sols.each_pair { |km, typ| klass, meth = km orig_typ = RDL::Globals.info.get(klass, meth, :orig_type) @@ -391,25 +406,30 @@ def self.make_extraction_report(typ_sols) name = nil elsif (typ.args[i].optional? || typ.args[i].vararg?) name = typ.args[i].type.base_name + sol_source = typ.args[i].type.solution_source elsif typ.args[i].is_a?(RDL::Type::FiniteHashType) name = typ.args[i].to_s + sol_source = "TODO: handle FHTs" else name = typ.args[i].base_name + sol_source = typ.args[i].solution_source end - twin_csv << [klass, meth, "Arg", name, inf_arg_type.to_s, orig_arg_typ.to_s, comp, code] + twin_csv << [klass, meth, "Arg", name, inf_arg_type.to_s, orig_arg_typ.to_s, comp, sol_source, code] arg_types +=1 } unless (orig_typ.ret == RDL::Globals.types[:bot]) ## bot type is given to any returns for which we don't have a type ret_types += 1 inf_ret_type = typ.solution.ret + sol_source = typ.ret.solution_source comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) compares[comp] += 1 - twin_csv << [klass, meth, "Ret", "", inf_ret_type.to_s, orig_typ.ret.to_s, comp, code] + twin_csv << [klass, meth, "Ret", "", inf_ret_type.to_s, orig_typ.ret.to_s, comp, sol_source, code] end else comp = typ.solution.nil? ? "N" : compare_single_type(typ.solution, orig_typ) compares[comp] += 1 - twin_csv << [klass, meth, "Var", meth, typ.solution.to_s, orig_typ.to_s, comp, ""] + sol_source = typ.solution_source + twin_csv << [klass, meth, "Var", meth, typ.solution.to_s, orig_typ.to_s, comp, sol_source, ""] var_types += 1 end @@ -436,12 +456,16 @@ def self.make_extraction_report(typ_sols) } RDL::Logging.log_header :inference, :info, "Extraction Complete" + RDL::Logging.log :inference, :info, "SIMILARITY SCORE CUTOFF = #{RDL::Heuristic.simscore_cutoff}" + RDL::Logging.log :inference, :info, "USING TOP N=#{RDL::Heuristic.get_top_n} TYPES" twin_csv << ["Total # E:", compares["E"]] RDL::Logging.log :inference, :info, "Total exactly correct (E): #{compares["E"]}" twin_csv << ["Total # P:", compares["P"]] RDL::Logging.log :inference, :info, "Total correct up to parameter (P): #{compares["P"]}" twin_csv << ["Total # T:", compares["T"]] - RDL::Logging.log :inference, :info, "Total not correct but got type for (T): #{compares["T"]}" + RDL::Logging.log :inference, :info, "Total not correct but got type for (T): #{compares["T"] + compares["TS"]}" + twin_csv << ["Total # TS:", compares["TS"]] + RDL::Logging.log :inference, :info, "Total number of T's that were structural types: #{compares["TS"]}" twin_csv << ["Total # N:", compares["N"]] RDL::Logging.log :inference, :info, "Total no type for (N): #{compares["N"]}" #twin_csv << ["Total # method types:", meth_types] @@ -469,7 +493,7 @@ def self.extract_solutions() if $use_twin_network uri = URI "http://127.0.0.1:5000/" $http = Net::HTTP.new(uri.hostname, uri.port) - #$http.start + $http.start end @counter = 0; used_twin = false @@ -480,11 +504,9 @@ def self.extract_solutions() typ_sols = {} RDL::Logging.log :inference, :info, "[#{@counter}] Running solution extraction..." - RDL::Globals.constrained_types.each { |klass, name| begin RDL::Logging.log :inference, :debug, "Extracting #{RDL::Util.pp_klass_method(klass, name)}" - RDL::Type::VarType.no_print_XXX! typ = RDL::Globals.info.get(klass, name, :type) if typ.is_a?(Array) @@ -510,7 +532,6 @@ def self.extract_solutions() var_sol = extract_var_sol(typ, :var) typ.solution = var_sol RDL::Logging.log :inference, :trace, "Extracted solution for #{klass} variable #{name} is #{var_sol}." - typ_sols[[klass.to_s, name.to_sym]] = typ end rescue => e @@ -557,7 +578,7 @@ def self.extract_solutions() rescue => e puts "RECEIVED ERROR #{e} from #{e.backtrace}" ensure - #$http.finish + $http.finish if $http puts "MAKING EXTRACTION REPORT" return make_extraction_report(typ_sols) end diff --git a/lib/rdl/eval_deepsim.rb b/lib/rdl/eval_deepsim.rb new file mode 100644 index 00000000..7ab6760e --- /dev/null +++ b/lib/rdl/eval_deepsim.rb @@ -0,0 +1,97 @@ +class RDL::Heuristic + + @arg_type_pool = [] + @ret_type_pool = [] + @var_ranked_sols = {} + + def self.get_ranked_sols + @var_ranked_sols + end + + def self.make_var_types + $orig_type_list.each { |klass, meth| + begin + meth_type = RDL::Typecheck.make_unknown_method_type(klass, meth) + rescue => e + puts "Could not make unknown method type for #{klass}/#{meth}" + next + end + orig_type = RDL::Globals.info.get(klass, meth, :orig_type) + raise "Could not find original type for #{klass}/#{meth}" unless orig_type + orig_type = orig_type[0] + + next if orig_type.args.size != meth_type.args.size + + meth_type.args.each_with_index { |arg, i| + arg = arg.type if arg.optional_var_type? || arg.vararg_var_type? + next if arg.is_a?(RDL::Type::FiniteHashType) + #puts "Adding arg solution #{orig_type.args[i]} for variable #{arg}" + arg.solution = orig_type.args[i] + @arg_type_pool << arg + } + + next if orig_type.ret == RDL::Globals.types[:bot] + meth_type.ret.solution = orig_type.ret + #puts "Adding ret solution #{orig_type.ret} for #{meth_type.ret}" + @ret_type_pool << meth_type.ret + } + end + + def self.gen_rankings(category) + if category == :arg + pool = @arg_type_pool + elsif category == :ret + pool = @ret_type_pool + else + raise "Unexpected category #{category}." + end + + pool.each { |var1| + vec_res = vectorize_var(var1) + next if vec_res.nil? + to_compare = [] + pool.each { |var2| + next if var2 == var1 + vec_res = vectorize_var(var2) + next if vec_res.nil? + to_compare << var2 + } + to_compare_ids = to_compare.map { |v| v.object_id } + params = { action: "get_similarities", id1: var1.object_id, id_comps: to_compare_ids, kind: category } + res = send_query(params) + res = JSON.parse(res.body) + raise "Expected #{to_compare_ids.size} similarity scores, received #{res.size}" unless res.size == to_compare_ids.size + sols = {} + res.each_with_index { |score, i| + #@bert_cache[var1][var2] = score + sols[score] = to_compare[i] + } + ranked_sols = sols.sort.map { |score, var| var.solution }.reverse.uniq + @var_ranked_sols[var1] = ranked_sols + } + end + + def self.find_first_match(var) + rankings = @var_ranked_sols[var] + rank = rankings.index { |sol| (var.solution == sol) || (var.solution.is_a?(RDL::Type::GenericType) && sol.is_a?(RDL::Type::GenericType) && (var.solution.base == sol.base)) } + rank = rank.nil? ? -1 : 1 + rank + return rank + end + + def self.get_rank_accs(category) + make_var_types + gen_rankings(category) + correct_sol_rankings = @var_ranked_sols.keys.map { |var| find_first_match(var) } + tot = correct_sol_rankings.size + puts "TOTAL # QUERIES = #{tot}" + puts "CATEGORY = #{category}" + puts "rank,num sols at or under rank, accuracy" + 1.upto(10) { |rank| + count = correct_sol_rankings.count { |r| (r != -1) && (r <= rank) } + puts "#{rank},#{count},#{count.to_f / tot}" + } + + end + + +end diff --git a/lib/rdl/eval_deepsim.rb~ b/lib/rdl/eval_deepsim.rb~ new file mode 100644 index 00000000..c41740c1 --- /dev/null +++ b/lib/rdl/eval_deepsim.rb~ @@ -0,0 +1,3 @@ +class RDL::Heuristic + +end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 23d4b057..12a8c561 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -1,8 +1,41 @@ class RDL::Heuristic + + @simscore_cutoff = 0.5 + + @top_n = 3 + + @nums_above_cutoff = {} + + def self.get_nums_above_cutoff + @nums_above_cutoff + end + + def self.report_nums_above_cutoff + vals = @nums_above_cutoff.values + puts "Total number of queries: #{vals.size}" + [1, 3, 5, 7, 9, 11, 13].each { |v| + puts "Number of queries with more than #{v} solutions over 0.5: #{vals.count { |x| x > v }}" + } + end + + def self.simscore_cutoff=(c) + @simscore_cutoff = c + end + + def self.simscore_cutoff + @simscore_cutoff + end + + def self.get_top_n + @top_n + end @rules = {} @meth_cache = {} + @meth_to_cls_map = Hash.new {|h, m| h[m] = Set.new} # maps a method to a set of classes with that method + + @ast_cache = {} @twin_cache = Hash.new { |h, k| h[k] = {} } @bert_cache = Hash.new { |h, k| h[k] = {} } @@ -10,6 +43,43 @@ class RDL::Heuristic $use_only_param_position = false ## whether or not to use just an arg's parameter position, or use all of its uses in a method, when running on BERT model + def self.empty_cache! + ## temporarily commenting out +=begin + @meth_cache = {} + @meth_to_cls_map = Hash.new {|h, m| h[m] = Set.new} # maps a method to a set of classes with that method + @ast_cache = {} + @twin_cache = Hash.new { |h, k| h[k] = {} } + @bert_cache = Hash.new { |h, k| h[k] = {} } + @vectorized_vars = {} + send_query({action: "empty_cache!"}) if $use_twin_network + RDL::Globals.parser_cache = Hash.new + RDL::Typecheck.empty_cache! + RDL::Type::StructuralType.clear_cache! + RDL::Type::VarType.clear_cache! +=end + #RDL::Type::NominalType.clear_cache! + #RDL::Type::SingletonType.clear_cache! + #RDL::Type::IntersectionType.clear_cache! + #RDL::Type::VarargType.clear_cache! + #RDL::Type::DynamicType.clear_cache! + end + + def self.init_meth_to_cls # to be called before the first call to struct_to_nominal + ObjectSpace.each_object(Module).each do |c| + #unless c.to_s.start_with?("# 10 ## in this case, just keep the struct types + return if (matching_classes.size == 0) || (matching_classes.size > 10) ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } + RDL::Logging.log :heuristic, :trace, "These are: %s" % (nom_sing_types*", ") union = RDL::Type::UnionType.new(*nom_sing_types).canonical + RDL::Logging.log :heuristic, :trace, "The union of which is canonicalized to %s" % union #struct_types.each { |st| var_type.ubounds.delete_if { |s, loc| s.equal?(st) } } ## remove struct types from upper bounds + return union ## used to add and propagate here. Now that this is a heuristic, this should be done after running the rule. #var_type.add_and_propagate_upper_bound(union, nil) - end - + end def self.twin_network_guess(var_type) @@ -97,7 +171,7 @@ def self.twin_network_guess(var_type) sim_score = res.body.to_f =end - if sim_score > 0.8 + if sim_score > @simscore_cutoff #puts "Twin network found #{name1} and list #{names} have average similarity score of #{sim_score}.".green #puts "Adding #{t} as a potential solution." #sols << t @@ -125,7 +199,7 @@ def self.twin_network_guess(var_type) end sim_score = res.body.to_f - if sim_score > 0.8 + if sim_score > @simscore_cutoff puts "Twin network found #{name1} and #{name2} have similarity score of #{sim_score}." puts "Attempting to apply Integer as solution." ## TODO: once we replace "count" above, also have to replace Integer as solution. @@ -176,32 +250,52 @@ def self.send_query(params) uri = URI "http://127.0.0.1:5000/" uri.query = URI.encode_www_form(params) #res = Net::HTTP.get_response(uri) - puts "About to send query #{params}" if params[:action] == "get_similarity" - start = Time.now - #res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https', :read_timeout => 9000) { |http| res = http.request_get(uri) } - res = $http.request_get(uri) - endt = Time.now - puts "Total time taken: #{endt-start}" + res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https', :read_timeout => 9000) { |http| res = http.request_get(uri) } + #res = $http.request_get(uri) raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" return res end + class CastRemover < Parser::TreeRewriter + ## Want to compare *original* sources, excluding RDL.type_casts which can affect comparison + def on_send(t) + if (t.children[1] == :type_cast) && (t.children[0] && t.children[0].loc.expression.source == "RDL") + replace(t.loc.expression, t.children[2].loc.expression.source) + end + super + end + end + + def self.ast_without_casts(klass, method) + return @ast_cache[[klass, method]] if @ast_cache[[klass, method]] + ast = RDL::Typecheck.get_ast(klass, method) + return nil if ast.nil? + ast = Parser::CurrentRuby.parse(ast.loc.expression.source) ## need to re-parse to get rid of offset issue + remover = CastRemover.new + buffer = Parser::Source::Buffer.new("(ast)") + buffer.source = ast.location.expression.source + new_source = remover.rewrite(buffer, ast) + new_ast = Parser::CurrentRuby.parse new_source + @ast_cache[[klass, method]] = new_ast + return new_ast + end + def self.vectorize_var(var_type) return true if @vectorized_vars[var_type.object_id] ## already vectorized and cached server side #puts "About to vectorize var #{var_type}" if (var_type.category == :arg) - ast = RDL::Typecheck.get_ast(var_type.cls, var_type.meth) + ast = ast_without_casts(var_type.cls, var_type.meth) return nil if ast.nil? locs = get_var_loc(ast, var_type) source = ast.loc.expression.source - #puts "Querying for var #{var_type.base_name}" + #puts "Querying for x`var #{var_type.base_name}" #puts "Sanity check: " #locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "arg" } send_query(params) elsif (var_type.category == :var) var_type.meths_using_var.each { |klass, meth| - ast = RDL::Typecheck.get_ast(klass, meth) + ast = ast_without_casts(klass, meth) locs = get_var_loc(ast, var_type) source = ast.loc.expression.source #puts "Querying for var #{var_type.name} in method #{klass}##{meth}" @@ -212,11 +306,13 @@ def self.vectorize_var(var_type) } send_query({ action: "bert_vectorize", object_id: var_type.object_id, category: "var", average: true }) elsif (var_type.category == :ret) - ast = RDL::Typecheck.get_ast(var_type.cls, var_type.meth) + ast = ast_without_casts(var_type.cls, var_type.meth) return nil if ast.nil? begin_pos = ast.loc.expression.begin_pos locs = [ast.loc.name.begin_pos - begin_pos, ast.loc.name.end_pos - begin_pos-1] ## for now, let's just try using method name locs = locs + get_ret_sites(ast) + locs = [ast.loc.name.begin_pos - begin_pos, ast.loc.name.end_pos - begin_pos-1] if locs.empty? + return nil if locs.empty? source = ast.loc.expression.source #puts "Querying for return #{var_type}" #puts "Sanity check: " @@ -224,16 +320,13 @@ def self.vectorize_var(var_type) params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "ret" } send_query(params) else - puts "not implemented yet" + #puts "not implemented yet for vartype #{var_type}" + return nil end @vectorized_vars[var_type.object_id] = true return true end - def self.vectorize_vars(vars) - vars = vars.keep_if { |v| !@vectorized_vars[v.object_id] && [:arg, :var, :ret].include?(v.category) } - return true if vars.empty? - end ## [+ var_kind +] is either :arg, :ret, or :var. def self.visualize_bert(var_kind) @@ -242,7 +335,7 @@ def self.visualize_bert(var_kind) type = RDL::Globals.info.get(klass, mname, :type) orig_type = RDL::Globals.info.get(klass, mname, :orig_type) if orig_type.nil? - puts "Could not find original type for #{klass}##{mname}.".red + puts "Could not find original type for #{klass}##{mname}.".red unless $collecting_time_data next end if type.is_a?(Array) @@ -283,6 +376,99 @@ def self.visualize_bert(var_kind) send_query(params) end + def self.fast_bert_model_guess(var_type) + return unless (var_type.category == :arg) || (var_type.category == :var) || (var_type.category == :ret) + puts "Vectorizing #{var_type} of object id #{var_type.object_id}..." unless $collecting_time_data + ret = vectorize_var(var_type) + return nil if ret.nil? + + sols = {} + to_compare_vars_same_kind = [] + to_compare_types_same_kind = [] + to_compare_vars_diff_kind = [] + to_compare_types_diff_kind = [] + + RDL::Typecheck.type_vars_map.each { |t, vars| + raise "Got here for #{t}" if vars.empty? + vars.each { |var2| + next if (var_type == var2) || !((var2.category == :arg) || (var2.category == :var) || (var2.category == :ret)) #!(var_type.category == var2.category) + #next if !(var_type.category == var2.category) + next if ((var_type.category == :ret) || (var2.category == :ret)) && !(var_type.category == var2.category) # only compare rets with rets + if sim_score = @bert_cache[var_type][var2] + puts "Hit cache for vars #{var_type} and #{var2}, with score of #{sim_score}".red unless $collecting_time_data + message = "Received similarity score of #{sim_score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" + if sim_score > @simscore_cutoff + puts message.green unless $collecting_time_data + sols[sim_score] = t + else + puts message.red unless $collecting_time_data + end + else + puts "Vectorizing #{var2} of object id #{var2.object_id}..." unless $collecting_time_data + vec_res = vectorize_var(var2) + if vec_res.nil? + puts "Could not find AST for #{var2}".yellow unless $collecting_time_data + next + end + if (var_type.category == var2.category) || (var_type.category == :var) || (var2.category == :var) + to_compare_vars_same_kind << var2 + to_compare_types_same_kind << t + else + to_compare_vars_diff_kind << var2 + to_compare_types_diff_kind << t + end + end + } + } + + unless to_compare_vars_same_kind.empty? + to_compare_ids = to_compare_vars_same_kind.map { |v| v.object_id } + #kind = (var_type.category == :var) ? "cosine" : var_type.category.to_s ## kind of comparison to make + kind = (var_type.category == :var) ? "arg" : var_type.category.to_s ## kind of comparison to make + params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind } + res = send_query(params) + res = JSON.parse(res.body) + raise "Expected #{to_compare_ids.size} similarity scores, received #{res.size}" unless res.size == to_compare_ids.size + res.each_with_index { |score, i| + var2 = to_compare_vars_same_kind[i] + t = to_compare_types_same_kind[i] + @bert_cache[var_type][var2] = score + message = "Received similarity score of #{score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" + if score > @simscore_cutoff + puts message.green unless $collecting_time_data + sols[score] = t + else + puts message.red unless $collecting_time_data + end + } + end + unless to_compare_vars_diff_kind.empty? + to_compare_ids = to_compare_vars_diff_kind.map { |v| v.object_id } + #kind = "cosine" + kind = "arg" + params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind } + res = send_query(params) + res = JSON.parse(res.body) + raise "Expected #{to_compare_ids.size} similarity scores, received #{res.size}" unless res.size == to_compare_ids.size + res.each_with_index { |score, i| + var2 = to_compare_vars_diff_kind[i] + t = to_compare_types_diff_kind[i] + @bert_cache[var_type][var2] = score + message = "Received similarity score of #{score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" + if score > @simscore_cutoff + puts message.green unless $collecting_time_data + sols[score] = t + else + puts message.red unless $collecting_time_data + end + } + end + + res = sols.sort.map { |sim_score, t| t }.reverse.uniq#.first(@top_n) + @nums_above_cutoff[var_type] = res.size + return res.first(@top_n) + end + def self.bert_model_guess(var_type) if (var_type.category == :arg) || (var_type.category == :var) || (var_type.category == :ret) sols = {} @@ -299,16 +485,16 @@ def self.bert_model_guess(var_type) next if ((var_type.category == :ret) || (var2.category == :ret)) && !(var_type.category == var2.category) # only compare rets with rets count += 1 if @bert_cache[var_type][var2] - puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red + puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red unless $collecting_time_data #sum += @bert_cache[var_type][var2] sim_score = @bert_cache[var_type][var2] else vec_res = vectorize_var(var2) if vec_res.nil? - puts "Could not find AST for #{var2}".yellow + puts "Could not find AST for #{var2}".yellow unless $collecting_time_data next end - puts "About to ask for similarity of #{var_type} and #{var2}" + puts "About to ask for similarity of #{var_type} and #{var2}" unless $collecting_time_data res = send_query({ action: "get_similarity", id1: var_type.object_id, id2: var2.object_id, kind1: var_type.category.to_s, kind2: var2.category.to_s }) #sum += res.body.to_f sim_score = res.body.to_f @@ -316,11 +502,11 @@ def self.bert_model_guess(var_type) @bert_cache[var2][var_type] = res.body.to_f end message = "Received similarity score of #{sim_score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" - if sim_score > 0.8 - puts message.green + if sim_score > @simscore_cutoff + puts message.green unless $collecting_time_data sols[sim_score] = t else - puts message.red + puts message.red unless $collecting_time_data #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red end } @@ -330,7 +516,7 @@ def self.bert_model_guess(var_type) puts "Received overall sim_score average of #{sim_score} for var #{var_type.cls}##{var_type.meth}##{var_type.name} and type #{t}".green if sim_score != 0 - if sim_score > 0.8 + if sim_score > @simscore_cutoff sols[sim_score] = t else #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red @@ -522,5 +708,7 @@ def model_set_type ### For rules involving :include?, :==, :!=, etc. we would need to track the exact receiver/args used in the method call, and somehow store these in the bounds created for a var type. if $use_twin_network - RDL::Heuristic.add(:twin_network) { |var| RDL::Heuristic.bert_model_guess(var) } + RDL::Heuristic.add(:twin_network) { |var| RDL::Heuristic.fast_bert_model_guess(var) } end + + diff --git a/lib/rdl/reporting/sorbet.rb b/lib/rdl/reporting/sorbet.rb index d4599db4..c4a1c80a 100644 --- a/lib/rdl/reporting/sorbet.rb +++ b/lib/rdl/reporting/sorbet.rb @@ -5,6 +5,8 @@ module RDL::Reporting::Sorbet def to_sorbet_type(typ) RDL::Logging.debug :reporting, typ + typ = typ.canonical + case typ when RDL::Type::StructuralType RDL::Globals.types[:dyn] @@ -17,28 +19,26 @@ def to_sorbet_type(typ) RDL::Globals.types[:dyn] when RDL::Type::UnionType - type = typ.canonical - if type.is_a? RDL::Type::UnionType - types = type.types.map { |x| to_sorbet_type(x) } + if typ.is_a? RDL::Type::UnionType + types = typ.types.map { |x| to_sorbet_type(x) } RDL::Type::UnionType.new(*types) else - type + typ end when RDL::Type::IntersectionType - type = typ.canonical - if type.is_a? RDL::Type::IntersectionType - types = type.types.map { |x| to_sorbet_type(x) } + if typ.is_a? RDL::Type::IntersectionType + types = typ.types.map { |x| to_sorbet_type(x) } RDL::Type::IntersectionType.new(*types) else - type + typ end when RDL::Type::SingletonType typ.nominal when RDL::Type::FiniteHashType - c = typ.canonical + c = typ#.canonical types = c.elts.values.map { |v| v.is_a?(RDL::Type::OptionalType) ? to_sorbet_type(v.type) : to_sorbet_type(v) } return RDL::Globals.types[:dyn] if types.member? RDL::Globals.types[:dyn] diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 6506931a..0cc3247a 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -266,12 +266,15 @@ def self.infer(klass, meth) end def self._infer(klass, meth) + @num_lines = Hash.new(0) unless @num_lines RDL::Logging.log_header :inference, :debug, "Infering #{RDL::Util.pp_klass_method(klass, meth)}" RDL::Config.instance.use_comp_types = true RDL::Config.instance.number_mode = true @var_cache = {} ast = get_ast(klass, meth) + num_lines = RDL::Util.count_num_lines(klass, meth) + @num_lines[[klass, meth]] = num_lines.nil? ? 0 : num_lines if ast.nil? RDL::Logging.log :inference, :warning, "Warning: Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}; skipping method" @@ -343,6 +346,7 @@ def self._infer(klass, meth) else targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this @num_casts = 0 + @cached_cast_locs = [] _, body_type = tc(scope, Env.new(targs_dup), body) ## TODO: need separate argument indicating we're performing inference? or is this exactly the same as type checking... end @@ -356,7 +360,7 @@ def self._infer(klass, meth) RDL::Globals.info.set(klass, meth, :typechecked, true) - RDL::Globals.constrained_types << [klass, meth] + RDL::Globals.constrained_types << [klass, meth] unless RDL::Globals.constrained_types.include? [klass, name] RDL::Logging.log :inference, :debug, "Done with constraint generation." end @@ -403,6 +407,7 @@ def self.typecheck(klass, meth, ast=nil, types = nil) else targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this @num_casts = 0 + @cached_cast_locs = [] _, body_type = tc(scope, Env.new(targs_dup), body) end error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || meth == :initialize ||RDL::Type::Type.leq(body_type, type.ret, ast: ast) @@ -419,6 +424,10 @@ def self.get_num_casts return @num_casts end + def self.get_num_lines + return @num_lines + end + ### TODO: clean up below. Should probably incorporate it into `targs.merge` call in `self.typecheck`. def self.widen_scopes(h1, h2) h1new = h1.nil? ? nil : {} @@ -1630,7 +1639,8 @@ def self.tc_type_cast(scope, env, e) end sub_expr = e.children[2] env2, _ = tc(scope, env, sub_expr) - @num_casts += 1 + @num_casts += 1 unless @cached_cast_locs.include?(e.object_id) ## make sure we only count casts once + @cached_cast_locs << e.object_id [env2, typ] end @@ -2568,13 +2578,14 @@ def self.make_unknown_method_type(klass, meth) meth_type = RDL::Type::MethodType.new(arg_types, block_type, ret_vartype) RDL::Globals.info.add(klass, meth, :type, meth_type) + RDL::Globals.types_to_delete << [klass, meth] unless RDL::Globals.types_to_delete.include? [klass, meth] return meth_type end def self.make_unknown_var_type(klass, name, kind_text) var_type = RDL::Type::VarType.new(cls: klass, name: name, category: kind_text) RDL::Globals.info.set(klass, name, :type, var_type) - RDL::Globals.constrained_types << [klass, name] + RDL::Globals.constrained_types << [klass, name] unless RDL::Globals.constrained_types.include? [klass, name] return var_type end diff --git a/lib/rdl/types/bot.rb b/lib/rdl/types/bot.rb index 5e0cda8c..285f891a 100644 --- a/lib/rdl/types/bot.rb +++ b/lib/rdl/types/bot.rb @@ -11,6 +11,10 @@ def self.new return @@cache end + def self.clear_cache! + @@cache = nil + end + def initialize super end diff --git a/lib/rdl/types/dynamic.rb b/lib/rdl/types/dynamic.rb index daae1230..32f2a25c 100644 --- a/lib/rdl/types/dynamic.rb +++ b/lib/rdl/types/dynamic.rb @@ -13,6 +13,10 @@ def self.new return @@cache end + def self.clear_cache! + @@cache = {} + end + def initialize super end diff --git a/lib/rdl/types/generic.rb b/lib/rdl/types/generic.rb index 6a8c9b8e..43b1ac3b 100644 --- a/lib/rdl/types/generic.rb +++ b/lib/rdl/types/generic.rb @@ -92,7 +92,7 @@ def canonical end def canonicalize! - @params.map! {|p| p.canonical} + @params.map! {|param| param.canonical } end end end diff --git a/lib/rdl/types/intersection.rb b/lib/rdl/types/intersection.rb index 5da70031..82e20b2d 100644 --- a/lib/rdl/types/intersection.rb +++ b/lib/rdl/types/intersection.rb @@ -41,6 +41,10 @@ def initialize(types) super() end + def self.clear_cache! + @@cache = {} + end + def canonical canonicalize! return @canonical if @canonical diff --git a/lib/rdl/types/nominal.rb b/lib/rdl/types/nominal.rb index 6cc45d39..97152197 100644 --- a/lib/rdl/types/nominal.rb +++ b/lib/rdl/types/nominal.rb @@ -21,6 +21,10 @@ def initialize(name) @name = name end + def self.clear_cache! + @@cache = {} + end + def ==(other) return false if other.nil? other = other.canonical diff --git a/lib/rdl/types/singleton.rb b/lib/rdl/types/singleton.rb index 9de0faf3..d2340197 100644 --- a/lib/rdl/types/singleton.rb +++ b/lib/rdl/types/singleton.rb @@ -25,6 +25,10 @@ def initialize(val) @nominal = NominalType.new(val.class) end + def self.clear_cache! + @@cache = {} + end + def ==(other) return false if other.nil? other = other.canonical diff --git a/lib/rdl/types/structural.rb b/lib/rdl/types/structural.rb index 12e79c71..a33f9bc0 100644 --- a/lib/rdl/types/structural.rb +++ b/lib/rdl/types/structural.rb @@ -29,6 +29,10 @@ def initialize(methods) super() end + def self.clear_cache! + @@cache = {} + end + def to_s # :nodoc: "[ " + @methods.each_pair.map { |m, t| "#{m.to_s}: #{t.to_s}" }.sort.join(", ") + " ]" end diff --git a/lib/rdl/types/top.rb b/lib/rdl/types/top.rb index 651c6746..134fabd6 100644 --- a/lib/rdl/types/top.rb +++ b/lib/rdl/types/top.rb @@ -11,6 +11,10 @@ def self.new return @@cache end + def self.clear_cache! + @@cache = nil + end + def initialize super end diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index e71a6fab..d82e24d0 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -7,6 +7,10 @@ class TypeError < StandardError; end class Type @@contract_cache = {} + def self.clear_cache! + @@contract_cache = {} + end + def name RDL::Logging.log :typecheck, :error, "Attempted to access name field for #{self.class}" '_NAME_ERROR' diff --git a/lib/rdl/types/var.rb b/lib/rdl/types/var.rb index ab6c2194..8de8dfca 100644 --- a/lib/rdl/types/var.rb +++ b/lib/rdl/types/var.rb @@ -1,7 +1,7 @@ module RDL::Type class VarType < Type attr_reader :name, :cls, :meth, :category, :to_infer - attr_accessor :lbounds, :ubounds, :solution, :meths_using_var + attr_accessor :lbounds, :ubounds, :solution, :solution_source, :meths_using_var @@cache = {} @@print_XXX = false @@ -50,13 +50,15 @@ def initialize(name_or_hash) end end + def self.clear_cache! + @@cache = {} + end ## Adds an upper bound to self, and transitively pushes it to all of self's lower bounds. # [+ typ +] is the Type to add as upper bound. # [+ ast +] is the AST where the bound originates from, used for error messages. # [+ new_cons +] is a Hash>. When provided, can be used to roll back constraints in case an error pops up. def add_and_propagate_upper_bound(typ, ast, new_cons = {}) - return if self.equal?(typ) if !@ubounds.any? { |t, a| t == typ } @ubounds << [typ, ast] @@ -67,7 +69,6 @@ def add_and_propagate_upper_bound(typ, ast, new_cons = {}) RDL::Logging.debug_error :inference, "Nil found in lbounds... Continuing" next end - if lower_t.is_a?(VarType) && lower_t.to_infer lower_t.add_and_propagate_upper_bound(typ, ast, new_cons) unless lower_t.ubounds.any? { |t, _| t == typ } else @@ -87,7 +88,6 @@ def add_and_propagate_upper_bound(typ, ast, new_cons = {}) def add_and_propagate_lower_bound(typ, ast, new_cons = {}) return if self.equal?(typ) RDL::Logging.log :typecheck, :trace, "#{typ} <= #{self}" - if !@lbounds.any? { |t, a| t == typ } RDL::Logging.log :typecheck, :trace, '@lbounds.any' @lbounds << [typ, ast] @@ -99,7 +99,6 @@ def add_and_propagate_lower_bound(typ, ast, new_cons = {}) RDL::Logging.debug_error :inference, "Nil found in upper_t.lbounds... Continuing" next end - if typ.is_a?(VarType) && !typ.ubounds RDL::Logging.debug_error :inference, "Nil found in ubounds... Continuing" next @@ -112,16 +111,16 @@ def add_and_propagate_lower_bound(typ, ast, new_cons = {}) if typ.is_a?(VarType) && !typ.ubounds.any? { |t, _| t == upper_t } new_cons[typ] = new_cons[typ] ? new_cons[typ] | [[:upper, upper_t, ast]] : [[:upper, upper_t, ast]] end - RDL::Logging.log :typecheck, :trace, "about to check #{typ} <= #{upper_t} with".colorize(:green) + #RDL::Logging.log :typecheck, :trace, "about to check #{typ} <= #{upper_t} with".colorize(:green) - RDL::Util.each_leq_constraints(new_cons) { |a, b| RDL::Logging.log(:typecheck, :trace, "#{a} <= #{b}") } + #RDL::Util.each_leq_constraints(new_cons) { |a, b| RDL::Logging.log(:typecheck, :trace, "#{a} <= #{b}") } unless RDL::Type::Type.leq(typ, upper_t, {}, false, ast: ast, no_constraint: true, propagate: true, new_cons: new_cons) d1 = ast.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [typ.to_s], ast.loc.expression).render.join("\n") d2 = a.nil? ? "" : (Diagnostic.new :error, :infer_constraint_error, [upper_t.to_s], a.loc.expression).render.join("\n") raise RDL::Typecheck::StaticTypeError, ("Inconsistent type constraint #{typ} <= #{upper_t} generated during inference.\n #{d1}\n #{d2}") end - RDL::Logging.log :typecheck, :trace, "Checked #{typ} <= #{upper_t}".colorize(:green) + #RDL::Logging.log :typecheck, :trace, "Checked #{typ} <= #{upper_t}".colorize(:green) end } end @@ -175,7 +174,7 @@ def add_method_use(klass, meth) end def ==(other) - return false if other.nil? + return false if other.class != self.class other = other.canonical return (other.instance_of? self.class) && other.to_s == to_s#(other.name.to_s == @name.to_s) end diff --git a/lib/rdl/types/vararg.rb b/lib/rdl/types/vararg.rb index 6b8bd109..7fbcdfa7 100644 --- a/lib/rdl/types/vararg.rb +++ b/lib/rdl/types/vararg.rb @@ -25,6 +25,10 @@ def initialize(type) super() end + def self.clear_cache! + @@cache = {} + end + # def solution # VarargType.new @type.solution # end diff --git a/lib/rdl/util.rb b/lib/rdl/util.rb index 7426f7c8..1bce2f1d 100644 --- a/lib/rdl/util.rb +++ b/lib/rdl/util.rb @@ -57,6 +57,28 @@ def self.each_leq_constraints(cons) nil end + def self.count_num_lines(klass, meth) + ast = RDL::Typecheck.get_ast(klass, meth) + return nil if ast.nil? + source = ast.loc.expression.source + + lines = source.lines.delete_if { |line| line.empty? || line.strip.empty? || line.strip.start_with?("#") } + num_lines_to_subtract = 0 + in_comment = false + lines.each { |line| + if line.start_with? "=begin" + in_comment = true + num_lines_to_subtract += 1 + elsif line.start_with? "=end" + in_comment = false + num_lines_to_subtract += 1 + elsif in_comment + num_lines_to_subtract += 1 + end + } + return lines.size - num_lines_to_subtract + end + def self.puts_constraints(cons) each_leq_constraints(cons) { |a, b| puts "#{a} <= #{b}" } end diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 7d0b45cd..96807d69 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -460,6 +460,11 @@ def orig_type(klass=self, meth, type, wrap: nil, typecheck: nil) $orig_types = true klass, meth, type = RDL::Wrap.process_type_args(self, klass, meth, type) RDL::Globals.info.add(klass, meth, :orig_type, type) + if RDL::Util.method_defined?(klass, meth) #|| meth == :initialize + RDL::Globals.info.set(klass, meth, :source_location, RDL::Util.to_class(klass).instance_method(meth).source_location) + end + $orig_type_list = [] unless $orig_type_list + $orig_type_list << [klass, meth] end def orig_var_type(klass=self, var, type) @@ -581,7 +586,6 @@ def get_type(klass, meth) end def create_binding_meth(klass, meth) - puts "CREATING BINDING METH FOR #{klass} AND #{meth}" require 'method_source' klass = klass.to_s meth = meth.to_sym @@ -602,7 +606,6 @@ def create_binding_meth(klass, meth) } vals_hash = "{ " + arg_vals.join(",") + " }" meth_string = "def RDL.#{new_meth_name} #{args_string} \n #{vals_hash} \n end" - puts "ABOUT TO EVALUATE #{meth_string}" RDL.class_eval meth_string end end @@ -851,43 +854,77 @@ def self.do_typecheck(sym) nil end - def self.do_infer(sym, render_report: true) + def self.do_infer(sym, render_report: true, num_times: 1) return unless RDL::Globals.to_infer[sym] - - RDL::Config.instance.use_unknown_types = true - $stn = 0 - num_casts = 0 - time = Time.now - - RDL::Globals.to_infer[sym].each { |klass, meth| - begin - RDL::Typecheck.infer klass, meth - num_casts += RDL::Typecheck.get_num_casts if RDL::Typecheck.get_num_casts - rescue Exception => e - if RDL::Config.instance.continue_on_errors - RDL::Logging.log :inference, :debug_error, "Error: #{e}; recording %dyn" + raise "Expected num_times to be positive int, got #{num_times}." unless num_times > 0 + run_times = [] + + $collecting_time_data = num_times > 1 + num_times.times { |run_count| + RDL::Config.instance.use_unknown_types = true + $stn = 0 + num_casts = 0 + RDL::Heuristic.empty_cache! + + (RDL::Globals.constrained_types + RDL::Globals.types_to_delete).each { |klass, name| + RDL::Globals.info.remove(klass, name, :type) + } + RDL::Globals.constrained_types = [] + RDL::Globals.types_to_delete = [] + + time = Time.now + + RDL::Globals.to_infer[sym].each { |klass, meth| + begin + RDL::Typecheck.infer klass, meth + num_casts += RDL::Typecheck.get_num_casts if RDL::Typecheck.get_num_casts + rescue Exception => e + if RDL::Config.instance.continue_on_errors + RDL::Logging.log :inference, :debug_error, "Error: #{e}; recording %dyn" # RDL::Globals.info.set(klass, meth, :type, [RDL::Globals.types[:dyn]]) - else - raise e + else + raise e + end end - end - } + } - RDL::Globals.to_infer[sym] = Set.new - #RDL::Heuristic.visualize_bert(:ret) + #RDL::Heuristic.visualize_bert(:ret) - RDL::Typecheck.resolve_constraints + RDL::Typecheck.resolve_constraints - report = RDL::Typecheck.extract_solutions + report = RDL::Typecheck.extract_solutions - report.to_csv 'infer_data_new.csv' if render_report - report.to_sorbet 'infer_data.rbi' if render_report + time = Time.now - time + run_times << time - time = Time.now - time + if run_count == (num_times - 1) + report.to_csv 'infer_data_new.csv' if render_report + report.to_sorbet 'infer_data.rbi' if render_report - RDL::Logging.log :inference, :info, "Total time taken: #{time}." - RDL::Logging.log :inference, :info, "Total number of type casts used: #{num_casts}." - RDL::Logging.log :inference, :info, "Total amount of time spent on stn: #{$stn}." + RDL::Logging.log :inference, :info, "Total number of type casts used: #{num_casts}." + nl = RDL::Typecheck.get_num_lines + RDL::Logging.log :inference, :info, "Analyized #{nl.size} methods comprising #{nl.values.sum} lines of code." + + failed_twin_sols = RDL::Typecheck.failed_twin_sol_cache.values.map { |ts| ts.size }.sum + + if num_times == 1 + RDL::Logging.log :inference, :info, "Total time taken: #{time}." + RDL::Logging.log :inference, :info, "Total amount of time spent on stn: #{$stn}." + RDL::Logging.log :inference, :info, "Total # rejected twin network solutions: #{failed_twin_sols}" + else + sorted = run_times.sort + median_proc = Proc.new { |sorted_arr| (sorted_arr[(sorted_arr.length - 1) / 2] + sorted_arr[sorted_arr.length / 2]) / 2.0 } + median = median_proc.call sorted + q1 = median_proc.call sorted[0..(sorted.length-1)/2] + q3 = median_proc.call sorted[sorted.length/2..-1] + RDL::Logging.log :inference, :info, "Total time taken across #{num_times} runs: #{run_times.sum}." + RDL::Logging.log :inference, :info, "Median time across #{num_times} runs: #{median}." + RDL::Logging.log :inference, :info, "SIQR across #{num_times} runs: #{(q3 - q1)/2.0}." + end + RDL::Heuristic.report_nums_above_cutoff + end + } + RDL::Globals.to_infer[sym] = Set.new end def self.load_sequel_schema(db) diff --git a/lib/types/core/object.rb b/lib/types/core/object.rb index 38117c91..485ef709 100644 --- a/lib/types/core/object.rb +++ b/lib/types/core/object.rb @@ -26,7 +26,8 @@ RDL.type :Object, :<=>, '(%any other) -> Integer or nil', wrap: false RDL.type :Object, :===, '(%any other) -> %bool', wrap: false RDL.type :Object, :==, '(%any other) -> %bool', wrap: false -RDL.type :Object, :=~, '(%any other) -> nil', wrap: false +## Deprecated +#RDL.type :Object, :=~, '(%any other) -> nil', wrap: false RDL.type :Object, :class, '() -> Class', wrap: false RDL.type :Object, :clone, '() -> self', wrap: false # RDL.type :Object, :define_singleton_method, '(XXXX : *XXXX)') # TODO diff --git a/rdl.gemspec b/rdl.gemspec index a219e9d2..e5f1aacb 100644 --- a/rdl.gemspec +++ b/rdl.gemspec @@ -14,7 +14,7 @@ contracts or statically checked. EOF s.authors = ['Jeffrey S. Foster', 'Brianna M. Ren', 'T. Stephen Strickland', 'Alexander T. Yu', 'Milod Kazerounian', 'Sankha Narayan Guria'] s.email = ['rdl-users@googlegroups.com'] - s.files = `git ls-files`.split($/) + s.files = `git ls-files`.split($/) + ['./lib/rdl/eval_deepsim.rb'] s.executables << 'rdl_query' s.homepage = 'https://github.com/tupl-tufts/rdl' s.license = 'BSD-3-Clause' From ffa30038a6f51d63df5e48e68b09b898b8871f13 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 9 Apr 2021 12:40:24 -0400 Subject: [PATCH 100/124] get rid of temp file --- lib/rdl/#constraint.rb# | 591 ---------------------------------------- lib/rdl/constraint.rb | 6 +- lib/rdl/wrap.rb | 2 - 3 files changed, 1 insertion(+), 598 deletions(-) delete mode 100644 lib/rdl/#constraint.rb# diff --git a/lib/rdl/#constraint.rb# b/lib/rdl/#constraint.rb# deleted file mode 100644 index 85222eee..00000000 --- a/lib/rdl/#constraint.rb# +++ /dev/null @@ -1,591 +0,0 @@ -require 'csv' - -$use_twin_network = false -$use_heuristics = true - - -class << RDL::Typecheck - ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. - attr_accessor :type_names_map - attr_accessor :type_vars_map - attr_accessor :failed_twin_sol_cache -end - -module RDL::Typecheck - - @type_names_map = Hash.new { |h, k| h[k] = [] }#[] - @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] - @failed_sol_cache = Hash.new { |h, k| h[k] = [] } - @failed_twin_sol_cache = Hash.new { |h, k| h[k] = [] } - - def self.empty_cache! - @type_names_map = Hash.new { |h, k| h[k] = [] }#[] - @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] - @failed_sol_cache = Hash.new { |h, k| h[k] = [] } - @failed_twin_sol_cache = Hash.new { |h, k| h[k] = [] } - end - - def self.resolve_constraints - RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." - RDL::Globals.constrained_types.each { |klass, name| - RDL::Logging.log :inference, :debug, "Resolving constraints from #{RDL::Util.pp_klass_method(klass, name)}" - typ = RDL::Globals.info.get(klass, name, :type) - ## If typ is an Array, then it's an array of method types - ## but for inference, we only use a single method type. - ## Otherwise, it's a single VarType for an instance/class var. - if typ.is_a?(Array) - var_types = name == :initialize ? typ[0].args + [typ[0].block] : typ[0].args + [typ[0].block, typ[0].ret] - else - var_types = [typ] - end - - var_types.each { |var_type| - begin - if var_type.var_type? || var_type.optional_var_type? || var_type.vararg_var_type? - var_type = var_type.type if var_type.optional_var_type? || var_type.vararg_var_type? - var_type.lbounds.each { |lower_t, ast| - RDL::Logging.log :typecheck, :trace, "#{lower_t} <= #{var_type}" - var_type.add_and_propagate_lower_bound(lower_t, ast) - } - var_type.ubounds.each { |upper_t, ast| - var_type.add_and_propagate_upper_bound(upper_t, ast) - } - elsif var_type.fht_var_type? - var_type.elts.values.each { |v| - vt = v.optional_var_type? || v.vararg_var_type? ? v.type : v - vt.lbounds.each { |lower_t, ast| - vt.add_and_propagate_lower_bound(lower_t, ast) - } - vt.ubounds.each { |upper_t, ast| - vt.add_and_propagate_upper_bound(upper_t, ast) - } - } - else - raise "Got unexpected type #{var_type}." - end - rescue => e - raise e unless RDL::Config.instance.continue_on_errors - - RDL::Logging.log :inference, :debug_error, "Caught error when resolving constraints for #{var_type}; skipping..." - end - } - } - end - - def self.extract_var_sol(var, category, add_sol_to_graph = true) - #raise "Expected VarType, got #{var}." unless var.is_a?(RDL::Type::VarType) - return var.canonical unless var.is_a?(RDL::Type::VarType) - if category == :arg - non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } - sol = non_vartype_ubounds.size == 1 ? non_vartype_ubounds[0] : RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical - sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::IntersectionType) ## could be, e.g., nominal type if only one type used to create intersection. - #return sol - elsif category == :ret - non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } - sol = RDL::Type::UnionType.new(*non_vartype_lbounds) - sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. - #return sol - elsif category == :var - if var.lbounds.empty? || (var.lbounds.size == 1 && var.lbounds[0][0] == RDL::Globals.types[:bot]) - ## use upper bounds in this case. - non_vartype_ubounds = var.ubounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } - sol = RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical - #return sol - else - ## use lower bounds - non_vartype_lbounds = var.lbounds.map { |t, ast| t}.reject { |t| t.instance_of?(RDL::Type::VarType) } - sol = RDL::Type::UnionType.new(*non_vartype_lbounds) - sol = sol.drop_vars.canonical if sol.is_a?(RDL::Type::UnionType) ## could be, e.g., nominal type if only one type used to create union. - #return sol#RDL::Type::UnionType.new(*non_vartype_lbounds).canonical - end - else - raise "Unexpected VarType category #{category}." - end - if (sol.is_a?(RDL::Type::UnionType) && !(sol == RDL::Globals.types[:bool])) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) - ## Try each rule. Return first non-nil result. - ## If no non-nil results, return original solution. - ## TODO: check constraints. - heuristics_start_time = Time.now - RDL::Logging.log_header :heuristic, :debug, "Beginning Heuristics..." - - RDL::Heuristic.rules.each { |name, rule| - next if (@counter == 1) && (name == :twin_network) - start_time = Time.now - RDL::Logging.log :heuristic, :debug, "Trying rule `#{name}` for variable #{var}." - typ = rule.call(var) - if typ.is_a?(Array) && (name == :twin_network) - count = 0 - typ.each { |t| - new_cons = {} - count += 1 - begin - t = t.canonical - next if @failed_sol_cache[var].include?(t) - if add_sol_to_graph - var.add_and_propagate_upper_bound(t, nil, new_cons) - var.add_and_propagate_lower_bound(t, nil, new_cons) - end - RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" - puts "Successfully applied twin network! Solution #{t} for #{var}".blue unless $collecting_time_data - @new_constraints = true if !new_cons.empty? - RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? - raise "2. got here with #{var} and #{t}" if t.to_s == "%bot" - @type_vars_map[t] = @type_vars_map[t] | [var] - var.solution_source = "Twin" if (var.solution != t) - return t - rescue RDL::Typecheck::StaticTypeError => e - @failed_sol_cache[var] << t - @failed_twin_sol_cache[var] << t - undo_constraints(new_cons) - end - } - elsif typ.is_a?(RDL::Type::Type) - new_cons = {} - begin - typ = typ.canonical - next if @failed_sol_cache[var].include?(typ) - if add_sol_to_graph - var.add_and_propagate_upper_bound(typ, nil, new_cons) - var.add_and_propagate_lower_bound(typ, nil, new_cons) - end -=begin - new_cons.each { |var, bounds| - bounds.each { |u_or_l, t, _| - puts "1. Added #{u_or_l} bound constraint #{t} of kind #{t.class} to variable #{var}" - puts "It has upper bounds: " - var.ubounds.each { |t, _| puts t } - } - } -=end - RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" - @new_constraints = true if !new_cons.empty? - RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? - raise "3.. got here with #{var} and #{typ} for #{name}" if typ.to_s == "%bot" - @type_vars_map[typ] = @type_vars_map[typ] | [var] - var.solution_source = "Heur: #{name}" if (var.solution != typ) - return typ - #sol = typ - rescue RDL::Typecheck::StaticTypeError => e - RDL::Logging.log :heuristic, :debug_error, "Attempted to apply heuristic rule #{name} solution #{typ} to var #{var}" - RDL::Logging.log :heuristic, :trace, "... but got the following error: #{e}" - @failed_sol_cache[var] << typ - undo_constraints(new_cons) - ## no new constraints in this case so we'll leave it as is - ensure - total_time = Time.now - start_time - RDL::Logging.log :hueristic, :debug, "Heuristic #{name} took #{total_time} to evaluate" - end - else - raise "Unexpected return value #{typ} from heuristic rule #{name}." unless typ.nil? - end - } - - heuristics_total_time = Time.now - heuristics_start_time - RDL::Logging.log_header :heuristic, :debug, "Evaluated heuristics in #{heuristics_total_time}" - end - ## out here, none of the heuristics applied. - ## Try to use `sol` as solution -- there is a chance it will - begin - new_cons = {} - sol = var if sol == RDL::Globals.types[:bot] # just use var itself when result of solution extraction was %bot. - return sol if sol.is_a?(RDL::Type::VarType) ## don't add var type as solution - sol = sol.canonical - raise RDL::Typecheck::StaticTypeError if @failed_sol_cache.include?(sol) - var.add_and_propagate_upper_bound(sol, nil, new_cons) unless (sol == RDL::Globals.types[:nil]) - var.add_and_propagate_lower_bound(sol, nil, new_cons) unless sol.is_a?(RDL::Type::StructuralType) || (sol.is_a?(RDL::Type::IntersectionType) && sol.types.any? { |t| t.is_a?(RDL::Type::StructuralType) } )# || (sol == RDL::Globals.types[:object]) || (sol == RDL::Globals.types[:top]) -=begin - new_cons.each { |var, bounds| - bounds.each { |u_or_l, t, _| - puts "2. Added #{u_or_l} bound constraint #{t} to variable #{var}" - } - } -=end - @new_constraints = true if !new_cons.empty? - RDL::Logging.log :inference, :trace, "New Constraints branch B" if !new_cons.empty? - #raise "Adding Solution Array or Number to variable #{var}".red if (sol.to_s == "(Array or Number)") && var.to_s != "{ { DashboardSection# var: @row }#[] call_ret: ret }" - if sol.is_a?(RDL::Type::GenericType) - new_params = sol.params.map { |p| if p.is_a?(RDL::Type::VarType) && (!p.to_infer || (p == var)) then p else extract_var_sol(p, category) end } - sol = RDL::Type::GenericType.new(sol.base, *new_params) - elsif sol.is_a?(RDL::Type::TupleType) - new_params = sol.params.map { |t| extract_var_sol(t, category) } - sol = RDL::Type::TupleType.new(*new_params) - end - rescue RDL::Typecheck::StaticTypeError => e - RDL::Logging.log :inference, :debug_error, "Attempted to apply solution #{sol} for var #{var}" - RDL::Logging.log :inference, :trace, "... but got the following error: #{e}" - @failed_sol_cache[var] << sol - undo_constraints(new_cons) - ## no new constraints in this case so we'll leave it as is - sol = var - end - if (sol.is_a?(RDL::Type::NominalType) || sol.is_a?(RDL::Type::GenericType) || sol.is_a?(RDL::Type::TupleType) || sol.is_a?(RDL::Type::FiniteHashType) || (sol == RDL::Globals.types[:bool])) && !(sol == RDL::Globals.types[:object]) - raise "1. got here with #{var} and #{sol}" if sol.to_s == "%bot" - name = var.base_name#(var.category == :ret) ? var.meth : var.name - @type_names_map[sol] = @type_names_map[sol] | [name.to_s] - @type_vars_map[sol] = @type_vars_map[sol] | [var] - end - var.solution_source = "Constraints" if (var.solution != sol) - return sol - end - - # [+ cons +] is Hash of constraints to be undone. - def self.undo_constraints(cons) - cons.each_key { |var_type| - cons[var_type].each { |upper_or_lower, bound_t, ast| - if upper_or_lower == :upper - var_type.ubounds.delete([bound_t, ast]) - elsif upper_or_lower == :lower - var_type.lbounds.delete([bound_t, ast]) - end - } - } - end - - def self.extract_meth_sol(tmeth) - raise "Expected MethodType, got #{tmeth}." unless tmeth.is_a?(RDL::Type::MethodType) - ## ARG SOLUTIONS - arg_sols = tmeth.args.map { |a| - if a.optional_var_type? - soln = RDL::Type::OptionalType.new(extract_var_sol(a.type, :arg)) - elsif a.fht_var_type? - hash_sol = a.elts.transform_values { |v| - if v.is_a?(RDL::Type::OptionalType) - RDL::Type::OptionalType.new(extract_var_sol(v.type, :arg)) - else - extract_var_sol(v, :arg) - end - } - soln = RDL::Type::FiniteHashType.new(hash_sol, nil) - else - soln = extract_var_sol(a, :arg) - end - - a.solution = soln - soln - } - - ## BLOCK SOLUTION - if tmeth.block && !tmeth.block.ubounds.empty? - non_vartype_ubounds = tmeth.block.ubounds.map { |t, ast| t.canonical }.reject { |t| t.is_a?(RDL::Type::VarType) } - non_vartype_ubounds.reject! { |t| t.is_a?(RDL::Type::StructuralType) }#&& (t.methods.size == 1) && (t.methods.has_key?(:to_proc) || t.methods.has_key?(:call)) } - if non_vartype_ubounds.size == 0 - block_sol = tmeth.block - elsif non_vartype_ubounds.size > 1 - block_sols = [] - inter = RDL::Type::IntersectionType.new(*non_vartype_ubounds).canonical - typs = inter.is_a?(RDL::Type::IntersectionType) ? inter.types : [inter] - typs.each { |m| - #raise "Expected block type to be a MethodType, got #{m}." unless m.is_a?(RDL::Type::MethodType) - block_sols << (m.is_a?(RDL::Type::MethodType) ? RDL::Type::MethodType.new(*extract_meth_sol(m)) : m) - } - block_sol = RDL::Type::IntersectionType.new(*block_sols).canonical - else - if non_vartype_ubounds[0].is_a?(RDL::Type::NominalType) && (non_vartype_ubounds[0].to_s == "Proc") - block_sol = non_vartype_ubounds[0] - elsif !non_vartype_ubounds[0].is_a?(RDL::Type::MethodType) - block_sol = tmeth.block - else - block_sol = RDL::Type::MethodType.new(*extract_meth_sol(non_vartype_ubounds[0])) - end - end - - tmeth.block.solution = block_sol - else - block_sol = nil - end - - ## RET SOLUTION - if tmeth.ret.to_s == "self" - ret_sol = tmeth.ret - else - ret_sol = tmeth.ret.is_a?(RDL::Type::VarType) ? extract_var_sol(tmeth.ret, :ret) : tmeth.ret - end - - tmeth.ret.solution = ret_sol - - return [arg_sols, block_sol, ret_sol] - end - - # Compares a single inferred type to original type. - # Returns "E" (exact match), "P" (match up to parameter), "T" (no match but got a type), or "N" (no type). - # [+ inf_type +] is the inferred type - # [+ orig_type +] is the original, gold standard type - def self.compare_single_type(inf_type, orig_type) - inf_type = inf_type.type if inf_type.optional? || inf_type.vararg? - orig_type = orig_type.type if orig_type.optional? || orig_type.vararg? - inf_type = inf_type.canonical - orig_type = orig_type.canonical - if inf_type.to_s == orig_type.to_s - return "E" - elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && abbreviated_class?(orig_type.to_s, inf_type.to_s) - ## needed for diferring scopes, e.g. TZInfo::Timestamp & Timestamp - return "E" - elsif inf_type.is_a?(RDL::Type::NominalType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } - return "E" - elsif inf_type.is_a?(RDL::Type::GenericType) && (orig_type == RDL::Globals.types[:object]) - return "E" - elsif [inf_type.to_s, orig_type.to_s].all? { |t| ["String", "Symbol", "(String or Symbol)", "(Symbol or String)"].include?(t) } - return "E" - elsif inf_type.is_a?(RDL::Type::UnionType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.types.all? { |t| t.is_a?(RDL::Type::NominalType) && t.klass.ancestors.any? { |anc| (anc.to_s == orig_type.to_s) || abbreviated_class?(orig_type.to_s, anc.to_s) } } - return "E" - elsif !inf_type.is_a?(RDL::Type::VarType) && (orig_type.is_a?(RDL::Type::TopType)) - return "E" - elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.params[0] == orig_type.params[0] && inf_type.array_type? && orig_type.array_type? - return "E" - elsif inf_type.is_a?(RDL::Type::StructuralType) && orig_type.is_a?(RDL::Type::StructuralType) && (inf_type.methods.map { |m, _| m } == orig_type.methods.map { |m, _| m}) - return "E" - elsif inf_type.is_a?(RDL::Type::SingletonType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.val.class.to_s == orig_type.name - return "E" - elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::GenericType) && inf_type.base.to_s == orig_type.base.to_s - return "P" - elsif inf_type.is_a?(RDL::Type::GenericType) && orig_type.is_a?(RDL::Type::NominalType) && inf_type.base.to_s == orig_type.to_s - return "P" - elsif orig_type.is_a?(RDL::Type::GenericType) && inf_type.is_a?(RDL::Type::NominalType) && orig_type.base.to_s == inf_type.to_s - return "P" - elsif inf_type.array_type? && orig_type.array_type? - return "P" - elsif inf_type.hash_type? && orig_type.hash_type? - return "P" - elsif !inf_type.is_a?(RDL::Type::VarType) - if inf_type.is_a?(RDL::Type::StructuralType) || (inf_type.is_a?(RDL::Type::IntersectionType) && inf_type.types.any? { |t| t.is_a?(RDL::Type::StructuralType) }) - return "TS" - else - return "T" - end - else - return "N" - end - end - - def self.abbreviated_class?(abbrev, original) - abbrev = abbrev.to_s - original = original.to_s - original.end_with?("::" + abbrev) - end - - - def self.make_extraction_report(typ_sols) - report = RDL::Reporting::InferenceReport.new - twin_csv = CSV.open("#{if !$use_twin_network then 'no_' end}twin_#{if $use_heuristics then 'heur_' end}_#{RDL::Heuristic.simscore_cutoff if $use_twin_network}_#{RDL::Heuristic.get_top_n if $use_twin_network}_infer_data.csv", 'wb') - twin_csv << ["Class", "Method Name", "Arg/Ret/Var", "Variable Name", - "Inferred Type", "Original Type", "Exact (E) / Up to Parameter (P) / Got Type (T) / None (N)", "Solution Source", "Source Code"] - compares = Hash.new 0 - ## Twin CSV format: [Class, Name, ] - #return unless $orig_types - - # complete_types = [] - # incomplete_types = [] - - # CSV.open("infer_data.csv", "wb") { |csv| - # csv << ["Class", "Method", "Inferred Type", "Original Type", "Source Code", "Comments"] - # } - - correct_types = 0 - meth_types = 0 - ret_types = 0 - arg_types = 0 - var_types = 0 - diff_struct_types = 0 - typ_sols.each_pair { |km, typ| - klass, meth = km - orig_typ = RDL::Globals.info.get(klass, meth, :orig_type) - next if orig_typ.nil? || typ.solution.nil? - if orig_typ.is_a?(Array) - raise "expected just one original type for #{klass}##{meth}" unless orig_typ.size == 1 - orig_typ = orig_typ[0] - end - if orig_typ.is_a?(RDL::Type::MethodType) - meth_types += 1 - ast = RDL::Typecheck.get_ast(klass, meth) - code = ast.loc.expression.source - orig_typ.args.each_with_index { |orig_arg_typ, i | - inf_arg_type = typ.solution.args[i] - comp = inf_arg_type.nil? ? "N" : compare_single_type(inf_arg_type, orig_arg_typ) - compares[comp] += 1 - if typ.args[i].nil? - name = nil - elsif (typ.args[i].optional? || typ.args[i].vararg?) - name = typ.args[i].type.base_name - sol_source = typ.args[i].type.solution_source - elsif typ.args[i].is_a?(RDL::Type::FiniteHashType) - name = typ.args[i].to_s - sol_source = "TODO: handle FHTs" - else - name = typ.args[i].base_name - sol_source = typ.args[i].solution_source - end - twin_csv << [klass, meth, "Arg", name, inf_arg_type.to_s, orig_arg_typ.to_s, comp, sol_source, code] - arg_types +=1 - } - unless (orig_typ.ret == RDL::Globals.types[:bot]) ## bot type is given to any returns for which we don't have a type - ret_types += 1 - inf_ret_type = typ.solution.ret - sol_source = typ.ret.solution_source - comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) - compares[comp] += 1 - twin_csv << [klass, meth, "Ret", "", inf_ret_type.to_s, orig_typ.ret.to_s, comp, sol_source, code] - end - else - comp = typ.solution.nil? ? "N" : compare_single_type(typ.solution, orig_typ) - compares[comp] += 1 - sol_source = typ.solution_source - twin_csv << [klass, meth, "Var", meth, typ.solution.to_s, orig_typ.to_s, comp, sol_source, ""] - var_types += 1 - end - - if !meth.to_s.include?("@") && !meth.to_s.include?("$")#orig_typ.is_a?(RDL::Type::MethodType) - ast = RDL::Typecheck.get_ast(klass, meth) - code = ast.loc.expression.source - # if RDL::Util.has_singleton_marker(klass) - # comment = RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)).method(meth).comment - # else - # comment = RDL::Util.to_class(klass).instance_method(meth).comment - # end - # csv << [klass, meth, typ, orig_typ, code] #, comment - - report[klass] << { klass: klass, method_name: meth, type: typ, - orig_type: orig_typ, source_code: code } - - - # if typ.include?("XXX") - # incomplete_types << [klass, meth, typ, orig_typ, code, comment] - # else - # complete_types << [klass, meth, typ, orig_typ, code, comment] - # end - end - } - - RDL::Logging.log_header :inference, :info, "Extraction Complete" - RDL::Logging.log :inference, :info, "SIMILARITY SCORE CUTOFF = #{RDL::Heuristic.simscore_cutoff}" - RDL::Logging.log :inference, :info, "USING TOP N=#{RDL::Heuristic.get_top_n} TYPES" - twin_csv << ["Total # E:", compares["E"]] - RDL::Logging.log :inference, :info, "Total exactly correct (E): #{compares["E"]}" - twin_csv << ["Total # P:", compares["P"]] - RDL::Logging.log :inference, :info, "Total correct up to parameter (P): #{compares["P"]}" - twin_csv << ["Total # T:", compares["T"]] - RDL::Logging.log :inference, :info, "Total not correct but got type for (T): #{compares["T"] + compares["TS"]}" - twin_csv << ["Total # TS:", compares["TS"]] - RDL::Logging.log :inference, :info, "Total number of T's that were structural types: #{compares["TS"]}" - twin_csv << ["Total # N:", compares["N"]] - RDL::Logging.log :inference, :info, "Total no type for (N): #{compares["N"]}" - #twin_csv << ["Total # method types:", meth_types] - #RDL::Logging.log :inference, :info, "Total # method types: #{meth_types}" - twin_csv << ["Total # return types:", ret_types] - RDL::Logging.log :inference, :info, "Total # return types: #{ret_types}" - twin_csv << ["Total # arg types:", arg_types] - RDL::Logging.log :inference, :info, "Total # argument types: #{arg_types}" - twin_csv << ["Total # var types:", var_types] - RDL::Logging.log :inference, :info, "Total # variable types: #{var_types}" - twin_csv << ["Total # individual types:", var_types + meth_types + arg_types] - RDL::Logging.log :inference, :info, "Total # individual types: #{ret_types + arg_types + var_types}" - rescue => e - RDL::Logging.log :inference, :error, "Report Generation Error" - RDL::Logging.log :inference, :debug_error, "... got #{e}" - puts "Got: #{e}" - raise e unless RDL::Config.instance.continue_on_errors - ensure - return report - end - - def self.extract_solutions() - ## Go through once to come up with solution for all var types. - RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" - if $use_twin_network - uri = URI "http://127.0.0.1:5000/" - $http = Net::HTTP.new(uri.hostname, uri.port) - $http.start - end - @counter = 0; - used_twin = false - typ_sols = {} - loop do - @counter += 1 - @new_constraints = false - typ_sols = {} - - RDL::Logging.log :inference, :info, "[#{@counter}] Running solution extraction..." - RDL::Globals.constrained_types.each { |klass, name| - begin - RDL::Logging.log :inference, :debug, "Extracting #{RDL::Util.pp_klass_method(klass, name)}" - RDL::Type::VarType.no_print_XXX! - typ = RDL::Globals.info.get(klass, name, :type) - if typ.is_a?(Array) - raise "Expected just one method type for #{klass}#{name}." unless typ.size == 1 - tmeth = typ[0] - - arg_sols, block_sol, ret_sol = extract_meth_sol(tmeth) - - block_string = block_sol ? " { #{block_sol} }" : nil - RDL::Logging.log :inference, :trace, "Extracted solution for #{klass}\##{name} is (#{arg_sols.join(',')})#{block_string} -> #{ret_sol}" - - #meth_sol = RDL::Type::MethodType.new arg_sols, block_sol, ret_sol - - typ_sols[[klass.to_s, name.to_sym]] = tmeth - elsif name.to_s == "splat_param" - else - ## Instance/Class (also some times splat parameter) variables: - ## There is no clear answer as to what to do in this case. - ## Just need to pick something in between bounds (inclusive). - ## For now, plan is to just use lower bound when it's not empty/%bot, - ## otherwise use upper bound. - ## Can improve later if desired. - var_sol = extract_var_sol(typ, :var) - typ.solution = var_sol - RDL::Logging.log :inference, :trace, "Extracted solution for #{klass} variable #{name} is #{var_sol}." - typ_sols[[klass.to_s, name.to_sym]] = typ - end - rescue => e - puts "GOT HERE WITH #{e}" - RDL::Logging.log :inference, :debug_error, "Error while exctracting solution for #{RDL::Util.pp_klass_method(klass, name)}: #{e}; continuing..." - raise e unless RDL::Config.instance.continue_on_errors - end - } - ## Trying: out here on last run, now try applying twin network to each pair of var types - if false #$use_twin_network && !@new_constraints && !used_twin - constrained_vars = [] - RDL::Globals.constrained_types.each { |klass, name| - typ = RDL::Globals.info.get(klass, name, :type) - ## First, collect *each individual VarType* into constrained_vars array - if typ.is_a?(Array) - typ[0].args.each { |a| - case a - when RDL::Type::VarType - constrained_vars << a - when RDL::Type::OptionalType, RDL::Type::VarargType - constrained_vars << a.type - when RDL::Type::FiniteHashType - a.elts.values.each { |v| - vt = v.optional_var_type? || v.vararg_var_type? ? v.type : v - constrained_vars << vt - } - else - raise "Expected type variable, got #{a}." - end - } - #constrained_vars = constrained_vars + typ[0].args - constrained_vars << typ[0].ret - elsif name.to_s == "splat_param" - else - constrained_vars << typ - end - } - pairs_enum = constrained_vars.combination(2) - RDL::Heuristic.twin_network_constraints(pairs_enum) - used_twin = true - end - break if !@new_constraints - end - rescue => e - puts "RECEIVED ERROR #{e} from #{e.backtrace}" - ensure - $http.finish if $http - puts "MAKING EXTRACTION REPORT" - return make_extraction_report(typ_sols) - end - - def self.set_new_constraints - @new_constraints = true - end - - -end diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index b22ba633..3dc43229 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,14 +1,13 @@ require 'csv' $use_twin_network = true -$use_heuristics = false +$use_heuristics = true class << RDL::Typecheck ## Hash>. A Hash mapping RDL types to a list of names of variables that have that type as a solution. attr_accessor :type_names_map attr_accessor :type_vars_map - attr_accessor :failed_twin_sol_cache end module RDL::Typecheck @@ -16,13 +15,11 @@ module RDL::Typecheck @type_names_map = Hash.new { |h, k| h[k] = [] }#[] @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] @failed_sol_cache = Hash.new { |h, k| h[k] = [] } - @failed_twin_sol_cache = Hash.new { |h, k| h[k] = [] } def self.empty_cache! @type_names_map = Hash.new { |h, k| h[k] = [] }#[] @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] @failed_sol_cache = Hash.new { |h, k| h[k] = [] } - @failed_twin_sol_cache = Hash.new { |h, k| h[k] = [] } end def self.resolve_constraints @@ -135,7 +132,6 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) return t rescue RDL::Typecheck::StaticTypeError => e @failed_sol_cache[var] << t - @failed_twin_sol_cache[var] << t undo_constraints(new_cons) end } diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 96807d69..0a08692b 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -905,12 +905,10 @@ def self.do_infer(sym, render_report: true, num_times: 1) nl = RDL::Typecheck.get_num_lines RDL::Logging.log :inference, :info, "Analyized #{nl.size} methods comprising #{nl.values.sum} lines of code." - failed_twin_sols = RDL::Typecheck.failed_twin_sol_cache.values.map { |ts| ts.size }.sum if num_times == 1 RDL::Logging.log :inference, :info, "Total time taken: #{time}." RDL::Logging.log :inference, :info, "Total amount of time spent on stn: #{$stn}." - RDL::Logging.log :inference, :info, "Total # rejected twin network solutions: #{failed_twin_sols}" else sorted = run_times.sort median_proc = Proc.new { |sorted_arr| (sorted_arr[(sorted_arr.length - 1) / 2] + sorted_arr[sorted_arr.length / 2]) / 2.0 } From f32d52923e1ff41152a3e9a87ace64aa0061300d Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 19 Apr 2021 08:59:36 -0400 Subject: [PATCH 101/124] latest progress --- lib/rdl/constraint.rb | 92 +++++++++++++++++++++++++++++++++++++++++-- lib/rdl/heuristics.rb | 1 + lib/rdl/typecheck.rb | 10 +++++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 3dc43229..da0a2e10 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,7 +1,7 @@ require 'csv' $use_twin_network = true -$use_heuristics = true +$use_heuristics = false class << RDL::Typecheck @@ -12,14 +12,49 @@ class << RDL::Typecheck module RDL::Typecheck + RUBY_TYPES = ["Abbrev", "Array", "Base64", "BasicObject", "Benchmark", "BigDecimal", + "BigMath", "Class", "Complex", "Coverage", "CSV", "Date", "Dir", "Encoding", + "Enumerable", "Enumerator", "Exception", "File", "FileUtils", "Float", "Gem", + "Hash", "Integer", "IO", "Kernel", "Marshal", "MatchData", "Math", "Module", + "NilClass", "Numeric", "Object", "Pathname", "PrettyPrint", "Proc", "Process", + "Random", "Range", "Rational", "Regexp", "Set", "String", "StringScanner", "Symbol", + "Time", "URI", "YAML", "FalseClass", "TrueClass", "Number"] + + RAILS_TYPES = ["AbstractController", "AbstractController::Translation", "ActionController", + "ActionController::Base", "ActionController::Instrumentation", "ActionController::Metal", + "ActionController::MimeResponds", "ActionController::Parameters", + "ActionController::StrongParameters", "ActionDispatch", 'ActionDispatch::Flash::FlashHash', + "ActionMailer", 'ActionMailer::Base', 'ActionMailer::MessageDelivery', + 'ActionView::Helpers::SanitizeHelper', 'ActionView::Helpers::UrlHelper', 'ActiveModel::Errors', + 'ActiveModel::Validations', 'ActiveRecord::Associations::CollectionProxy', + 'ActiveRecord::Associations::ClassMethods', 'ActiveRecord::Base', 'ActiveRecord::FinderMethods', + "ActiveRecord::ModelSchema::ClassMethods", 'ActiveRecord::Relation', 'ActiveRecord::Validations', + 'ActiveSupport::Logger', 'ActiveSupport::TaggedLogging', 'ActiveSupport::TimeWithZone', + 'ActiveSupport::TimeZone'] + + @rails_types = [] + @type_names_map = Hash.new { |h, k| h[k] = [] }#[] @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] @failed_sol_cache = Hash.new { |h, k| h[k] = [] } + @common_matches_by_twin = 0 + @rare_matches_by_twin = 0 + @common_by_twin = 0 + @rare_by_twin = 0 + @tot_orig_common = 0 + @tot_orig_rare = 0 + def self.empty_cache! @type_names_map = Hash.new { |h, k| h[k] = [] }#[] @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] @failed_sol_cache = Hash.new { |h, k| h[k] = [] } + @common_matches_by_twin = 0 + @rare_matches_by_twin = 0 + @common_by_twin = 0 + @rare_by_twin = 0 + @tot_orig_common = 0 + @tot_orig_rare = 0 end def self.resolve_constraints @@ -381,8 +416,11 @@ def self.make_extraction_report(typ_sols) ret_types = 0 arg_types = 0 var_types = 0 + num_lines = 0 + num_casts = 0 diff_struct_types = 0 typ_sols.each_pair { |km, typ| + compared = false klass, meth = km orig_typ = RDL::Globals.info.get(klass, meth, :orig_type) next if orig_typ.nil? || typ.solution.nil? @@ -391,12 +429,12 @@ def self.make_extraction_report(typ_sols) orig_typ = orig_typ[0] end if orig_typ.is_a?(RDL::Type::MethodType) - meth_types += 1 ast = RDL::Typecheck.get_ast(klass, meth) code = ast.loc.expression.source orig_typ.args.each_with_index { |orig_arg_typ, i | inf_arg_type = typ.solution.args[i] comp = inf_arg_type.nil? ? "N" : compare_single_type(inf_arg_type, orig_arg_typ) + compared = true compares[comp] += 1 if typ.args[i].nil? name = nil @@ -412,19 +450,28 @@ def self.make_extraction_report(typ_sols) end twin_csv << [klass, meth, "Arg", name, inf_arg_type.to_s, orig_arg_typ.to_s, comp, sol_source, code] arg_types +=1 + update_type_counts(inf_arg_type, orig_arg_typ, comp, sol_source) } unless (orig_typ.ret == RDL::Globals.types[:bot]) ## bot type is given to any returns for which we don't have a type ret_types += 1 inf_ret_type = typ.solution.ret sol_source = typ.ret.solution_source comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) + compared = true + update_type_counts(inf_ret_type, orig_typ, comp, sol_source) compares[comp] += 1 twin_csv << [klass, meth, "Ret", "", inf_ret_type.to_s, orig_typ.ret.to_s, comp, sol_source, code] end + if compared + meth_types += 1 + num_lines += RDL::Util.count_num_lines(klass, meth) + num_casts += RDL::Typecheck.get_meth_casts(klass, meth) + end else comp = typ.solution.nil? ? "N" : compare_single_type(typ.solution, orig_typ) compares[comp] += 1 sol_source = typ.solution_source + update_type_counts(typ.solution, orig_typ, comp, sol_source) twin_csv << [klass, meth, "Var", meth, typ.solution.to_s, orig_typ.to_s, comp, sol_source, ""] var_types += 1 end @@ -465,7 +512,8 @@ def self.make_extraction_report(typ_sols) twin_csv << ["Total # N:", compares["N"]] RDL::Logging.log :inference, :info, "Total no type for (N): #{compares["N"]}" #twin_csv << ["Total # method types:", meth_types] - #RDL::Logging.log :inference, :info, "Total # method types: #{meth_types}" + RDL::Logging.log :inference, :info, "Total # compared method types: #{meth_types} comprising #{num_lines} lines" + RDL::Logging.log :inference, :info, "Total # casts within compared method: #{num_casts}" twin_csv << ["Total # return types:", ret_types] RDL::Logging.log :inference, :info, "Total # return types: #{ret_types}" twin_csv << ["Total # arg types:", arg_types] @@ -474,6 +522,12 @@ def self.make_extraction_report(typ_sols) RDL::Logging.log :inference, :info, "Total # variable types: #{var_types}" twin_csv << ["Total # individual types:", var_types + meth_types + arg_types] RDL::Logging.log :inference, :info, "Total # individual types: #{ret_types + arg_types + var_types}" + RDL::Logging.log :inference, :info, "Total # common types guessed by twin: #{@common_by_twin}." + RDL::Logging.log :inference, :info, "Total # common type *matches* guessed by twin: #{@common_matches_by_twin}." + RDL::Logging.log :inference, :info, "Total # rare types guessed by twin: #{@rare_by_twin}" + RDL::Logging.log :inference, :info, "Total # rare type *matches* guessed by twin: #{@rare_matches_by_twin}" + RDL::Logging.log :inference, :info, "Total # common type originals: #{@tot_orig_common}" + RDL::Logging.log :inference, :info, "Total # rare type originals: #{@tot_orig_rare}" rescue => e RDL::Logging.log :inference, :error, "Report Generation Error" RDL::Logging.log :inference, :debug_error, "... got #{e}" @@ -483,6 +537,38 @@ def self.make_extraction_report(typ_sols) return report end + def self.update_type_counts(inf_type, orig_type, comp, sol_source) + if common_type?(orig_type) + @tot_orig_common += 1 + else + @tot_orig_rare += 1 + end + return unless (sol_source == "Twin") ## not interested in cases that constraints/heuristics got + if common_type?(inf_type) + @common_by_twin += 1 + if ["E", "P"].include?(comp) + @common_matches_by_twin += 1 + end + else + puts "Rare type #{inf_type}" + ## rare type + @rare_by_twin += 1 + if ["E", "P"].include?(comp) + @rare_matches_by_twin += 1 + end + end + end + + def self.common_type?(t) + t = t.type if t.optional? || t.vararg? + (t.is_a?(RDL::Type::NominalType) && (RUBY_TYPES.include?(t.name) || RAILS_TYPES.include?(t.name))) || + (t == RDL::Globals.types[:bool]) || + (t.to_s == "self") || + (t.is_a?(RDL::Type::GenericType) && common_type?(t.base)) || + (t.is_a?(RDL::Type::UnionType) && t.types.all? { |arm| common_type?(arm) } ) + + end + def self.extract_solutions() ## Go through once to come up with solution for all var types. RDL::Logging.log_header :inference, :info, "Begin Extract Solutions" diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 12a8c561..bdaa109b 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -425,6 +425,7 @@ def self.fast_bert_model_guess(var_type) to_compare_ids = to_compare_vars_same_kind.map { |v| v.object_id } #kind = (var_type.category == :var) ? "cosine" : var_type.category.to_s ## kind of comparison to make kind = (var_type.category == :var) ? "arg" : var_type.category.to_s ## kind of comparison to make + #kind = "cosine" params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind } res = send_query(params) res = JSON.parse(res.body) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 0cc3247a..ce83b4c4 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -267,6 +267,7 @@ def self.infer(klass, meth) def self._infer(klass, meth) @num_lines = Hash.new(0) unless @num_lines + @num_cast_map = {} unless @num_cast_map RDL::Logging.log_header :inference, :debug, "Infering #{RDL::Util.pp_klass_method(klass, meth)}" RDL::Config.instance.use_comp_types = true @@ -362,6 +363,7 @@ def self._infer(klass, meth) RDL::Globals.constrained_types << [klass, meth] unless RDL::Globals.constrained_types.include? [klass, name] RDL::Logging.log :inference, :debug, "Done with constraint generation." + @num_cast_map[[klass.to_s, meth.to_s]] = @num_casts end def self.typecheck(klass, meth, ast=nil, types = nil) @@ -424,6 +426,14 @@ def self.get_num_casts return @num_casts end + def self.get_meth_casts(klass, meth) + klass = klass.to_s + meth = meth.to_s + res = @num_cast_map[[klass, meth]] + puts "Couldn't find #{klass}/#{meth}" if res.nil? + return res + end + def self.get_num_lines return @num_lines end From aa11ed4b8fb64078c53de81072ec7040dfb8a4f9 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 19 Apr 2021 10:24:23 -0400 Subject: [PATCH 102/124] switch to not using twin, for now --- lib/rdl/constraint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index da0a2e10..b6e1d10e 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,6 +1,6 @@ require 'csv' -$use_twin_network = true +$use_twin_network = false $use_heuristics = false From c378049df78b5839ac7fdc080525671626388d9a Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 19 Apr 2021 11:02:21 -0400 Subject: [PATCH 103/124] fix for loading rails when using rake --- lib/rdl/wrap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 0a08692b..f9627724 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -950,7 +950,7 @@ def self.load_sequel_schema(db) end def self.load_rails_schema - return unless defined?(Rails) + return if !defined?(Rails) || (File.basename($0) == "rake") ::Rails.application.eager_load! # load Rails app models = ActiveRecord::Base.descendants.each { |m| begin From 335c283401be396d12639108799a5b2c1ea64016 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Mon, 19 Apr 2021 14:48:40 -0400 Subject: [PATCH 104/124] add pathname dependency --- lib/rdl/boot.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rdl/boot.rb b/lib/rdl/boot.rb index c5d027be..3d226132 100644 --- a/lib/rdl/boot.rb +++ b/lib/rdl/boot.rb @@ -4,6 +4,7 @@ require 'parser/current' #require 'method_source' require 'colorize' +require 'pathname' require 'rake' require 'net/http' From 9439801b2d44903b555a740161e9bbd67b8e4fce Mon Sep 17 00:00:00 2001 From: mckaz Date: Mon, 19 Apr 2021 18:50:26 +0000 Subject: [PATCH 105/124] dont use twin --- lib/rdl/constraint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index da0a2e10..b6e1d10e 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,6 +1,6 @@ require 'csv' -$use_twin_network = true +$use_twin_network = false $use_heuristics = false From 305e93b25d16e6dffc4739704334b6f7017fe4b7 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Thu, 8 Jul 2021 15:17:24 -0400 Subject: [PATCH 106/124] updates for artifact --- lib/rdl/constraint.rb | 163 +++++++++++++++++++++++++++++++----- lib/rdl/heuristics.rb | 162 +++++++++++++++++------------------ lib/rdl/reporting/sorbet.rb | 2 +- lib/rdl/typecheck.rb | 4 +- lib/rdl/types/type.rb | 2 +- lib/rdl/wrap.rb | 54 +++++++++++- 6 files changed, 275 insertions(+), 112 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index b6e1d10e..a9615d9a 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -1,7 +1,7 @@ require 'csv' -$use_twin_network = false -$use_heuristics = false +#$use_twin_network = true +#$use_heuristics = false class << RDL::Typecheck @@ -37,6 +37,8 @@ module RDL::Typecheck @type_names_map = Hash.new { |h, k| h[k] = [] }#[] @type_vars_map = Hash.new { |h, k| h[k] = [] }#[] @failed_sol_cache = Hash.new { |h, k| h[k] = [] } + @tried_heuristic_list = {} + @tried_twin_list = {} @common_matches_by_twin = 0 @rare_matches_by_twin = 0 @@ -104,6 +106,10 @@ def self.resolve_constraints } end + def self.overly_general?(typ) + return (typ.is_a?(RDL::Type::UnionType) && !(typ == RDL::Globals.types[:bool])) || (typ == RDL::Globals.types[:bot]) || (typ == RDL::Globals.types[:top]) || (typ == RDL::Globals.types[:nil]) || typ.is_a?(RDL::Type::StructuralType) || typ.is_a?(RDL::Type::IntersectionType) || (typ == RDL::Globals.types[:object]) + end + def self.extract_var_sol(var, category, add_sol_to_graph = true) #raise "Expected VarType, got #{var}." unless var.is_a?(RDL::Type::VarType) return var.canonical unless var.is_a?(RDL::Type::VarType) @@ -133,7 +139,7 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) else raise "Unexpected VarType category #{category}." end - if (sol.is_a?(RDL::Type::UnionType) && !(sol == RDL::Globals.types[:bool])) || (sol == RDL::Globals.types[:bot]) || (sol == RDL::Globals.types[:top]) || (sol == RDL::Globals.types[:nil]) || sol.is_a?(RDL::Type::StructuralType) || sol.is_a?(RDL::Type::IntersectionType) || (sol == RDL::Globals.types[:object]) + if overly_general?(sol) ## Try each rule. Return first non-nil result. ## If no non-nil results, return original solution. ## TODO: check constraints. @@ -142,12 +148,15 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) RDL::Heuristic.rules.each { |name, rule| next if (@counter == 1) && (name == :twin_network) + next if (name == :twin_network) && !$use_twin_network + next if (name != :twin_network) && !$use_heuristics start_time = Time.now RDL::Logging.log :heuristic, :debug, "Trying rule `#{name}` for variable #{var}." typ = rule.call(var) if typ.is_a?(Array) && (name == :twin_network) count = 0 typ.each { |t| + @tried_twin_list[var] = true new_cons = {} count += 1 begin @@ -158,7 +167,7 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) var.add_and_propagate_lower_bound(t, nil, new_cons) end RDL::Logging.log :hueristic, :debug, "Heuristic Applied: #{name}" - puts "Successfully applied twin network! Solution #{t} for #{var}".blue unless $collecting_time_data + #puts "Successfully applied twin network! Solution #{t} for #{var}".blue unless $collecting_time_data @new_constraints = true if !new_cons.empty? RDL::Logging.log :inference, :trace, "New Constraints branch A" if !new_cons.empty? raise "2. got here with #{var} and #{t}" if t.to_s == "%bot" @@ -174,6 +183,7 @@ def self.extract_var_sol(var, category, add_sol_to_graph = true) new_cons = {} begin typ = typ.canonical + @tried_heuristic_list[var] = true next if @failed_sol_cache[var].include?(typ) if add_sol_to_graph var.add_and_propagate_upper_bound(typ, nil, new_cons) @@ -397,7 +407,8 @@ def self.abbreviated_class?(abbrev, original) def self.make_extraction_report(typ_sols) report = RDL::Reporting::InferenceReport.new - twin_csv = CSV.open("#{if !$use_twin_network then 'no_' end}twin_#{if $use_heuristics then 'heur_' end}_#{RDL::Heuristic.simscore_cutoff}_#{RDL::Heuristic.get_top_n}_infer_data.csv", 'wb') + #twin_csv = CSV.open("#{if !$use_twin_network then 'no_' end}twin_#{if $use_heuristics then 'heur_' end}_#{RDL::Heuristic.simscore_cutoff}_#{RDL::Heuristic.get_top_n}_infer_data.csv", 'wb') + twin_csv = CSV.open("#{if $headeronly then 'headeronly_' end}#{if $namesonly then 'namesonly_' end}#{$infer_config}_top#{RDL::Heuristic.get_top_n}_infer_data.csv", 'wb') twin_csv << ["Class", "Method Name", "Arg/Ret/Var", "Variable Name", "Inferred Type", "Original Type", "Exact (E) / Up to Parameter (P) / Got Type (T) / None (N)", "Solution Source", "Source Code"] compares = Hash.new 0 @@ -416,14 +427,61 @@ def self.make_extraction_report(typ_sols) ret_types = 0 arg_types = 0 var_types = 0 + noncomp_args = 0 + noncomp_rets = 0 + noncomp_vars = 0 + noncomp_meths = 0 + noncomp_no_type = 0 + noncomp_og = 0 + noncomp_usable = 0 num_lines = 0 num_casts = 0 diff_struct_types = 0 + no_type_tried_heur = 0 + no_type_tried_twin = 0 + arr_type_rejected = 0 + hash_type_rejected = 0 typ_sols.each_pair { |km, typ| compared = false klass, meth = km orig_typ = RDL::Globals.info.get(klass, meth, :orig_type) - next if orig_typ.nil? || typ.solution.nil? + next if typ.solution.nil? + if orig_typ.nil? + if typ.is_a?(RDL::Type::MethodType) + noncomp_meths += 1 + typ.args.each { |t| + noncomp_args +=1 + t = t.solution + if t.nil? || t.kind_of_var_input? + noncomp_no_type += 1 + elsif overly_general?(t) + noncomp_og += 1 + else + noncomp_usable += 1 + end + } + noncomp_rets += 1 + if typ.ret.solution.nil? || typ.ret.solution.kind_of_var_input? + noncomp_no_type += 1 + elsif overly_general?(typ.ret) + noncomp_og += 1 + else + noncomp_usable += 1 + end + elsif typ.is_a?(RDL::Type::VarType) + noncomp_vars += 1 + if typ.solution.nil? || typ.solution.kind_of_var_input? + noncomp_no_type += 1 + elsif overly_general?(typ.solution) + noncomp_og += 1 + else + noncomp_usable += 1 + end + else + raise "Unexpected kind #{typ.class}" + end + next + end if orig_typ.is_a?(Array) raise "expected just one original type for #{klass}##{meth}" unless orig_typ.size == 1 orig_typ = orig_typ[0] @@ -450,13 +508,26 @@ def self.make_extraction_report(typ_sols) end twin_csv << [klass, meth, "Arg", name, inf_arg_type.to_s, orig_arg_typ.to_s, comp, sol_source, code] arg_types +=1 - update_type_counts(inf_arg_type, orig_arg_typ, comp, sol_source) + update_type_counts(inf_arg_type, orig_arg_typ, comp, sol_source) + if comp == "N" + no_type_tried_heur += 1 if @tried_heuristic_list.has_key?(inf_arg_type) + no_type_tried_twin += 1 if @tried_twin_list.has_key?(inf_arg_type) + arr_type_rejected += 1 if orig_arg_typ.array_type? && @failed_sol_cache[inf_arg_type].any? { |t| t.array_type? } + hash_type_rejected += 1 if orig_arg_typ.hash_type? && @failed_sol_cache[inf_arg_type].any? { |t| t.hash_type? } + end } unless (orig_typ.ret == RDL::Globals.types[:bot]) ## bot type is given to any returns for which we don't have a type ret_types += 1 inf_ret_type = typ.solution.ret sol_source = typ.ret.solution_source comp = inf_ret_type.nil? ? "N" : compare_single_type(inf_ret_type, orig_typ.ret) + if comp == "N" + no_type_tried_heur += 1 if @tried_heuristic_list.has_key?(inf_ret_type) + no_type_tried_twin += 1 if @tried_twin_list.has_key?(inf_ret_type) + arr_type_rejected += 1 if orig_typ.ret.array_type? && @failed_sol_cache[inf_ret_type].any? { |t| t.array_type? } + hash_type_rejected += 1 if orig_typ.ret.hash_type? && @failed_sol_cache[inf_ret_type].any? { |t| t.hash_type? } + + end compared = true update_type_counts(inf_ret_type, orig_typ, comp, sol_source) compares[comp] += 1 @@ -466,9 +537,36 @@ def self.make_extraction_report(typ_sols) meth_types += 1 num_lines += RDL::Util.count_num_lines(klass, meth) num_casts += RDL::Typecheck.get_meth_casts(klass, meth) + else + noncomp_meths += 1 + typ.args.each { |t| + t = t.solution + noncomp_args +=1 + if t.nil? || t.kind_of_var_input? + noncomp_no_type += 1 + elsif overly_general?(t) + noncomp_og += 1 + else + noncomp_usable += 1 + end + } + noncomp_rets += 1 + if typ.ret.solution.kind_of_var_input? || typ.ret.solution.nil? + noncomp_no_type += 1 + elsif overly_general?(typ.ret.solution) + noncomp_og += 1 + else + noncomp_usable += 1 + end end else comp = typ.solution.nil? ? "N" : compare_single_type(typ.solution, orig_typ) + if comp == "N" + no_type_tried_heur += 1 if @tried_heuristic_list.has_key?(typ) + no_type_tried_twin += 1 if @tried_twin_list.has_key?(typ) + arr_type_rejected += 1 if orig_typ.array_type? && @failed_sol_cache[typ].any? { |t| t.array_type? } + hash_type_rejected += 1 if orig_typ.hash_type? && @failed_sol_cache[typ].any? { |t| t.hash_type? } + end compares[comp] += 1 sol_source = typ.solution_source update_type_counts(typ.solution, orig_typ, comp, sol_source) @@ -499,21 +597,23 @@ def self.make_extraction_report(typ_sols) } RDL::Logging.log_header :inference, :info, "Extraction Complete" - RDL::Logging.log :inference, :info, "SIMILARITY SCORE CUTOFF = #{RDL::Heuristic.simscore_cutoff}" - RDL::Logging.log :inference, :info, "USING TOP N=#{RDL::Heuristic.get_top_n} TYPES" + #RDL::Logging.log :inference, :info, "SIMILARITY SCORE CUTOFF = #{RDL::Heuristic.simscore_cutoff}" + RDL::Logging.log :inference, :info, "DeepSim using top N=#{RDL::Heuristic.get_top_n} Guesses" twin_csv << ["Total # E:", compares["E"]] - RDL::Logging.log :inference, :info, "Total exactly correct (E): #{compares["E"]}" + RDL::Logging.log :inference, :info, "Total exact matches (E): #{compares["E"]}" twin_csv << ["Total # P:", compares["P"]] - RDL::Logging.log :inference, :info, "Total correct up to parameter (P): #{compares["P"]}" + RDL::Logging.log :inference, :info, "Total matches up to parameter (P): #{compares["P"]}" twin_csv << ["Total # T:", compares["T"]] - RDL::Logging.log :inference, :info, "Total not correct but got type for (T): #{compares["T"] + compares["TS"]}" + RDL::Logging.log :inference, :info, "Total not match but got type for (T): #{compares["T"] + compares["TS"]}" twin_csv << ["Total # TS:", compares["TS"]] RDL::Logging.log :inference, :info, "Total number of T's that were structural types: #{compares["TS"]}" twin_csv << ["Total # N:", compares["N"]] RDL::Logging.log :inference, :info, "Total no type for (N): #{compares["N"]}" + #RDL::Logging.log :inference, :info, "Of N types, tried to apply a heuristic guess to #{no_type_tried_heur} positions, and tried to apply a DeepSim guess to #{no_type_tried_twin} positions. #{arr_type_rejected} array types were rejected even though the orig was an array type, and #{hash_type_rejected} hash types were rejected, etc." #twin_csv << ["Total # method types:", meth_types] RDL::Logging.log :inference, :info, "Total # compared method types: #{meth_types} comprising #{num_lines} lines" RDL::Logging.log :inference, :info, "Total # casts within compared method: #{num_casts}" + #RDL::Logging.log :inference, :info, "#{noncomp_meths} non-compared methods (#{noncomp_args} args/#{noncomp_rets} rets) and #{noncomp_vars} non-compared variables. Of these, inferred: #{noncomp_usable} usable types, #{noncomp_og} overly general types, and #{noncomp_no_type} no types." twin_csv << ["Total # return types:", ret_types] RDL::Logging.log :inference, :info, "Total # return types: #{ret_types}" twin_csv << ["Total # arg types:", arg_types] @@ -522,12 +622,36 @@ def self.make_extraction_report(typ_sols) RDL::Logging.log :inference, :info, "Total # variable types: #{var_types}" twin_csv << ["Total # individual types:", var_types + meth_types + arg_types] RDL::Logging.log :inference, :info, "Total # individual types: #{ret_types + arg_types + var_types}" - RDL::Logging.log :inference, :info, "Total # common types guessed by twin: #{@common_by_twin}." - RDL::Logging.log :inference, :info, "Total # common type *matches* guessed by twin: #{@common_matches_by_twin}." +=begin + RDL::Logging.log :inference, :info, "Total # library types guessed by twin: #{@common_by_twin}." + RDL::Logging.log :inference, :info, "Total # library type matches guessed by twin: #{@common_matches_by_twin}." RDL::Logging.log :inference, :info, "Total # rare types guessed by twin: #{@rare_by_twin}" RDL::Logging.log :inference, :info, "Total # rare type *matches* guessed by twin: #{@rare_matches_by_twin}" - RDL::Logging.log :inference, :info, "Total # common type originals: #{@tot_orig_common}" - RDL::Logging.log :inference, :info, "Total # rare type originals: #{@tot_orig_rare}" + #RDL::Logging.log :inference, :info, "Total # common type originals: #{@tot_orig_common}" + #RDL::Logging.log :inference, :info, "Total # rare type originals: #{@tot_orig_rare}" +=end + $results_hash[:matches] = compares["E"] + $results_hash[:param] = compares["P"] + $results_hash[:gottype] = compares["T"] + compares["TS"] + $results_hash[:struct] = compares["TS"] + $results_hash[:notype] = compares["N"] + $results_hash[:compared_meths] = meth_types + $results_hash[:compared_ret_types] = ret_types + $results_hash[:compared_arg_types] = arg_types + $results_hash[:compared_var_types] = var_types + $results_hash[:compared_meths_lines] = num_lines + $results_hash[:compared_meths_casts] = num_casts + $results_hash[:noncomp_meths] = noncomp_meths + $results_hash[:noncomp_args] = noncomp_args + $results_hash[:noncomp_rets] = noncomp_rets + $results_hash[:noncomp_vars] = noncomp_vars + $results_hash[:noncomp_usable] = noncomp_usable + $results_hash[:noncomp_og] = noncomp_og + $results_hash[:noncomp_no_type] = noncomp_no_type + $results_hash[:noncomp_meths_lines] = num_lines + $results_hash[:noncomp_meths_casts] = num_casts + $results_hash[:library_guesses_by_twin] = @common_by_twin + $results_hash[:library_matches_by_twin] = @common_matches_by_twin rescue => e RDL::Logging.log :inference, :error, "Report Generation Error" RDL::Logging.log :inference, :debug_error, "... got #{e}" @@ -546,14 +670,13 @@ def self.update_type_counts(inf_type, orig_type, comp, sol_source) return unless (sol_source == "Twin") ## not interested in cases that constraints/heuristics got if common_type?(inf_type) @common_by_twin += 1 - if ["E", "P"].include?(comp) + if ["E"].include?(comp)#if ["E", "P"].include?(comp) @common_matches_by_twin += 1 end else - puts "Rare type #{inf_type}" ## rare type @rare_by_twin += 1 - if ["E", "P"].include?(comp) + if ["E"].include?(comp)#if ["E", "P"].include?(comp) @rare_matches_by_twin += 1 end end @@ -661,7 +784,7 @@ def self.extract_solutions() puts "RECEIVED ERROR #{e} from #{e.backtrace}" ensure $http.finish if $http - puts "MAKING EXTRACTION REPORT" + #puts "MAKING EXTRACTION REPORT" return make_extraction_report(typ_sols) end diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index bdaa109b..99964897 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -2,7 +2,7 @@ class RDL::Heuristic @simscore_cutoff = 0.5 - @top_n = 3 + #@top_n = 3 @nums_above_cutoff = {} @@ -27,7 +27,8 @@ def self.simscore_cutoff end def self.get_top_n - @top_n + #@top_n + $topn end @rules = {} @@ -44,8 +45,6 @@ def self.get_top_n $use_only_param_position = false ## whether or not to use just an arg's parameter position, or use all of its uses in a method, when running on BERT model def self.empty_cache! - ## temporarily commenting out -=begin @meth_cache = {} @meth_to_cls_map = Hash.new {|h, m| h[m] = Set.new} # maps a method to a set of classes with that method @ast_cache = {} @@ -57,7 +56,6 @@ def self.empty_cache! RDL::Typecheck.empty_cache! RDL::Type::StructuralType.clear_cache! RDL::Type::VarType.clear_cache! -=end #RDL::Type::NominalType.clear_cache! #RDL::Type::SingletonType.clear_cache! #RDL::Type::IntersectionType.clear_cache! @@ -310,7 +308,7 @@ def self.vectorize_var(var_type) return nil if ast.nil? begin_pos = ast.loc.expression.begin_pos locs = [ast.loc.name.begin_pos - begin_pos, ast.loc.name.end_pos - begin_pos-1] ## for now, let's just try using method name - locs = locs + get_ret_sites(ast) + locs = locs + get_ret_sites(ast) unless $namesonly locs = [ast.loc.name.begin_pos - begin_pos, ast.loc.name.end_pos - begin_pos-1] if locs.empty? return nil if locs.empty? source = ast.loc.expression.source @@ -335,7 +333,7 @@ def self.visualize_bert(var_kind) type = RDL::Globals.info.get(klass, mname, :type) orig_type = RDL::Globals.info.get(klass, mname, :orig_type) if orig_type.nil? - puts "Could not find original type for #{klass}##{mname}.".red unless $collecting_time_data + #puts "Could not find original type for #{klass}##{mname}.".red unless $collecting_time_data next end if type.is_a?(Array) @@ -378,7 +376,7 @@ def self.visualize_bert(var_kind) def self.fast_bert_model_guess(var_type) return unless (var_type.category == :arg) || (var_type.category == :var) || (var_type.category == :ret) - puts "Vectorizing #{var_type} of object id #{var_type.object_id}..." unless $collecting_time_data + #puts "Vectorizing #{var_type} of object id #{var_type.object_id}..." unless $collecting_time_data ret = vectorize_var(var_type) return nil if ret.nil? @@ -395,19 +393,19 @@ def self.fast_bert_model_guess(var_type) #next if !(var_type.category == var2.category) next if ((var_type.category == :ret) || (var2.category == :ret)) && !(var_type.category == var2.category) # only compare rets with rets if sim_score = @bert_cache[var_type][var2] - puts "Hit cache for vars #{var_type} and #{var2}, with score of #{sim_score}".red unless $collecting_time_data + #puts "Hit cache for vars #{var_type} and #{var2}, with score of #{sim_score}".red unless $collecting_time_data message = "Received similarity score of #{sim_score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" if sim_score > @simscore_cutoff - puts message.green unless $collecting_time_data + #puts message.green unless $collecting_time_data sols[sim_score] = t else - puts message.red unless $collecting_time_data + #puts message.red unless $collecting_time_data end else - puts "Vectorizing #{var2} of object id #{var2.object_id}..." unless $collecting_time_data + #puts "Vectorizing #{var2} of object id #{var2.object_id}..." unless $collecting_time_data vec_res = vectorize_var(var2) if vec_res.nil? - puts "Could not find AST for #{var2}".yellow unless $collecting_time_data + #puts "Could not find AST for #{var2}".yellow unless $collecting_time_data next end if (var_type.category == var2.category) || (var_type.category == :var) || (var2.category == :var) @@ -426,7 +424,7 @@ def self.fast_bert_model_guess(var_type) #kind = (var_type.category == :var) ? "cosine" : var_type.category.to_s ## kind of comparison to make kind = (var_type.category == :var) ? "arg" : var_type.category.to_s ## kind of comparison to make #kind = "cosine" - params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind } + params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind, namesonly: $namesonly, headeronly: $headeronly } res = send_query(params) res = JSON.parse(res.body) raise "Expected #{to_compare_ids.size} similarity scores, received #{res.size}" unless res.size == to_compare_ids.size @@ -436,10 +434,10 @@ def self.fast_bert_model_guess(var_type) @bert_cache[var_type][var2] = score message = "Received similarity score of #{score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" if score > @simscore_cutoff - puts message.green unless $collecting_time_data + #puts message.green unless $collecting_time_data sols[score] = t else - puts message.red unless $collecting_time_data + #puts message.red unless $collecting_time_data end } end @@ -447,7 +445,7 @@ def self.fast_bert_model_guess(var_type) to_compare_ids = to_compare_vars_diff_kind.map { |v| v.object_id } #kind = "cosine" kind = "arg" - params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind } + params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind, namesonly: $namesonly, headeronly: $headeronly } res = send_query(params) res = JSON.parse(res.body) raise "Expected #{to_compare_ids.size} similarity scores, received #{res.size}" unless res.size == to_compare_ids.size @@ -457,17 +455,17 @@ def self.fast_bert_model_guess(var_type) @bert_cache[var_type][var2] = score message = "Received similarity score of #{score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" if score > @simscore_cutoff - puts message.green unless $collecting_time_data + #puts message.green unless $collecting_time_data sols[score] = t else - puts message.red unless $collecting_time_data + #puts message.red unless $collecting_time_data end } end res = sols.sort.map { |sim_score, t| t }.reverse.uniq#.first(@top_n) @nums_above_cutoff[var_type] = res.size - return res.first(@top_n) + return res.first($topn) end def self.bert_model_guess(var_type) @@ -486,17 +484,17 @@ def self.bert_model_guess(var_type) next if ((var_type.category == :ret) || (var2.category == :ret)) && !(var_type.category == var2.category) # only compare rets with rets count += 1 if @bert_cache[var_type][var2] - puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red unless $collecting_time_data + #puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red unless $collecting_time_data #sum += @bert_cache[var_type][var2] sim_score = @bert_cache[var_type][var2] else vec_res = vectorize_var(var2) if vec_res.nil? - puts "Could not find AST for #{var2}".yellow unless $collecting_time_data + #puts "Could not find AST for #{var2}".yellow unless $collecting_time_data next end - puts "About to ask for similarity of #{var_type} and #{var2}" unless $collecting_time_data - res = send_query({ action: "get_similarity", id1: var_type.object_id, id2: var2.object_id, kind1: var_type.category.to_s, kind2: var2.category.to_s }) + #puts "About to ask for similarity of #{var_type} and #{var2}" unless $collecting_time_data + res = send_query({ action: "get_similarity", id1: var_type.object_id, id2: var2.object_id, kind1: var_type.category.to_s, kind2: var2.category.to_s, namesonly: $namesonly, headeronly: $headeronly }) #sum += res.body.to_f sim_score = res.body.to_f @bert_cache[var_type][var2] = res.body.to_f @@ -504,10 +502,10 @@ def self.bert_model_guess(var_type) end message = "Received similarity score of #{sim_score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" if sim_score > @simscore_cutoff - puts message.green unless $collecting_time_data + #puts message.green unless $collecting_time_data sols[sim_score] = t else - puts message.red unless $collecting_time_data + #puts message.red unless $collecting_time_data #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red end } @@ -529,7 +527,7 @@ def self.bert_model_guess(var_type) else - puts "not yet implemented" + #puts "not yet implemented" end end @@ -581,11 +579,12 @@ def self.get_var_loc(ast, var_type) ## Found the arg corresponding to var_type locs << (c.loc.name.begin_pos - begin_pos) ## translate it so that 0 is first position locs << (c.loc.name.end_pos - begin_pos - 1) - return locs if $use_only_param_position + #return locs if $headeronly #$use_only_param_position end } + return locs if $headeronly end - + search_ast_for_var_locs(body, var_type.name, locs, begin_pos) raise "Expected even number of locations." unless (locs.length % 2) == 0 @@ -648,68 +647,63 @@ def model_set_type end end -if $use_heuristics - if defined? Rails - RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } - RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } - end +if defined? Rails + RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } + RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } +end - RDL::Heuristic.add(:struct_to_nominal) { |var| t1 = Time.now; g = RDL::Heuristic.struct_to_nominal(var); $stn = $stn + (Time.now - t1); g } - RDL::Heuristic.add(:int_names) { |var| if var.base_name.end_with?("id") || (var.base_name.end_with? "num") || (var.base_name.end_with? "count") then RDL::Globals.types[:integer] end } - RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") || (var.base_name.end_with? "nums") || (var.base_name.end_with? "counts") then RDL::Globals.parser.scan_str "#T Array" end } - RDL::Heuristic.add(:predicate_method) { |var| if var.base_name.end_with?("?") then RDL::Globals.types[:bool] end } - RDL::Heuristic.add(:string_name) { |var| if var.base_name.end_with?("name") then RDL::Globals.types[:string] end } +RDL::Heuristic.add(:struct_to_nominal) { |var| t1 = Time.now; g = RDL::Heuristic.struct_to_nominal(var); $stn = $stn + (Time.now - t1); g } +RDL::Heuristic.add(:int_names) { |var| if var.base_name.end_with?("id") || (var.base_name.end_with? "num") || (var.base_name.end_with? "count") then RDL::Globals.types[:integer] end } +RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") || (var.base_name.end_with? "nums") || (var.base_name.end_with? "counts") then RDL::Globals.parser.scan_str "#T Array" end } +RDL::Heuristic.add(:predicate_method) { |var| if var.base_name.end_with?("?") then RDL::Globals.types[:bool] end } +RDL::Heuristic.add(:string_name) { |var| if var.base_name.end_with?("name") then RDL::Globals.types[:string] end } - RDL::Heuristic.add(:hash_access) { |var| - old_var = var - var = var.type if old_var.is_a?(RDL::Type::OptionalType) - types = [] - var.ubounds.reject { |t, ast| t.is_a?(RDL::Type::VarType) }.each { |t, ast| - if t.is_a?(RDL::Type::IntersectionType) - types = types + t.types - else - types << t - end - } - if !types.empty? && types.all? { |t| t.is_a?(RDL::Type::StructuralType) && t.methods.all? { |meth, typ| ((meth == :[]) || (meth == :[]=)) && typ.args[0].is_a?(RDL::Type::SingletonType) && typ.args[0].val.is_a?(Symbol) } } - hash_typ = {} - types.each { |struct| - struct.methods.each { |meth, typ| - if meth == :[] - value_type = typ.ret#typ.ret.is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.ret, :arg).canonical : typ.ret.canonical - elsif meth == :[]= - value_type = typ.args[1]#typ.args[1].is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.args[1], :arg).canonical : typ.args[1].canonical - else - raise "Method should be one of :[] or :[]=, got #{meth}." - end - if value_type.is_a?(RDL::Type::UnionType) - RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg, false) }).drop_vars.canonical - elsif value_type.is_a?(RDL::Type::IntersectionType) - RDL::Type::IntersectionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical - else - value_type = RDL::Typecheck.extract_var_sol(value_type, :arg, false) - end - #value_type = value_type.drop_vars!.canonical if (value_type.is_a?(RDL::Type::UnionType) || value_type.is_a?(RDL::Type::IntersectionType)) && (!value_type.types.all? { |t| t.is_a?(RDL::Type::VarType) }) - hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: - } - } - #var.ubounds.delete_if { |t| t.is_a?(RDL::Type::StructuralType) } #= [] ## might have to change this later, in particular to take advantage of comp types when performing solution extraction - fht = RDL::Type::FiniteHashType.new(hash_typ, nil) - if old_var.is_a?(RDL::Type::OptionalType) - RDL::Type::OptionalType.new(fht) - else - fht - end +RDL::Heuristic.add(:hash_access) { |var| + old_var = var + var = var.type if old_var.is_a?(RDL::Type::OptionalType) + types = [] + var.ubounds.reject { |t, ast| t.is_a?(RDL::Type::VarType) }.each { |t, ast| + if t.is_a?(RDL::Type::IntersectionType) + types = types + t.types + else + types << t end } - -end + if !types.empty? && types.all? { |t| t.is_a?(RDL::Type::StructuralType) && t.methods.all? { |meth, typ| ((meth == :[]) || (meth == :[]=)) && typ.args[0].is_a?(RDL::Type::SingletonType) && typ.args[0].val.is_a?(Symbol) } } + hash_typ = {} + types.each { |struct| + struct.methods.each { |meth, typ| + if meth == :[] + value_type = typ.ret#typ.ret.is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.ret, :arg).canonical : typ.ret.canonical + elsif meth == :[]= + value_type = typ.args[1]#typ.args[1].is_a?(RDL::Type::VarType) ? RDL::Typecheck.extract_var_sol(typ.args[1], :arg).canonical : typ.args[1].canonical + else + raise "Method should be one of :[] or :[]=, got #{meth}." + end + if value_type.is_a?(RDL::Type::UnionType) + RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg, false) }).drop_vars.canonical + elsif value_type.is_a?(RDL::Type::IntersectionType) + RDL::Type::IntersectionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical + else + value_type = RDL::Typecheck.extract_var_sol(value_type, :arg, false) + end + #value_type = value_type.drop_vars!.canonical if (value_type.is_a?(RDL::Type::UnionType) || value_type.is_a?(RDL::Type::IntersectionType)) && (!value_type.types.all? { |t| t.is_a?(RDL::Type::VarType) }) + hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: + } + } + #var.ubounds.delete_if { |t| t.is_a?(RDL::Type::StructuralType) } #= [] ## might have to change this later, in particular to take advantage of comp types when performing solution extraction + fht = RDL::Type::FiniteHashType.new(hash_typ, nil) + if old_var.is_a?(RDL::Type::OptionalType) + RDL::Type::OptionalType.new(fht) + else + fht + end + end +} ### For rules involving :include?, :==, :!=, etc. we would need to track the exact receiver/args used in the method call, and somehow store these in the bounds created for a var type. -if $use_twin_network - RDL::Heuristic.add(:twin_network) { |var| RDL::Heuristic.fast_bert_model_guess(var) } -end +RDL::Heuristic.add(:twin_network) { |var| RDL::Heuristic.fast_bert_model_guess(var) } diff --git a/lib/rdl/reporting/sorbet.rb b/lib/rdl/reporting/sorbet.rb index c4a1c80a..ccc63b4c 100644 --- a/lib/rdl/reporting/sorbet.rb +++ b/lib/rdl/reporting/sorbet.rb @@ -138,7 +138,7 @@ def to_sorbet_string(typ, header, in_hash: false) when RDL::Type::TopType "T.nilable(BasicObject)" else - RDL::Logging.warning :reporting, "Unmatched class #{typ.class}" + #RDL::Logging.warning :reporting, "Unmatched class #{typ.class}" 'T.untyped' end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index ce83b4c4..dc9c4d62 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -268,7 +268,7 @@ def self.infer(klass, meth) def self._infer(klass, meth) @num_lines = Hash.new(0) unless @num_lines @num_cast_map = {} unless @num_cast_map - RDL::Logging.log_header :inference, :debug, "Infering #{RDL::Util.pp_klass_method(klass, meth)}" + RDL::Logging.log_header :inference, :info, "Generating constraints for method #{RDL::Util.pp_klass_method(klass, meth)}..." RDL::Config.instance.use_comp_types = true RDL::Config.instance.number_mode = true @@ -277,7 +277,7 @@ def self._infer(klass, meth) num_lines = RDL::Util.count_num_lines(klass, meth) @num_lines[[klass, meth]] = num_lines.nil? ? 0 : num_lines if ast.nil? - RDL::Logging.log :inference, :warning, "Warning: Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}; skipping method" + #RDL::Logging.log :inference, :warning, "Warning: Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}; skipping method" # if RDL::Config.instance.continue_on_errors # puts "#{warning_text} recording %dyn" if RDL::Config.instance.convert_to_dyn_verbose diff --git a/lib/rdl/types/type.rb b/lib/rdl/types/type.rb index d82e24d0..9bb63505 100644 --- a/lib/rdl/types/type.rb +++ b/lib/rdl/types/type.rb @@ -22,7 +22,7 @@ def solution def solution=(soln) @solution = soln - RDL::Logging.log :typecheck, :warning, "Solution written to #{self.class}" + #RDL::Logging.log :typecheck, :warning, "Solution written to #{self.class}" end def to_contract diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index f9627724..a0607a58 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -854,11 +854,38 @@ def self.do_typecheck(sym) nil end - def self.do_infer(sym, render_report: true, num_times: 1) + def self.do_infer(sym, render_report: true, num_times: 1, config: "C", topn: 3, headeronly: false, namesonly: false) return unless RDL::Globals.to_infer[sym] raise "Expected num_times to be positive int, got #{num_times}." unless num_times > 0 + config_args = Object.const_defined?("INFER_CONFIG") ? INFER_CONFIG : ARGV + if config_args.size >= 1 + config = config_args[0] + end + if config_args.size >= 2 + topn = config_args[1].to_i + end + if (config_args.size == 3) || (config_args.size == 4) + fin_args = config_args.size == 3 ? [config_args[2]] : [config_args[2], config_args[3]] + raise "Expected final args to be headeronly and/or namesonly, received: #{fin_args}" unless fin_args.all? { |e| (e == "namesonly") || (e == "headeronly") } + headeronly = fin_args.include? "headeronly" + namesonly = fin_args.include? "namesonly" + end + #if (ARGV.size >= 1) || Object.const_defined?("INFER_CONFIG") + # config = Object.const_defined?("INFER_CONFIG") ? INFER_CONFIG[0] : ARGV[0] + #end + raise "Expected config to be one of ['C', 'CH', 'CD', 'CHD'], got #{config}" unless ['C', 'CH', 'CD', 'CHD'].include?(config) + raise "Expected topn to be an integer, received #{topn}." unless topn.is_a?(Integer) + $infer_config = config + $topn = topn + $namesonly = namesonly + $headeronly = headeronly + + $use_heuristics = config.include?("H") + $use_twin_network = config.include?("D") run_times = [] + $results_hash = {} + $collecting_time_data = num_times > 1 num_times.times { |run_count| RDL::Config.instance.use_unknown_types = true @@ -901,14 +928,14 @@ def self.do_infer(sym, render_report: true, num_times: 1) report.to_csv 'infer_data_new.csv' if render_report report.to_sorbet 'infer_data.rbi' if render_report - RDL::Logging.log :inference, :info, "Total number of type casts used: #{num_casts}." + #RDL::Logging.log :inference, :info, "Total number of type casts used: #{num_casts}." nl = RDL::Typecheck.get_num_lines RDL::Logging.log :inference, :info, "Analyized #{nl.size} methods comprising #{nl.values.sum} lines of code." if num_times == 1 RDL::Logging.log :inference, :info, "Total time taken: #{time}." - RDL::Logging.log :inference, :info, "Total amount of time spent on stn: #{$stn}." + #RDL::Logging.log :inference, :info, "Total amount of time spent on stn: #{$stn}." else sorted = run_times.sort median_proc = Proc.new { |sorted_arr| (sorted_arr[(sorted_arr.length - 1) / 2] + sorted_arr[sorted_arr.length / 2]) / 2.0 } @@ -919,12 +946,31 @@ def self.do_infer(sym, render_report: true, num_times: 1) RDL::Logging.log :inference, :info, "Median time across #{num_times} runs: #{median}." RDL::Logging.log :inference, :info, "SIQR across #{num_times} runs: #{(q3 - q1)/2.0}." end - RDL::Heuristic.report_nums_above_cutoff + #RDL::Heuristic.report_nums_above_cutoff + $results_hash[:num_casts] = num_casts + $results_hash[:time] = time + write_results_hash() end } RDL::Globals.to_infer[sym] = Set.new end + def self.write_results_hash() + config = "C" + ($use_heuristics ? "H" : "") + ($use_twin_network ? "D" : "") + filename = "results_top#{$topn}.json" + filename = "headeronly_" + filename if $headeronly + filename = "namesonly_" + filename if $namesonly + if File.file?(filename) + old_res = JSON.parse(File.read(filename)) + else + old_res = {} + end + old_res[config] = $results_hash + File.open(filename, "w") { |f| + f.puts JSON.pretty_generate(old_res) + } + end + def self.load_sequel_schema(db) db.disconnect db.tables.each { |table| From 20feaec42b942e46e5e70464a19f8174ad023f48 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 9 Jul 2021 10:12:20 -0400 Subject: [PATCH 107/124] minor updates --- lib/rdl/constraint.rb | 3 ++- lib/rdl/heuristics.rb | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index a9615d9a..f090970c 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -598,7 +598,8 @@ def self.make_extraction_report(typ_sols) RDL::Logging.log_header :inference, :info, "Extraction Complete" #RDL::Logging.log :inference, :info, "SIMILARITY SCORE CUTOFF = #{RDL::Heuristic.simscore_cutoff}" - RDL::Logging.log :inference, :info, "DeepSim using top N=#{RDL::Heuristic.get_top_n} Guesses" + RDL::Logging.log :inference, :info, "Ran SimTyper under configuration #{$infer_config}" + RDL::Logging.log :inference, :info, "DeepSim using top N=#{RDL::Heuristic.get_top_n} Guesses" if $use_twin_network twin_csv << ["Total # E:", compares["E"]] RDL::Logging.log :inference, :info, "Total exact matches (E): #{compares["E"]}" twin_csv << ["Total # P:", compares["P"]] diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index 99964897..794a7cb9 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -45,17 +45,17 @@ def self.get_top_n $use_only_param_position = false ## whether or not to use just an arg's parameter position, or use all of its uses in a method, when running on BERT model def self.empty_cache! - @meth_cache = {} - @meth_to_cls_map = Hash.new {|h, m| h[m] = Set.new} # maps a method to a set of classes with that method - @ast_cache = {} - @twin_cache = Hash.new { |h, k| h[k] = {} } - @bert_cache = Hash.new { |h, k| h[k] = {} } - @vectorized_vars = {} + #@meth_cache = {} + #@meth_to_cls_map = Hash.new {|h, m| h[m] = Set.new} # maps a method to a set of classes with that method + #@ast_cache = {} + #@twin_cache = Hash.new { |h, k| h[k] = {} } + #@bert_cache = Hash.new { |h, k| h[k] = {} } + #@vectorized_vars = {} send_query({action: "empty_cache!"}) if $use_twin_network - RDL::Globals.parser_cache = Hash.new - RDL::Typecheck.empty_cache! - RDL::Type::StructuralType.clear_cache! - RDL::Type::VarType.clear_cache! + #RDL::Globals.parser_cache = Hash.new + #RDL::Typecheck.empty_cache! + #RDL::Type::StructuralType.clear_cache! + #RDL::Type::VarType.clear_cache! #RDL::Type::NominalType.clear_cache! #RDL::Type::SingletonType.clear_cache! #RDL::Type::IntersectionType.clear_cache! @@ -579,10 +579,10 @@ def self.get_var_loc(ast, var_type) ## Found the arg corresponding to var_type locs << (c.loc.name.begin_pos - begin_pos) ## translate it so that 0 is first position locs << (c.loc.name.end_pos - begin_pos - 1) - #return locs if $headeronly #$use_only_param_position + return locs if $headeronly #$use_only_param_position end } - return locs if $headeronly + #return locs if $headeronly end search_ast_for_var_locs(body, var_type.name, locs, begin_pos) From b54f076c451ab48b52b4704cd4d2a31020beb23e Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 9 Jul 2021 10:20:05 -0400 Subject: [PATCH 108/124] typo fix --- lib/rdl/wrap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index a0607a58..012e8de2 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -930,7 +930,7 @@ def self.do_infer(sym, render_report: true, num_times: 1, config: "C", topn: 3, #RDL::Logging.log :inference, :info, "Total number of type casts used: #{num_casts}." nl = RDL::Typecheck.get_num_lines - RDL::Logging.log :inference, :info, "Analyized #{nl.size} methods comprising #{nl.values.sum} lines of code." + RDL::Logging.log :inference, :info, "Analyzed #{nl.size} methods comprising #{nl.values.sum} lines of code." if num_times == 1 From a7d10bb3efe5dc07f58399501313a9fd672f19e4 Mon Sep 17 00:00:00 2001 From: Milod Kazerounian Date: Fri, 9 Jul 2021 10:41:32 -0400 Subject: [PATCH 109/124] attempt fix for CDO schema loading issue --- lib/rdl/wrap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index 012e8de2..ca81e00f 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -996,7 +996,7 @@ def self.load_sequel_schema(db) end def self.load_rails_schema - return if !defined?(Rails) || (File.basename($0) == "rake") + return if !defined?(Rails) || (File.basename($0) == "rake") || $dont_load_rails ::Rails.application.eager_load! # load Rails app models = ActiveRecord::Base.descendants.each { |m| begin From 5f07faa24f0a0252069223f52379bc378e83a723 Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Thu, 30 Sep 2021 13:22:02 -0400 Subject: [PATCH 110/124] stringio has to be imported for Ruby 3 --- lib/rdl/util.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rdl/util.rb b/lib/rdl/util.rb index 55274cc5..80279d28 100644 --- a/lib/rdl/util.rb +++ b/lib/rdl/util.rb @@ -91,6 +91,7 @@ def self.pp_klass_method(klass, meth) def self.silent_warnings old_stderr = $stderr + require "stringio" $stderr = StringIO.new yield ensure From 792c40fa77322524075596d01daaf77d1a42f5d9 Mon Sep 17 00:00:00 2001 From: Mark Aldrich Date: Wed, 15 Jun 2022 16:36:05 -0400 Subject: [PATCH 111/124] undid ML changes to heuristics --- lib/rdl/heuristics.rb | 577 +----------------------------------------- 1 file changed, 6 insertions(+), 571 deletions(-) diff --git a/lib/rdl/heuristics.rb b/lib/rdl/heuristics.rb index dfe53d3f..dd081276 100644 --- a/lib/rdl/heuristics.rb +++ b/lib/rdl/heuristics.rb @@ -1,82 +1,8 @@ class RDL::Heuristic - - @simscore_cutoff = 0.5 - - #@top_n = 3 - - @nums_above_cutoff = {} - - def self.get_nums_above_cutoff - @nums_above_cutoff - end - - def self.report_nums_above_cutoff - vals = @nums_above_cutoff.values - puts "Total number of queries: #{vals.size}" - [1, 3, 5, 7, 9, 11, 13].each { |v| - puts "Number of queries with more than #{v} solutions over 0.5: #{vals.count { |x| x > v }}" - } - end - - def self.simscore_cutoff=(c) - @simscore_cutoff = c - end - - def self.simscore_cutoff - @simscore_cutoff - end - - def self.get_top_n - #@top_n - $topn - end @rules = {} @meth_cache = {} - @meth_to_cls_map = Hash.new {|h, m| h[m] = Set.new} # maps a method to a set of classes with that method - - @ast_cache = {} - - @twin_cache = Hash.new { |h, k| h[k] = {} } - @bert_cache = Hash.new { |h, k| h[k] = {} } - @vectorized_vars = {} ## Maps VarType object IDs to true/false, indicating whether or not they have been vectorized already - - $use_only_param_position = false ## whether or not to use just an arg's parameter position, or use all of its uses in a method, when running on BERT model - - def self.empty_cache! - #@meth_cache = {} - #@meth_to_cls_map = Hash.new {|h, m| h[m] = Set.new} # maps a method to a set of classes with that method - #@ast_cache = {} - #@twin_cache = Hash.new { |h, k| h[k] = {} } - #@bert_cache = Hash.new { |h, k| h[k] = {} } - #@vectorized_vars = {} - send_query({action: "empty_cache!"}) if $use_twin_network - #RDL::Globals.parser_cache = Hash.new - #RDL::Typecheck.empty_cache! - #RDL::Type::StructuralType.clear_cache! - #RDL::Type::VarType.clear_cache! - #RDL::Type::NominalType.clear_cache! - #RDL::Type::SingletonType.clear_cache! - #RDL::Type::IntersectionType.clear_cache! - #RDL::Type::VarargType.clear_cache! - #RDL::Type::DynamicType.clear_cache! - end - - def self.init_meth_to_cls # to be called before the first call to struct_to_nominal - ObjectSpace.each_object(Module).each do |c| - #unless c.to_s.start_with?("# 10) ## in this case, just keep the struct types + return if matching_classes.size > 10 ## in this case, just keep the struct types nom_sing_types = matching_classes.map { |c| if c.singleton_class? then RDL::Type::SingletonType.new(RDL::Util.singleton_class_to_class(c)) else RDL::Type::NominalType.new(c) end } RDL::Logging.log :heuristic, :trace, "These are: %s" % (nom_sing_types*", ") union = RDL::Type::UnionType.new(*nom_sing_types).canonical @@ -156,498 +82,11 @@ def self.struct_to_nominal(var_type) return union ## used to add and propagate here. Now that this is a heuristic, this should be done after running the rule. #var_type.add_and_propagate_upper_bound(union, nil) - end - - - def self.twin_network_guess(var_type) - return unless (var_type.category == :arg) || (var_type.category == :var) || (var_type.category == :ret) ## this rule only applies to args and (instance/class/global) variables - name1 = var_type.base_name#var_type.category == :ret ? var_type.meth.to_s : var_type.name.to_s - #sols = [] - sols = {} - - - uri = URI "http://127.0.0.1:5000/" - - RDL::Typecheck.type_names_map.each { |t, names| - - sum = 0 - count = 0 - names.each { |name| - count += 1 - if @twin_cache[name1][name] - sum += @twin_cache[name1][name] - else - params = { words: [name1, name], action: "twin" } - uri.query = URI.encode_www_form(params) - res = Net::HTTP.get_response(uri) - raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" - sum += res.body.to_f - @twin_cache[name1][name] = res.body.to_f - end - } - sim_score = sum / count - -=begin -## Below was query approach before implementing caching. - params = { words: [name1] + names } - uri.query = URI.encode_www_form(params) - res = Net::HTTP.get_response(uri) - puts "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" - - sim_score = res.body.to_f -=end - if sim_score > @simscore_cutoff - #puts "Twin network found #{name1} and list #{names} have average similarity score of #{sim_score}.".green - #puts "Adding #{t} as a potential solution." - #sols << t - sols[sim_score] = t - else - #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red - end - } - #puts "Done querying all of type_names_map".green - - ## return list of types that are sorted from highest similarity score to lowest - return sols.sort.map { |sim_score, t| t }.reverse - - ## TODO: Is creating UnionType the right way to go? - #return RDL::Type::UnionType.new(*sols).canonical - -=begin - params = { in1: name1, in2: name2 } - uri.query = URI.encode_www_form(params) - - res = Net::HTTP.get_response(uri) - if res.msg != "OK" - puts "Failed to make request to twin network server. Received response #{res.body}." - return nil - end - - sim_score = res.body.to_f - if sim_score > @simscore_cutoff - puts "Twin network found #{name1} and #{name2} have similarity score of #{sim_score}." - puts "Attempting to apply Integer as solution." - ## TODO: once we replace "count" above, also have to replace Integer as solution. - return RDL::Globals.types[:integer] - else - puts "Twin network found insufficient similarity score of #{sim_score} between #{name1} and #{name2}." - return nil - end -=end - - end - - - def self.twin_network_constraints(pairs_enum) - sols = {} - pairs_enum.each { |var1, var2| - name1 = var1.base_name - name2 = var2.base_name - - if @twin_cache[name1][name2] - sols[[var1, var2]] = @twin_cache[name1][name2] - else - res = send_query(params) - sols[[var1, var2]] = res.body.to_f - @twin_cache[name1][name2] = res.body.to_f - end - } - sorted = sols.sort_by { |k, v| v }.reverse - sorted.each { |vars, score| - #puts "Score: #{score}. Vars: [#{vars[0]}, #{vars[1]}" - var1, var2 = vars - if score > 0.9 - new_cons = {} - begin - var1.add_and_propagate_upper_bound(var2, nil, new_cons) - var1.add_and_propagate_lower_bound(var2, nil, new_cons) - RDL::Typecheck.set_new_constraints if !new_cons.empty? - rescue => e - RDL::Typecheck.undo_constraints(new_cons) - end - end - } - - - end - - def self.send_query(params) - uri = URI "http://127.0.0.1:5000/" - uri.query = URI.encode_www_form(params) - #res = Net::HTTP.get_response(uri) - res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https', :read_timeout => 9000) { |http| res = http.request_get(uri) } - #res = $http.request_get(uri) - raise "Failed to make request to twin network server. Received response #{res.body}." unless res.msg == "OK" - return res - end - - class CastRemover < Parser::TreeRewriter - ## Want to compare *original* sources, excluding RDL.type_casts which can affect comparison - def on_send(t) - if (t.children[1] == :type_cast) && (t.children[0] && t.children[0].loc.expression.source == "RDL") - replace(t.loc.expression, t.children[2].loc.expression.source) - end - super - end - end - - def self.ast_without_casts(klass, method) - return @ast_cache[[klass, method]] if @ast_cache[[klass, method]] - ast = RDL::Typecheck.get_ast(klass, method) - return nil if ast.nil? - ast = Parser::CurrentRuby.parse(ast.loc.expression.source) ## need to re-parse to get rid of offset issue - remover = CastRemover.new - buffer = Parser::Source::Buffer.new("(ast)") - buffer.source = ast.location.expression.source - new_source = remover.rewrite(buffer, ast) - new_ast = Parser::CurrentRuby.parse new_source - @ast_cache[[klass, method]] = new_ast - return new_ast - end - - def self.vectorize_var(var_type) - return true if @vectorized_vars[var_type.object_id] ## already vectorized and cached server side - #puts "About to vectorize var #{var_type}" - if (var_type.category == :arg) - ast = ast_without_casts(var_type.cls, var_type.meth) - return nil if ast.nil? - locs = get_var_loc(ast, var_type) - source = ast.loc.expression.source - #puts "Querying for x`var #{var_type.base_name}" - #puts "Sanity check: " - #locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } - params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "arg" } - send_query(params) - elsif (var_type.category == :var) - var_type.meths_using_var.each { |klass, meth| - ast = ast_without_casts(klass, meth) - locs = get_var_loc(ast, var_type) - source = ast.loc.expression.source - #puts "Querying for var #{var_type.name} in method #{klass}##{meth}" - #puts "Sanity check: " - #locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } - params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "var", average: false } - send_query(params) - } - send_query({ action: "bert_vectorize", object_id: var_type.object_id, category: "var", average: true }) - elsif (var_type.category == :ret) - ast = ast_without_casts(var_type.cls, var_type.meth) - return nil if ast.nil? - begin_pos = ast.loc.expression.begin_pos - locs = [ast.loc.name.begin_pos - begin_pos, ast.loc.name.end_pos - begin_pos-1] ## for now, let's just try using method name - locs = locs + get_ret_sites(ast) unless $namesonly - locs = [ast.loc.name.begin_pos - begin_pos, ast.loc.name.end_pos - begin_pos-1] if locs.empty? - return nil if locs.empty? - source = ast.loc.expression.source - #puts "Querying for return #{var_type}" - #puts "Sanity check: " - #locs.each_slice(2) { |b, e| puts " #{source[b..e]} from #{b}..#{e}" } - params = { source: source, action: "bert_vectorize", object_id: var_type.object_id, locs: locs, category: "ret" } - send_query(params) - else - #puts "not implemented yet for vartype #{var_type}" - return nil - end - @vectorized_vars[var_type.object_id] = true - return true - end - - - ## [+ var_kind +] is either :arg, :ret, or :var. - def self.visualize_bert(var_kind) - var_id_list = [] - RDL::Globals.constrained_types.each { |klass, mname| - type = RDL::Globals.info.get(klass, mname, :type) - orig_type = RDL::Globals.info.get(klass, mname, :orig_type) - if orig_type.nil? - #puts "Could not find original type for #{klass}##{mname}.".red unless $collecting_time_data - next - end - if type.is_a?(Array) - next if var_kind == :var - raise "Expected just one method type for #{klass}#{mname}." unless type.size == 1 - type = type[0] - orig_type = orig_type[0] - if var_kind == :arg - type.args.each_with_index { |arg_type, i| - arg_type = arg_type.type if arg_type.optional_var_type? || arg_type.vararg_var_type? - next if arg_type.is_a?(RDL::Type::FiniteHashType) - vectorize_var(arg_type) - name = "#{klass}##{mname}/#{arg_type.base_name}" - params = { action: "add_info", object_id: arg_type.object_id, type: orig_type.args[i].to_s, name: name } - send_query(params) - var_id_list << arg_type.object_id - } - else - raise "Expected var_kind of :ret, got #{var_kind}." unless var_kind == :ret - ret_type = type.ret - next unless ret_type.is_a?(RDL::Type::VarType) - vectorize_var(ret_type) - name = "#{klass}##{mname}/ret" - params = { action: "add_info", object_id: ret_type.object_id, type: orig_type.ret.to_s[0..15], name: name } - send_query(params) - var_id_list << ret_type.object_id - end - else - next unless var_kind == :var - vectorize_var(type) - name = "#{klass}##{mname}/#{type.name}" - params = { action: "add_info", object_id: type.object_id, type: orig_type.to_s, name: name } - send_query(params) - var_id_list << type.object_id - end - } - params = { action: "visualize", id_list: var_id_list } - send_query(params) end - def self.fast_bert_model_guess(var_type) - return unless (var_type.category == :arg) || (var_type.category == :var) || (var_type.category == :ret) - #puts "Vectorizing #{var_type} of object id #{var_type.object_id}..." unless $collecting_time_data - ret = vectorize_var(var_type) - return nil if ret.nil? - - sols = {} - to_compare_vars_same_kind = [] - to_compare_types_same_kind = [] - to_compare_vars_diff_kind = [] - to_compare_types_diff_kind = [] - - RDL::Typecheck.type_vars_map.each { |t, vars| - raise "Got here for #{t}" if vars.empty? - vars.each { |var2| - next if (var_type == var2) || !((var2.category == :arg) || (var2.category == :var) || (var2.category == :ret)) #!(var_type.category == var2.category) - #next if !(var_type.category == var2.category) - next if ((var_type.category == :ret) || (var2.category == :ret)) && !(var_type.category == var2.category) # only compare rets with rets - if sim_score = @bert_cache[var_type][var2] - #puts "Hit cache for vars #{var_type} and #{var2}, with score of #{sim_score}".red unless $collecting_time_data - message = "Received similarity score of #{sim_score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" - if sim_score > @simscore_cutoff - #puts message.green unless $collecting_time_data - sols[sim_score] = t - else - #puts message.red unless $collecting_time_data - end - else - #puts "Vectorizing #{var2} of object id #{var2.object_id}..." unless $collecting_time_data - vec_res = vectorize_var(var2) - if vec_res.nil? - #puts "Could not find AST for #{var2}".yellow unless $collecting_time_data - next - end - if (var_type.category == var2.category) || (var_type.category == :var) || (var2.category == :var) - to_compare_vars_same_kind << var2 - to_compare_types_same_kind << t - else - to_compare_vars_diff_kind << var2 - to_compare_types_diff_kind << t - end - end - } - } - - unless to_compare_vars_same_kind.empty? - to_compare_ids = to_compare_vars_same_kind.map { |v| v.object_id } - #kind = (var_type.category == :var) ? "cosine" : var_type.category.to_s ## kind of comparison to make - kind = (var_type.category == :var) ? "arg" : var_type.category.to_s ## kind of comparison to make - #kind = "cosine" - params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind, namesonly: $namesonly, headeronly: $headeronly } - res = send_query(params) - res = JSON.parse(res.body) - raise "Expected #{to_compare_ids.size} similarity scores, received #{res.size}" unless res.size == to_compare_ids.size - res.each_with_index { |score, i| - var2 = to_compare_vars_same_kind[i] - t = to_compare_types_same_kind[i] - @bert_cache[var_type][var2] = score - message = "Received similarity score of #{score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" - if score > @simscore_cutoff - #puts message.green unless $collecting_time_data - sols[score] = t - else - #puts message.red unless $collecting_time_data - end - } - end - unless to_compare_vars_diff_kind.empty? - to_compare_ids = to_compare_vars_diff_kind.map { |v| v.object_id } - #kind = "cosine" - kind = "arg" - params = { action: "get_similarities", id1: var_type.object_id, id_comps: to_compare_ids, kind: kind, namesonly: $namesonly, headeronly: $headeronly } - res = send_query(params) - res = JSON.parse(res.body) - raise "Expected #{to_compare_ids.size} similarity scores, received #{res.size}" unless res.size == to_compare_ids.size - res.each_with_index { |score, i| - var2 = to_compare_vars_diff_kind[i] - t = to_compare_types_diff_kind[i] - @bert_cache[var_type][var2] = score - message = "Received similarity score of #{score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" - if score > @simscore_cutoff - #puts message.green unless $collecting_time_data - sols[score] = t - else - #puts message.red unless $collecting_time_data - end - } - end - - res = sols.sort.map { |sim_score, t| t }.reverse.uniq#.first(@top_n) - @nums_above_cutoff[var_type] = res.size - return res.first($topn) - end - - def self.bert_model_guess(var_type) - if (var_type.category == :arg) || (var_type.category == :var) || (var_type.category == :ret) - sols = {} - - vectorize_var(var_type) - - RDL::Typecheck.type_vars_map.each { |t, vars| - sum = 0 - count = 0 - raise "Got here for #{t}" if vars.empty? - vars.each { |var2| - next if (var_type == var2) || !((var2.category == :arg) || (var2.category == :var) || (var2.category == :ret)) #!(var_type.category == var2.category) - #next if !(var_type.category == var2.category) - next if ((var_type.category == :ret) || (var2.category == :ret)) && !(var_type.category == var2.category) # only compare rets with rets - count += 1 - if @bert_cache[var_type][var2] - #puts "Hit cache for vars #{var_type} and #{var2}, with score of #{@bert_cache[var_type][var2]}".red unless $collecting_time_data - #sum += @bert_cache[var_type][var2] - sim_score = @bert_cache[var_type][var2] - else - vec_res = vectorize_var(var2) - if vec_res.nil? - #puts "Could not find AST for #{var2}".yellow unless $collecting_time_data - next - end - #puts "About to ask for similarity of #{var_type} and #{var2}" unless $collecting_time_data - res = send_query({ action: "get_similarity", id1: var_type.object_id, id2: var2.object_id, kind1: var_type.category.to_s, kind2: var2.category.to_s, namesonly: $namesonly, headeronly: $headeronly }) - #sum += res.body.to_f - sim_score = res.body.to_f - @bert_cache[var_type][var2] = res.body.to_f - @bert_cache[var2][var_type] = res.body.to_f - end - message = "Received similarity score of #{sim_score} for vars #{var_type.cls}##{var_type.meth}##{var_type.name} and #{var2.cls}##{var2.meth}##{var2.name}" - if sim_score > @simscore_cutoff - #puts message.green unless $collecting_time_data - sols[sim_score] = t - else - #puts message.red unless $collecting_time_data - #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red - end - } -=begin - ## TRYING COMPARE WITH SINGLE VAR DOWN HERE. - sim_score = (count == 0) ? 0 : sum / count - puts "Received overall sim_score average of #{sim_score} for var #{var_type.cls}##{var_type.meth}##{var_type.name} and type #{t}".green if sim_score != 0 - - - if sim_score > @simscore_cutoff - sols[sim_score] = t - else - #puts "Twin network found insufficient average similarity score of #{sim_score} between #{name1} and #{names}.".red - end -=end - } - ## return list of types that are sorted from highest similarity score to lowest - return sols.sort.map { |sim_score, t| t }.reverse.uniq - - - else - #puts "not yet implemented" - end - end - - def self.get_ret_sites(ast) - begin_pos = ast.loc.expression.begin_pos - locs = [] - - if ast.type == :def - meth_name, args, body = *ast - elsif ast.type == :defs - _, meth_name, args, body = *ast - else - raise RuntimeError, "Unexpected ast type #{ast.type}" - end - - return [] if body.nil? - - ret_sites = RDL::Typecheck.find_ret_sites(body) - - ret_sites.each { |exp, _| - if (exp.type == :send) || (exp.type == :csend) - if exp.loc.selector - locs << exp.loc.selector.begin_pos - begin_pos - locs << exp.loc.selector.end_pos - begin_pos - end - else - locs << (exp.loc.expression.begin_pos - begin_pos) - locs << (exp.loc.expression.end_pos - begin_pos - 1) - end - } - return locs - end - - def self.get_var_loc(ast, var_type) - begin_pos = ast.loc.expression.begin_pos - locs = [] - - if ast.type == :def - meth_name, args, body = *ast - elsif ast.type == :defs - _, meth_name, args, body = *ast - else - raise RuntimeError, "Unexpected ast type #{ast.type}" - end - - if (var_type.category == :arg) - args.children.each { |c| - if (c.children[0].to_s == var_type.base_name) - ## Found the arg corresponding to var_type - locs << (c.loc.name.begin_pos - begin_pos) ## translate it so that 0 is first position - locs << (c.loc.name.end_pos - begin_pos - 1) - return locs if $headeronly #$use_only_param_position - end - } - #return locs if $headeronly - end - - search_ast_for_var_locs(body, var_type.name, locs, begin_pos) - - raise "Expected even number of locations." unless (locs.length % 2) == 0 - raise "Did not find var #{var_type} anywhere in ast #{ast}." if locs.length == 0 - return locs.sort ## locs will be sorted Array, where every two ints designate the beginning/end of an occurence of the arg in var_type - - end - - ## Recursively earches ast [Parser::AST::Node] for uses of local variable called var_name [String]. - ## Adds begining/end locations to locs Array, translating first by amount in - ## begin_pos [Integer]. - def self.search_ast_for_var_locs(ast, var_name, locs, begin_pos) - if !ast.is_a?(Parser::AST::Node) - return ## reached non-node of AST, stop recursing here. - elsif (ast.type == :lvar) || (ast.type == :ivar) || (ast.type == :cvar) || (ast.type == :gvar) - if (ast.children[0].to_s == var_name.to_s) - locs << (ast.loc.expression.begin_pos - begin_pos) - locs << (ast.loc.expression.end_pos - begin_pos - 1) - return - else - return - end - elsif (ast.type == :lvasgn) || (ast.type == :ivasgn) || (ast.type == :cvasgn) || (ast.type == :gvasgn) - if (ast.children[0].to_s == var_name.to_s) - locs << (ast.loc.name.begin_pos - begin_pos) - locs << (ast.loc.name.end_pos - begin_pos - 1) - end - search_ast_for_var_locs(ast.children[1], var_name, locs, begin_pos) - else - ast.children.each { |c| search_ast_for_var_locs(c, var_name, locs, begin_pos) } - end - end end - class << RDL::Heuristic attr_reader :rules end @@ -674,6 +113,7 @@ def model_set_type end end + if defined? Rails RDL::Heuristic.add(:is_model) { |var| if var.base_name.camelize.is_rails_model? then var.base_name.to_type end } RDL::Heuristic.add(:is_pluralized_model) { |var| if var.base_name.is_pluralized_model? then var.base_name.model_set_type end } @@ -684,8 +124,6 @@ def model_set_type RDL::Heuristic.add(:int_array_name) { |var| if var.base_name.end_with?("ids") || (var.base_name.end_with? "nums") || (var.base_name.end_with? "counts") then RDL::Globals.parser.scan_str "#T Array" end } RDL::Heuristic.add(:predicate_method) { |var| if var.base_name.end_with?("?") then RDL::Globals.types[:bool] end } RDL::Heuristic.add(:string_name) { |var| if var.base_name.end_with?("name") then RDL::Globals.types[:string] end } - - RDL::Heuristic.add(:hash_access) { |var| old_var = var var = var.type if old_var.is_a?(RDL::Type::OptionalType) @@ -709,11 +147,11 @@ def model_set_type raise "Method should be one of :[] or :[]=, got #{meth}." end if value_type.is_a?(RDL::Type::UnionType) - RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg, false) }).drop_vars.canonical + RDL::Type::UnionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical elsif value_type.is_a?(RDL::Type::IntersectionType) RDL::Type::IntersectionType.new(*value_type.types.map { |t| RDL::Typecheck.extract_var_sol(t, :arg) }).drop_vars.canonical else - value_type = RDL::Typecheck.extract_var_sol(value_type, :arg, false) + value_type = RDL::Typecheck.extract_var_sol(value_type, :arg) end #value_type = value_type.drop_vars!.canonical if (value_type.is_a?(RDL::Type::UnionType) || value_type.is_a?(RDL::Type::IntersectionType)) && (!value_type.types.all? { |t| t.is_a?(RDL::Type::VarType) }) hash_typ[typ.args[0].val] = RDL::Type::UnionType.new(value_type, hash_typ[typ.args[0].val]).canonical#RDL::Type::OptionalType.new(value_type) ## TODO: @@ -729,8 +167,5 @@ def model_set_type end } -### For rules involving :include?, :==, :!=, etc. we would need to track the exact receiver/args used in the method call, and somehow store these in the bounds created for a var type. - -RDL::Heuristic.add(:twin_network) { |var| RDL::Heuristic.fast_bert_model_guess(var) } - +### For rules involving :include?, :==, :!=, etc. we would need to track the exact receiver/args used in the method call, and somehow store these in the bounds created for a var type. From 761da459eb5b72fe65dd0f2b6f0307d216300aac Mon Sep 17 00:00:00 2001 From: Mark Aldrich Date: Wed, 15 Jun 2022 16:37:14 -0400 Subject: [PATCH 112/124] skipped test --- test/test_infer.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_infer.rb b/test/test_infer.rb index bd5e8e86..1b7a1016 100644 --- a/test/test_infer.rb +++ b/test/test_infer.rb @@ -84,8 +84,11 @@ def assert_type_equal(meth, expected_type, depends_on: []) assert expected_type.match(typ.solution), error_str end - def self.should_have_type(meth, typ, depends_on: []) + def self.should_have_type(meth, typ, depends_on: [], shouldSkip: false) define_method "test_#{meth}" do + if shouldSkip + skip + end assert_type_equal meth, tm(typ), depends_on: depends_on end end @@ -145,7 +148,8 @@ def repeat def repeat_n(n) 'a' * n end - should_have_type :repeat_n, '(Numeric) -> String' + should_have_type :repeat_n, '(Numeric) -> String', shouldSkip: true + # skipped because of `number_mode`. # Note: The last type in the unions below comes from requiring `sorbet` (via # requiring `parlour`) to render RBI files. The Structural -> Nominal From 117e735d37662d4c506527f43a03aba6f1573957 Mon Sep 17 00:00:00 2001 From: Mark Aldrich Date: Fri, 17 Jun 2022 12:34:03 -0400 Subject: [PATCH 113/124] added render: json type --- lib/types/rails/action_controller/instrumentation.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/types/rails/action_controller/instrumentation.rb b/lib/types/rails/action_controller/instrumentation.rb index e18c17c3..d5138f16 100644 --- a/lib/types/rails/action_controller/instrumentation.rb +++ b/lib/types/rails/action_controller/instrumentation.rb @@ -1,6 +1,7 @@ RDL.nowrap :'ActionController::Instrumentation' -RDL.type :'ActionController::Instrumentation', :render, '(String or Symbol) -> Array' -RDL.type :'ActionController::Instrumentation', :render, '(?String or Symbol, {content_type: ?String, layout: ?%bool or String, action: ?String or Symbol, location: ?String, nothing: ?%bool, file: ?String, text: ?[to_s: () -> String], status: ?Symbol, content_type: ?String, formats: ?Symbol or Array, locals: ?Hash}) -> Array' +#RDL.type :'ActionController::Instrumentation', :render, '(String or Symbol) -> Array' +RDL.type :'ActionController::Instrumentation', :render, '(?String or Symbol, {content_type: ?String, json: ?String, layout: ?%bool or String, action: ?String or Symbol, location: ?String, nothing: ?%bool, file: ?String, text: ?[to_s: () -> String], status: ?Symbol, content_type: ?String, formats: ?Symbol or Array, locals: ?Hash}) -> Array' RDL.type :'ActionController::Instrumentation', :redirect_to, '(?(String or Symbol or ActiveRecord::Base), {controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' + #RDL.type :'ActionController::Instrumentation', :redirect_to, '({controller: ?String, action: String, notice: ?String, alert: ?String}) -> String' ## When no first argument is provided, `action` must be present in options. #RDL.type :'ActionController::Instrumentation', :redirect_to, '(String or Symbol or ActiveRecord::Base, ?{controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' From 124d38e892937c2b755648936434420e41c993df Mon Sep 17 00:00:00 2001 From: Mark Aldrich Date: Fri, 17 Jun 2022 12:35:17 -0400 Subject: [PATCH 114/124] readded the simpler render type --- lib/types/rails/action_controller/instrumentation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/rails/action_controller/instrumentation.rb b/lib/types/rails/action_controller/instrumentation.rb index d5138f16..47c15a94 100644 --- a/lib/types/rails/action_controller/instrumentation.rb +++ b/lib/types/rails/action_controller/instrumentation.rb @@ -1,5 +1,5 @@ RDL.nowrap :'ActionController::Instrumentation' -#RDL.type :'ActionController::Instrumentation', :render, '(String or Symbol) -> Array' +RDL.type :'ActionController::Instrumentation', :render, '(String or Symbol) -> Array' RDL.type :'ActionController::Instrumentation', :render, '(?String or Symbol, {content_type: ?String, json: ?String, layout: ?%bool or String, action: ?String or Symbol, location: ?String, nothing: ?%bool, file: ?String, text: ?[to_s: () -> String], status: ?Symbol, content_type: ?String, formats: ?Symbol or Array, locals: ?Hash}) -> Array' RDL.type :'ActionController::Instrumentation', :redirect_to, '(?(String or Symbol or ActiveRecord::Base), {controller: ?String, action: ?String, notice: ?String, alert: ?String}) -> String' From 93b0ab11e165156e446c6050e9b90212d2497447 Mon Sep 17 00:00:00 2001 From: Mark Aldrich Date: Fri, 17 Jun 2022 13:36:03 -0400 Subject: [PATCH 115/124] log global type constraints --- lib/rdl/constraint.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 83a39771..377b0ab5 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -4,6 +4,7 @@ module RDL::Typecheck def self.resolve_constraints RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." + RDL::Logging.log_header :inference, :trace, "Current constraints: #{RDL::Globals.constrained_types}" RDL::Globals.constrained_types.each { |klass, name| RDL::Logging.log :inference, :debug, "Resolving constraints from #{RDL::Util.pp_klass_method(klass, name)}" typ = RDL::Globals.info.get(klass, name, :type) From 061bb2141efe26f93da10513e2664fa03594a178 Mon Sep 17 00:00:00 2001 From: Mark Aldrich Date: Fri, 17 Jun 2022 13:39:33 -0400 Subject: [PATCH 116/124] undo; didn't log the correct info --- lib/rdl/constraint.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/rdl/constraint.rb b/lib/rdl/constraint.rb index 377b0ab5..f370f24f 100644 --- a/lib/rdl/constraint.rb +++ b/lib/rdl/constraint.rb @@ -6,7 +6,6 @@ def self.resolve_constraints RDL::Logging.log_header :inference, :info, "Starting constraint resolution..." RDL::Logging.log_header :inference, :trace, "Current constraints: #{RDL::Globals.constrained_types}" RDL::Globals.constrained_types.each { |klass, name| - RDL::Logging.log :inference, :debug, "Resolving constraints from #{RDL::Util.pp_klass_method(klass, name)}" typ = RDL::Globals.info.get(klass, name, :type) ## If typ is an Array, then it's an array of method types ## but for inference, we only use a single method type. From 0d800e08613ba5024574d441f4e9c277a9100e55 Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Fri, 10 Nov 2023 16:45:08 -0600 Subject: [PATCH 117/124] subtyping annotated arg --- lib/rdl/types/annotated_arg.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/rdl/types/annotated_arg.rb b/lib/rdl/types/annotated_arg.rb index b518c160..38260693 100644 --- a/lib/rdl/types/annotated_arg.rb +++ b/lib/rdl/types/annotated_arg.rb @@ -56,5 +56,13 @@ def optional? def vararg? return type.vararg? end + + def <=(other) + if other.is_a? AnnotatedArgType + Type.leq(@type, other.type) + else + Type.leq(@type, other) + end + end end end From 4a255e113b877ef277405603d23fd5d666f5cf14 Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Fri, 10 Nov 2023 17:45:40 -0600 Subject: [PATCH 118/124] explicit keyword args in Ruby 3 --- lib/rdl/contracts/proc.rb | 8 ++++---- lib/rdl/typecheck.rb | 2 +- lib/rdl/wrap.rb | 2 +- test/test_typecheck.rb | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/rdl/contracts/proc.rb b/lib/rdl/contracts/proc.rb index 8816bf79..b1eeb774 100644 --- a/lib/rdl/contracts/proc.rb +++ b/lib/rdl/contracts/proc.rb @@ -9,11 +9,11 @@ def initialize(pre_cond:nil, post_cond:nil) def wrap(slf, &blk) - Proc.new {|*v, &other_blk| - @pre_cond.check(slf, *v, &other_blk) - tmp = other_blk ? slf.instance_exec(*v, other_blk, &blk) : slf.instance_exec(*v, &blk) # TODO fix blk + Proc.new {|*v, **kv, &other_blk| + @pre_cond.check(slf, *v, **kv, &other_blk) + tmp = other_blk ? slf.instance_exec(*v, **kv, &other_blk) : slf.instance_exec(*v, **kv, &blk) # TODO fix blk # tmp = blk.call(*v, &other_blk) # TODO: Instance eval with self - @post_cond.check(slf, tmp, *v, &other_blk) + @post_cond.check(slf, tmp, *v, **kv, &other_blk) tmp } end diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index e257e13b..54e05a5e 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -2181,7 +2181,7 @@ def align_combine(range, offset, attributes) raise IndexError, "The range #{range} is outside the bounds of the source of size #{@source_buffer.source.size}" end dummy_range = Parser::Source::Range.new(@source_buffer, range.begin_pos - offset, range.end_pos - offset) - action = TreeRewriter::Action.new(dummy_range, @enforcer, attributes) + action = TreeRewriter::Action.new(dummy_range, @enforcer, **attributes) @action_root = @action_root.combine(action) self end diff --git a/lib/rdl/wrap.rb b/lib/rdl/wrap.rb index cd289eee..b8e275ae 100644 --- a/lib/rdl/wrap.rb +++ b/lib/rdl/wrap.rb @@ -144,7 +144,7 @@ def self.process_type_args(default_class, *args) else raise ArgumentError, "Invalid arguments to `type`" end - raise ArgumentError, "Excepting method type, got #{type.class} instead" if type.class != RDL::Type::MethodType + raise ArgumentError, "Expecting method type, got #{type.class} instead" if type.class != RDL::Type::MethodType # meth = :initialize if meth && slf && meth.to_sym == :new # actually wrap constructor klass = RDL::Util.add_singleton_marker(klass) if slf return [klass, meth, type] diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 5807c6ff..581cd00f 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -1819,7 +1819,7 @@ def foo(x) when :b end end - type(:foo, '(Symbol) -> NilClass', {:typecheck => :call}) + type(:foo, '(Symbol) -> NilClass', typecheck: :call) end assert_nil TestTypecheck::A5.new.foo(:a) From 3b98676b80b163787b1a0a0a9d6f432b9a130894 Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Fri, 10 Nov 2023 17:45:54 -0600 Subject: [PATCH 119/124] change of BigDecimal API --- test/test_lib_types.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_lib_types.rb b/test/test_lib_types.rb index 6b127d20..4ff42aff 100644 --- a/test/test_lib_types.rb +++ b/test/test_lib_types.rb @@ -81,9 +81,9 @@ def test_bigdecimal BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) - BigDecimal.new(BigDecimal('Infinity')) - BigDecimal.new(BigDecimal('-Infinity')) - BigDecimal(BigDecimal.new('NaN')) + BigDecimal(BigDecimal('Infinity')) + BigDecimal(BigDecimal('-Infinity')) + BigDecimal(BigDecimal('NaN')) end BigDecimal.save_limit do BigDecimal.limit(200) @@ -106,10 +106,10 @@ def test_bigmath # From the Ruby stdlib documentation BigMath.E(10) BigMath.PI(10) - BigMath.atan(BigDecimal.new('-1'), 16) + BigMath.atan(BigDecimal('-1'), 16) BigMath.cos(BigMath.PI(4), 16) BigMath.sin(BigMath.PI(5)/4, 5) - BigMath.sqrt(BigDecimal.new('2'), 16) + BigMath.sqrt(BigDecimal('2'), 16) end def test_class From 1bad367779f03c6dbedb61085cd3fd506c39256e Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Fri, 10 Nov 2023 18:11:52 -0600 Subject: [PATCH 120/124] github actions CI --- .github/workflows/test.yml | 29 +++++++++++++++++++++++++++++ .travis.yml | 35 ----------------------------------- Gemfile | 4 ++++ Gemfile.lock | 30 ++++++++++++++++++++++++++++++ gemfiles/Gemfile.travis | 5 ----- rdl.gemspec | 5 ++++- 6 files changed, 67 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml create mode 100644 Gemfile create mode 100644 Gemfile.lock delete mode 100644 gemfiles/Gemfile.travis diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..c02c3a64 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: RDL Test + +on: + push: + branches: [ master, dev ] + pull_request: + branches: [ master, dev ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + ruby-version: ['2.7.6', '3.2.2'] + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + - name: Build and quick test with Rake + run: | + gem install bundler + bundle install --jobs 4 --retry 3 + bundle exec rake test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5fdcb0b4..00000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -language: ruby -gemfile: - - gemfiles/Gemfile.travis -rvm: - - 2.1.1 - - 2.1.6 - - 2.1.7 - - 2.1.8 - - 2.2.0 - - 2.2.1 - - 2.2.2 - - 2.2.3 - - 2.2.4 - - 2.2.5 - - 2.2.6 - - 2.3.0 - - 2.3.1 - - 2.3.2 - - 2.3.3 - - 2.3.4 - - 2.4.0 - - 2.4.1 - - 2.4.2 - - 2.4.3 - - 2.4.4 - - 2.4.5 - - 2.5.5 - - 2.6.2 - - 2.6.3 -notifications: - email: - recipients: - - rdl-devel@googlegroups.com - on_success: never # default: change - on_failure: always # default: always diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..de7eca4e --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +# Specify your gem's dependencies in rdl.gemspec +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..c57468b9 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,30 @@ +PATH + remote: . + specs: + rdl (2.1.0) + parser (~> 3.2, >= 3.2.2.4) + sql-parser (~> 0.0.2) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + minitest (5.20.0) + parser (3.2.2.4) + ast (~> 2.4.1) + racc + racc (1.7.3) + rake (13.0.6) + sql-parser (0.0.2) + racc + +PLATFORMS + arm64-darwin-21 + +DEPENDENCIES + minitest (~> 5.10) + rake (~> 13.0.1) + rdl! + +BUNDLED WITH + 2.3.22 diff --git a/gemfiles/Gemfile.travis b/gemfiles/Gemfile.travis deleted file mode 100644 index 2fc104a5..00000000 --- a/gemfiles/Gemfile.travis +++ /dev/null @@ -1,5 +0,0 @@ -source 'https://rubygems.org' - -gem 'rake' -gem 'minitest' -gem 'parser' diff --git a/rdl.gemspec b/rdl.gemspec index 34e5fe32..0068c967 100644 --- a/rdl.gemspec +++ b/rdl.gemspec @@ -18,6 +18,9 @@ EOF s.executables << 'rdl_query' s.homepage = 'https://github.com/plum-umd/rdl' s.license = 'BSD-3-Clause' - s.add_runtime_dependency 'parser', '~>2.3', '>= 2.3.1.4' + s.add_runtime_dependency 'parser', '~> 3.2', '>= 3.2.2.4' s.add_runtime_dependency 'sql-parser', '~>0.0.2' + + s.add_development_dependency 'rake', '~> 13.0.1' + s.add_development_dependency 'minitest', '~> 5.10' end From 60abf92200a6dc3557676ef613b0e9bf4f8f00ba Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Fri, 10 Nov 2023 18:14:21 -0600 Subject: [PATCH 121/124] change to generic platform --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index c57468b9..5ab80f8e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -19,7 +19,7 @@ GEM racc PLATFORMS - arm64-darwin-21 + ruby DEPENDENCIES minitest (~> 5.10) From 10f23673d1d9c7b2de6291c76a5b4bf93c1921cc Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Fri, 10 Nov 2023 18:16:31 -0600 Subject: [PATCH 122/124] change testing task --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c02c3a64..025b1e83 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - - name: Build and quick test with Rake + - name: Run Tests run: | gem install bundler bundle install --jobs 4 --retry 3 From f412ff937ac0a19dd40641ed137fb11208ec40b1 Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Thu, 16 Nov 2023 15:06:42 -0600 Subject: [PATCH 123/124] update gemfile.lock --- Gemfile.lock | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5ab80f8e..3c05a00b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,10 @@ PATH remote: . specs: - rdl (2.1.0) + rdl (2.2.0) + colorize (~> 0.8, >= 0.8.1) + method_source + parlour (~> 2.1.0, >= 2.1.0) parser (~> 3.2, >= 3.2.2.4) sql-parser (~> 0.0.2) @@ -9,12 +12,25 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) + coderay (1.1.3) + colorize (0.8.1) + commander (4.6.0) + highline (~> 2.0.0) + highline (2.0.3) + method_source (1.0.0) minitest (5.20.0) + parlour (2.1.0) + commander (~> 4.5) + parser + rainbow (~> 3.0) + sorbet-runtime (>= 0.5) parser (3.2.2.4) ast (~> 2.4.1) racc racc (1.7.3) + rainbow (3.1.1) rake (13.0.6) + sorbet-runtime (0.5.11132) sql-parser (0.0.2) racc @@ -22,6 +38,7 @@ PLATFORMS ruby DEPENDENCIES + coderay (~> 1.1, >= 1.1.2) minitest (~> 5.10) rake (~> 13.0.1) rdl! From 3991afe82285f93b18ab813047e8b0bde2fea9eb Mon Sep 17 00:00:00 2001 From: Sankha Narayan Guria Date: Thu, 16 Nov 2023 15:25:33 -0600 Subject: [PATCH 124/124] fix type error in ruby 3.2.2 --- lib/rdl/typecheck.rb | 2 +- test/test_typecheck.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rdl/typecheck.rb b/lib/rdl/typecheck.rb index 87c46e67..b556e1ec 100644 --- a/lib/rdl/typecheck.rb +++ b/lib/rdl/typecheck.rb @@ -1686,7 +1686,7 @@ def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, case trecv when RDL::Type::SingletonType if trecv.val.is_a? Class or trecv.val.is_a? Module - if (meth == :new) && trecv.val.methods.include?(:new) && trecv.val.method(:new).owner == Class then ## last condition makes sure :new isn't overriden + if (meth == :new) && trecv.val.methods.include?(:new) && trecv.val.method(:new).owner.is_a?(Class) then ## last condition makes sure :new isn't overriden meth_lookup = :initialize init = true trecv_lookup = trecv.val.to_s diff --git a/test/test_typecheck.rb b/test/test_typecheck.rb index 4e21d33d..f6b56b40 100644 --- a/test/test_typecheck.rb +++ b/test/test_typecheck.rb @@ -1247,6 +1247,7 @@ def test_instantiate RDL.type :Array, :initialize, '() -> self', wrap: false RDL.type :Array, :initialize, '(Integer) -> self', wrap: false RDL.type :Array, :initialize, '(Integer, t) -> self', wrap: false + assert ( self.class.class_eval { type "(Integer, Integer) -> Array", typecheck: :now @@ -1260,7 +1261,6 @@ def def_inst_pass(x, y) a = Array.new(x,y); RDL.instantiate!(a, "Integer"); a; e } ) -=begin # below works with computational types assert_raises(RDL::Typecheck::StaticTypeError) { self.class.class_eval { @@ -1268,7 +1268,7 @@ def def_inst_pass(x, y) a = Array.new(x,y); RDL.instantiate!(a, "Integer"); a; e def def_inst_hash_fail2(x) hash = {}; hash.instantiate("Integer", "String") ; hash["test"] = x; hash["test"]; end } } -=end + assert( self.class.class_eval { type "(Integer) -> Integer", typecheck: :now