Module: RDL

Extended by:
Annotate
Defined in:
lib/rdl_disable.rb,
lib/rdl/boot.rb,
lib/rdl/boot.rb,
lib/rdl/wrap.rb,
lib/rdl/query.rb,
lib/rdl_disable.rb,
lib/rdl/types/type_inferencer.rb

Overview

Defines RDL methods that do nothing

Defined Under Namespace

Modules: Annotate, Contract, Globals, RDLAnnotate, Type, Typecheck Classes: Config, Info, Query, Rails, Switch, TypeInferencer, Util, Wrap

Class Method Summary collapse

Methods included from Annotate

attr_accessor_type, attr_reader_type, attr_writer_type, post, pre, rdl_alias, readd_comp_types, type, type_params, var_type

Class Method Details

.at(*args) ⇒ Object

Register [+ blk ] to be executed when ‘rdl_do_typecheck [ sym +]` is called. The blk will be called with sym as its argument. The order in which multiple blks for the same sym will be executed is unspecified



581
582
583
584
# File 'lib/rdl/wrap.rb', line 581

def self.at(sym, &blk)
  RDL::Globals.to_do_at[sym] = [] unless RDL::Globals.to_do_at[sym]
  RDL::Globals.to_do_at[sym] << blk
end

.check_type_codeObject



680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
# File 'lib/rdl/wrap.rb', line 680

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::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"
    type_list = "(RDL::Type::Type, Array<RDL::Type::Type>"
    (typ.args+[typ.ret]+[typ.block]).each { |t|
      ## First collect all bindings to be used during type checking.
      if (t.is_a?(RDL::Type::BoundArgType))
        arg_list << ", #{t.name}"
        type_list << ", #{t.type.class}"
      end
    }
    arg_list << ")"
    type_list << ")"
    code_type = RDL::Globals.parser.scan_str "#{type_list} -> RDL::Type::Type"
    (typ.args+[typ.ret]+[typ.block]).each { |t|
      if t.is_a?(RDL::Type::ComputedType)
        meth = cleanse_meth_name(meth)
        if klass.to_s.include?("::") ## hacky way around namespace issue
          tmp_meth =  "def klass.tc_#{meth}#{count}#{arg_list} #{t.code}; end"
          tmp_eval = "klass = #{klass} ; #{tmp_meth}"
        else
          tmp_meth = tmp_eval = "def #{klass}.tc_#{meth}#{count}#{arg_list} #{t.code}; end"
        end
        eval tmp_eval
        ast = Parser::CurrentRuby.parse tmp_meth
        RDL::Typecheck.typecheck("[s]#{klass}", "tc_#{meth}#{count}".to_sym, ast, [code_type], [[:-, :+]]) 
        count += 1
      end
    }
  }
  RDL.do_typecheck :type_code
  RDL.config { |config| config.use_comp_types = true }
  true
end

.cleanse_meth_name(meth) ⇒ Object



719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
# File 'lib/rdl/wrap.rb', line 719

def self.cleanse_meth_name(meth)
  meth = meth.to_s
  meth.gsub!("%", "percent")
  meth.gsub!("&", "ampersand")
  meth.gsub!("*", "asterisk")
  meth.gsub!("+", "plus")
  meth.gsub!("-", "dash")
  meth.gsub!("@", "at")
  meth.gsub!("/", "slash")
  meth.gsub!("<", "lt")
  meth.gsub!(">", "gt")
  meth.gsub!("=", "eq")
  meth.gsub!("[", "lbracket")
  meth.gsub!("]", "rbracket")
  meth.gsub!("^", "carrot")
  meth.gsub!("|", "line")
  meth.gsub!("~", "line")
  meth.gsub!("?", "qmark")
  meth.gsub!("!", "bang")
  meth
end

.config(*args) {|RDL::Config.instance| ... } ⇒ Object

Yields:



10
11
12
# File 'lib/rdl/boot.rb', line 10

def RDL.config
  yield(RDL::Config.instance)
end

.deinstantiate!(*args) ⇒ Object

Raises:

  • (RuntimeError)


794
795
796
797
798
799
800
801
# File 'lib/rdl/wrap.rb', line 794

def self.deinstantiate!(obj)
  klass = obj.class.to_s
  klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
  raise RuntimeError, "Class #{self.to_s} is not parameterized" unless RDL::Globals.type_params[klass]
  raise RuntimeError, "Instance is not instantiated" unless obj.instance_variable_get(:@__rdl_type).instance_of?(RDL::Type::GenericType)
  obj.instance_variable_set(:@__rdl_type, nil)
  obj
end

.do_typecheck(*args) ⇒ Object

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.



588
589
590
591
592
593
594
595
596
597
598
599
# File 'lib/rdl/wrap.rb', line 588

def self.do_typecheck(sym)
  if RDL::Globals.to_do_at[sym]
    RDL::Globals.to_do_at[sym].each { |blk| blk.call(sym) }
  end
  RDL::Globals.to_do_at[sym] = Array.new
  return unless RDL::Globals.to_typecheck[sym]
  RDL::Globals.to_typecheck[sym].each { |klass, meth|
    RDL::Typecheck.typecheck(klass, meth)
  }
  RDL::Globals.to_typecheck[sym] = Set.new
  nil
end

.instantiate!(*args) ⇒ Object

typs

is an array of types, classes, symbols, or strings to instantiate

the type parameters. If a class, symbol, or string is given, it is converted to a NominalType.

Raises:

  • (RuntimeError)


764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
# File 'lib/rdl/wrap.rb', line 764

def self.instantiate!(obj, *typs, check: false)
  klass = obj.class.to_s
  klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
  formals, _, all = RDL::Globals.type_params[klass]
  raise RuntimeError, "Receiver is of class #{klass}, which is not parameterized" unless formals
  raise RuntimeError, "Expecting #{formals.size} type parameters, got #{typs.size}" unless formals.size == typs.size
  raise RuntimeError, "Instance already has type instantiation" if obj.instance_variable_defined?(:@__rdl_type) && obj.instance_variable_get(:@__rdl_type)
  new_typs = typs.map { |t| if t.is_a? RDL::Type::Type then t else RDL::Globals.parser.scan_str "#T #{t}" end }
  t = RDL::Type::GenericType.new(RDL::Type::NominalType.new(klass), *new_typs)
  if check
    if all.instance_of? Symbol
      obj.send(all) { |*objs|
        new_typs.zip(objs).each { |nt, o|
          if nt.instance_of? RDL::Type::GenericType # require o to be instantiated
            t_o = RDL::Util.rdl_type(o)
            raise RDL::Type::TypeError, "Expecting element of type #{nt.to_s}, but got uninstantiated object #{o.inspect}" unless t_o
            raise RDL::Type::TypeError, "Expecting type #{nt.to_s}, got type #{t_o.to_s}" unless t_o <= nt
          else
            raise RDL::Type::TypeError, "Expecting type #{nt.to_s}, got #{o.inspect}" unless nt.member? o
          end
        }
      }
    else
      raise RDL::Type::TypeError, "Not an instance of #{t}" unless instance_exec(*new_typs, &all)
    end
  end
  obj.instance_variable_set(:@__rdl_type, t)
  obj
end

.load_rails_schemaObject



624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/rdl/wrap.rb', line 624

def self.load_rails_schema
  return unless defined?(Rails)
  ::Rails.application.eager_load! # load Rails app
  models = ActiveRecord::Base.descendants.each { |m|
    begin
      ## load schema for each Rails model
      m.send(:load_schema) unless m.abstract_class?
    rescue
    end }

  models.each { |model|
    next if model.to_s == "ApplicationRecord"
    next if model.to_s == "GroupManager"
    RDL.nowrap model
    s1 = {}
    model.columns_hash.each { |k, v| t_name = v.type.to_s.camelize
      ## Map SQL column types to the corresponding RDL type
      if t_name == "Boolean"
        t_name = "%bool"
        s1[k] = RDL::Globals.types[:bool]
      elsif t_name == "Datetime"
        t_name = "DateTime or Time"
        s1[k] = RDL::Type::UnionType.new(RDL::Type::NominalType.new(Time), RDL::Type::NominalType.new(DateTime))
      elsif t_name == "Text"
        ## difference between `text` and `string` is in the SQL types they're mapped to, not in Ruby types
        t_name = "String"
        s1[k] = RDL::Globals.types[:string]
      else
        s1[k] = RDL::Type::NominalType.new(t_name)
      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
    }
    s2 = s1.transform_keys { |k| k.to_sym }
    assoc = {}
    model.reflect_on_all_associations.each { |a|
      ## 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.name.to_s.camelize.singularize}>", 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
      end
    }
    s2[:__associations] = RDL::Type::FiniteHashType.new(assoc, nil)
    base_name = model.to_s
    base_type = RDL::Type::NominalType.new(model.to_s)
    hash_type = RDL::Type::FiniteHashType.new(s2, nil)
    schema = RDL::Type::GenericType.new(base_type, hash_type)
    RDL::Globals.ar_db_schema[base_name.to_sym] = schema
  }
end

.load_sequel_schema(db) ⇒ Object



601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
# File 'lib/rdl/wrap.rb', line 601

def self.load_sequel_schema(db)
  db.tables.each { |table|
    hash_str = "{ "
    kl_name = table.to_s.camelize.singularize
    db.schema(table).each { |col|
      hash_str << "#{col[0]}: "
      typ = col[1][:type].to_s.camelize
      if typ == "Datetime"
        typ = "DateTime or Time" ## Sequel accepts both
      elsif typ == "Boolean"
        typ = "%bool"
      elsif typ == "Text"
        typ = "String"
      end
      hash_str << "#{typ},"
      RDL.type kl_name, col[0], "() -> #{typ}", wrap: false
      RDL.type kl_name, "#{col[0]}=", "(#{typ}) -> #{typ}", wrap: false
    }
    hash_str.chomp!(",") << " }"
    RDL::Globals.seq_db_schema[table] = RDL::Globals.parser.scan_str "#T #{hash_str}"
  }
end

.note_type(*args) ⇒ Object

Does nothing at run time



742
743
744
# File 'lib/rdl/wrap.rb', line 742

def self.note_type(x)
  return x
end

.nowrap(*args) ⇒ Object



573
574
575
576
# File 'lib/rdl/wrap.rb', line 573

def self.nowrap(klass=self)
  RDL.config { |config| config.add_nowrap(klass) }
  nil
end

.query(*args) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/rdl/query.rb', line 72

def self.query(q)
  RDL::Globals.contract_switch.off {
    if q =~ /^[A-Z]\w*(::[A-Z]\w*)*(#|\.)([a-z_]\w*(!|\?|=)?|!|~|\+|\*\*|-|\*|\/|%|<<|>>|&|\||\^|<|<=|=>|>|==|===|!=|=~|!~|<=>|\[\]|\[\]=)$/
      typs = RDL::Query.method_query(q)
      if typs.nil? then
        puts "No types for #{q}"
      else
        typs.each { |t|
          puts "#{q}: #{t}"
        }
      end
    elsif q =~ /^[A-Z]\w*(::[A-Z]\w*)*$/
      typs = RDL::Query.class_query(q)
      if typs.nil? then
        puts "No method types for #{q}"
      else
        typs.each { |m, t| puts "#{m}: #{t}"}
      end
    elsif q =~ /\(.*\)/
      typs = RDL::Query.method_type_query(q)
      if typs.empty? then
        puts "No matching methods"
      else
        typs.each { |m, t| puts "#{m}: #{t}" }
      end
    else
      raise "Don't know how to handle query"
    end
    nil
  }
end

.remove_type(*args) ⇒ Object

Raises:

  • (RuntimeError)


746
747
748
749
750
# File 'lib/rdl/wrap.rb', line 746

def self.remove_type(klass, meth)
  raise RuntimeError, "No existing type for #{RDL::Util.pp_klass_method(klass, meth)}" unless RDL::Globals.info.has? klass, meth, :type
  RDL::Globals.info.remove klass, meth, :type
  nil
end

.resetObject



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/rdl/boot.rb', line 149

def self.reset
  RDL::Globals.module_eval {
    @info = RDL::Info.new
    @wrapped_calls = Hash.new 0
    @type_params = Hash.new
    @aliases = Hash.new
    @to_wrap = Set.new
    @to_typecheck = Hash.new
    @to_typecheck[:now] = Set.new
    @to_do_at = Hash.new
    @ar_db_schema = Hash.new
    @seq_db_schema = Hash.new
    @deferred = []

    @parser = RDL::Type::Parser.new

    # Map from file names to [digest, cache] where 2nd elt maps
    #  :ast to the AST
    #  :line_defs maps linenumber to AST for def at that line
    @parser_cache = Hash.new

    # Some generally useful types; not really a big deal to do this since
    # NominalTypes are cached, but these names are shorter to type
    @types = Hash.new
    @types[:nil] = RDL::Type::NominalType.new NilClass # actually creates singleton type
    @types[:top] = RDL::Type::TopType.new
    @types[:bot] = RDL::Type::BotType.new
    @types[:dyn] = RDL::Type::DynamicType.new
    @types[:object] = RDL::Type::NominalType.new Object
    @types[:true] = RDL::Type::NominalType.new TrueClass # actually creates singleton type
    @types[:false] = RDL::Type::NominalType.new FalseClass # also singleton type
    @types[:bool] = RDL::Type::UnionType.new(@types[:true], @types[:false])
    @types[:float] = RDL::Type::NominalType.new Float
    @types[:complex] = RDL::Type::NominalType.new Complex
    @types[:rational] = RDL::Type::NominalType.new Rational
    @types[:integer] = RDL::Type::NominalType.new Integer
    @types[:numeric] = RDL::Type::NominalType.new Numeric
    @types[:string] = RDL::Type::NominalType.new String
    @types[:array] = RDL::Type::NominalType.new Array
    @types[:hash] = RDL::Type::NominalType.new Hash
    @types[:symbol] = RDL::Type::NominalType.new Symbol
    @types[:range] = RDL::Type::NominalType.new Range
    @types[:regexp] = RDL::Type::NominalType.new Regexp
    @types[:standard_error] = RDL::Type::NominalType.new StandardError
    @types[:proc] = RDL::Type::NominalType.new Proc

    # Hash from special type names to their values
    @special_types = {'%any' => @types[:top],
                      '%bot' => @types[:bot],
                      '%bool' => @types[:bool],
                      '%dyn' => @types[:dyn]}
  }
end

.type_alias(*args) ⇒ Object

Add a new type alias.

name

must be a string beginning with %.

typ

can be either a string, in which case it will be parsed

into a type, or a Type.

Raises:

  • (RuntimeError)


559
560
561
562
563
564
565
566
567
568
569
570
571
# File 'lib/rdl/wrap.rb', line 559

def self.type_alias(name, typ)
  raise RuntimeError, "Attempt to redefine type #{name}" if RDL::Globals.special_types[name]
  case typ
  when String
    t = RDL::Globals.parser.scan_str "#T #{typ}"
    RDL::Globals.special_types[name] = t
  when RDL::Type::Type
    RDL::Globals.special_types[name] = typ
  else
    raise RuntimeError, "Unexpected typ argument #{typ.inspect}"
  end
  nil
end

.type_cast(*args) ⇒ Object

Returns a new object that wraps self in a type cast. If force is true this cast is unchecked, so use with caution

Raises:

  • (RuntimeError)


753
754
755
756
757
758
759
# File 'lib/rdl/wrap.rb', line 753

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
  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)
  new_obj
end