├── README.rdoc ├── Rakefile ├── lib ├── auto_typesig.rb ├── rtc.rb ├── rtc │ ├── annot_lexer.rex │ ├── annot_lexer.rex.rb │ ├── annot_parser.racc │ ├── annot_parser.tab.rb │ ├── annotated.rb │ ├── logging.rb │ ├── options.rb │ ├── parser.rb │ ├── position.rb │ ├── proxy_object.rb │ ├── runtime │ │ ├── class_loader.rb │ │ ├── master_switch.rb │ │ ├── method_check.rb │ │ ├── method_wrapper.rb │ │ ├── native.rb │ │ └── type_inferencer.rb │ ├── tools │ │ └── hash-builder.rb │ ├── typing │ │ ├── base_types.rb │ │ ├── base_types2.rb │ │ ├── noncore_base_types │ │ │ └── base_csv.rb │ │ ├── type_signatures.rb │ │ ├── types.rb │ │ └── types │ │ │ ├── bottom.rb │ │ │ ├── hash.rb │ │ │ ├── intersection.rb │ │ │ ├── lazy_nominal.rb │ │ │ ├── nominal.rb │ │ │ ├── optional.rb │ │ │ ├── parameterized.rb │ │ │ ├── procedural.rb │ │ │ ├── structural.rb │ │ │ ├── symbol.rb │ │ │ ├── terminal.rb │ │ │ ├── top.rb │ │ │ ├── touch │ │ │ ├── tuple.rb │ │ │ ├── type.rb │ │ │ ├── type_parameter.rb │ │ │ ├── type_variables.rb │ │ │ ├── union.rb │ │ │ └── vararg.rb │ └── utils.rb ├── rtc_lib.rb ├── rtc_profile.rb └── rtc_profiler.rb ├── rtc.gemspec └── test ├── test_lazy_types.rb ├── test_options.rb ├── test_proxy.rb ├── test_proxy_call.rb ├── test_proxy_call_tmp.rb └── unit ├── test_autowrap.rb ├── test_blocks.rb ├── test_class_annotations.rb ├── test_frozen.rb ├── test_frozen_prertc.rb ├── test_hash_types.rb ├── test_parser.rb ├── test_polymorphic_methods.rb ├── test_structural_types.rb ├── test_type_checking.rb └── test_types.rb /Rakefile: -------------------------------------------------------------------------------- 1 | # Rakefile for rubydust gem 2 | # 3 | # Ryan W Sims (rwsims@umd.edu) 4 | 5 | require 'rake' 6 | require 'rake/clean' 7 | require 'rake/testtask' 8 | 9 | # Prevent OS X from including extended attribute junk in the tar output 10 | ENV['COPY_EXTENDED_ATTRIBUTES_DISABLE'] = 'true' 11 | 12 | CLEAN.include("lib/rtc/annot_parser.tab.rb", "lib/rtc/annot_lexer.rex.rb") 13 | 14 | desc "Default task" 15 | task :default => :rtc 16 | 17 | file "lib/rtc/annot_parser.tab.rb" => "lib/rtc/annot_parser.racc" do 18 | sh "racc lib/rtc/annot_parser.racc" 19 | end 20 | 21 | file "lib/rtc/annot_lexer.rex.rb" => "lib/rtc/annot_lexer.rex" do 22 | sh "rex --matcheos lib/rtc/annot_lexer.rex" 23 | end 24 | 25 | desc "Generates the lexer from the .rex file" 26 | task :lexer => "lib/rtc/annot_lexer.rex.rb" 27 | 28 | desc "Generates the parser from the .racc file" 29 | task :parser => [:lexer,"lib/rtc/annot_parser.tab.rb"] 30 | 31 | desc "Builds rtc" 32 | task :rtc => :parser 33 | 34 | Rake::TestTask.new do |t| 35 | t.test_files = FileList["test/unit/*.rb"] 36 | end 37 | 38 | task :test => :rtc 39 | -------------------------------------------------------------------------------- /lib/auto_typesig.rb: -------------------------------------------------------------------------------- 1 | # Purpose 2 | # This file uses the run-time types used in a unit test file to 3 | # build a typesig template for the class. 4 | # 5 | # How to use? 6 | # 7 | # 1. In this file, change $cls to the class in the unit test 8 | # 2. In the unit test file, add "require 'auto_typesig'" on top 9 | # 3. Run the unit test as usual 10 | # 4. Typesigs will be written to typesig_#{cls}.rb 11 | # In the result typesig file 12 | # * Each run-time type is added as a separate typesig 13 | # * Each intersection type has an inferred (and possibly incorrect) 14 | # typesig as a comment right below it. 15 | # * Each type in the commented typesig is created using the least 16 | # common ancestor class in the corresponding type position. 17 | # * typesigs are sorted based on method names 18 | # * intersection typesigs are sorted based on the number of arguments 19 | 20 | # Problems 21 | # 1. Does not work with a lot of parameterized stuff 22 | # 2. Inferred typesigs may be incorrect 23 | # 3. No support for class methods 24 | # 4. Block types are not recorded 25 | 26 | require 'set' 27 | require 'rtc_lib' 28 | 29 | $cls = Fixnum 30 | $outfile = "typesig_#{$cls}.rb" 31 | $auto_typesigs = {} 32 | $typesigh = {} 33 | $info = [] 34 | 35 | class AutoTypesig 36 | attr_accessor :arg_types 37 | attr_accessor :ret_type 38 | 39 | def initialize(arg_types, ret_type) 40 | @arg_types = arg_types 41 | @ret_type = ret_type 42 | end 43 | end 44 | 45 | $gid= 0 46 | 47 | module Spec 48 | def auto_typesig(m) 49 | om = "foo_#{$gid}" 50 | $gid = $gid + 1 51 | 52 | class_eval do 53 | alias_method om, m 54 | 55 | define_method(m) do |*args, &blk| 56 | r = self.send(om.to_sym, *args, &blk) 57 | $info.push([self.class, m, args, r]) 58 | r 59 | end 60 | end 61 | end 62 | end 63 | 64 | $cls.class_eval do 65 | extend Spec 66 | 67 | methods = self.instance_methods(false) 68 | deletes = [:old_equal?, :old_eql?, :old_eq] 69 | 70 | deletes.each {|d| 71 | methods.delete(d) 72 | } 73 | 74 | methods.each {|m| auto_typesig m} 75 | end 76 | 77 | 78 | module MiniTest 79 | class Unit 80 | alias :old_run :run 81 | 82 | def get_lca(argi) 83 | ancestors = argi.map {|a| 84 | if a.instance_of?(Rtc::Types::NominalType) 85 | a.klass.ancestors 86 | elsif a.instance_of?(Rtc::Types::ParameterizedType) 87 | a.nominal.klass.ancestors 88 | elsif a.instance_of?(Rtc::Types::BottomType) 89 | NilClass.ancestors 90 | else 91 | raise Exception, "Type not supported" 92 | end 93 | } 94 | 95 | lc = ancestors[0] 96 | ancestors.each {|a| lc = lc & a} 97 | lc[0] 98 | end 99 | 100 | 101 | def run args = [] 102 | old_run(args) 103 | 104 | puts "\nWriting typesigs for class #{$cls} in #{$outfile}..." 105 | 106 | info = Array.new($info) 107 | info.each {|c, m, args, ret| 108 | $typesigh[c] = {} if not $typesigh[c] 109 | $typesigh[c][m] = [] if not $typesigh[c][m] 110 | arg_types = args.map {|a| a.rtc_type} 111 | ret_type = ret.rtc_type 112 | t = Rtc::Types::ProceduralType.new([], ret_type, arg_types) 113 | $typesigh[c][m].push(t) 114 | } 115 | 116 | $typesigh.each {|cls, data| 117 | f = File.new($outfile, 'w') 118 | 119 | begin 120 | f.puts "class #{cls}\n rtc_annotated\n\n" 121 | 122 | data.sort.each {|m, tlist| 123 | t = Rtc::Types::IntersectionType.of(tlist) 124 | m = "\'#{m}\'" if m.match(/[^A-za-z0-9]/) 125 | 126 | if t.instance_of?(Rtc::Types::IntersectionType) 127 | sh = {} 128 | max_size = 0 129 | num_types = t.types.size 130 | 131 | t.types.map {|t| 132 | s = t.arg_types.size 133 | sh[s] = [] if not sh[s] 134 | sh[s].push(t) 135 | max_size = s if max_size < s 136 | } 137 | 138 | t = sh.sort.to_a.map {|a, b| b} 139 | t.flatten! 140 | 141 | args = t.map {|t| t.arg_types} 142 | rets = t.map {|t| t.return_type} 143 | 144 | if num_types > 1 145 | nt = [] 146 | i = 0 147 | 148 | while i < max_size 149 | argi = args.map {|a| a.values_at(i)} 150 | argi.flatten! 151 | argi.select! {|v| v != nil} 152 | lc = get_lca(argi) 153 | nt.push(lc.rtc_type) 154 | i += 1 155 | end 156 | 157 | nrt = get_lca(rets).rtc_type 158 | end 159 | else 160 | t = [t] 161 | end 162 | 163 | t.each {|t| 164 | t = t.to_s[2..-3] 165 | f.puts " typesig(\"#{m}: #{t}\")" 166 | } 167 | 168 | if nt 169 | nt.map! {|t| t.to_s[8..-2]} 170 | nt = "(" + nt.map {|t| "#{t}"}.join(", ") + ")" 171 | nrt = nrt.to_s[8..-2] 172 | typesig2 = " # typesig(\"#{m}: #{nt} -> #{nrt}\")" 173 | f.puts typesig2 174 | end 175 | } 176 | 177 | f.puts "end" 178 | rescue Exception => e 179 | f.close 180 | puts e.backtrace 181 | end 182 | } 183 | end 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /lib/rtc.rb: -------------------------------------------------------------------------------- 1 | # File to be included by client code. 2 | 3 | module Rtc 4 | Disabled = ENV.fetch("RTC_DISABLE", false) 5 | 6 | def self.setup 7 | yield self 8 | end 9 | end 10 | 11 | require 'rtc/annotated' 12 | require 'rtc/options' 13 | 14 | $RTC_STRICT = false 15 | $CHECK_COUNT = 0 16 | 17 | def rtc_annotated(*t_params) 18 | extend Rtc::Annotated 19 | add_type_parameters(t_params) 20 | end 21 | 22 | #FIXME(jtoman): needs a catchier and better name 23 | def rtc_no_subtype 24 | self.rtc_meta[:no_subtype] = true 25 | end 26 | 27 | def rtc_typesig(my_sig) 28 | raise "This form of class annotations is deprecated. Please use the rtc_annotated (t_param) form instead." 29 | end 30 | 31 | -------------------------------------------------------------------------------- /lib/rtc/annot_lexer.rex: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # The latest rexical generator on github supports matching against the 3 | # end of string. For this file to work correctly, you MUST use the 4 | # latest upstream rexical. 5 | ######################################################################## 6 | 7 | 8 | # ###################################################################### 9 | # DRuby annotation language parser 10 | # Adapted directly from DRuby source file typeAnnotationLexer.mll 11 | # Version of GitHub DRuby repo commit 0cda0264851bcdf6b301c3d7f564e9a3ee220e45 12 | # ###################################################################### 13 | module Rtc 14 | class TypeAnnotationParser 15 | 16 | macro 17 | SPACE_RE [\t\ ] 18 | CAP_RE [A-Z] 19 | LOW_RE [a-z] 20 | NUM_RE [0-9] 21 | ALPHA_RE [A-Za-z] 22 | ALPHANUM_RE [A-Za-z0-9] 23 | 24 | IDENT_RE \w 25 | 26 | INST_ID_RE [A-Za-z_]+\w* 27 | CONST_ID_RE [A-Z]+\w* 28 | TYPE_ID_RE [a-z_]+\w*\'? 29 | SCOPED_ID_RE ([A-Za-z_]+\w*|self)\.(\w|\[|\]|=)+[\?\!\=]? 30 | SUFFIXED_ID_RE [A-Za-z_]+\w*[\?\!\=] 31 | SYMBOL_RE :[A-Za-z_][A-Za-z_0-9]* 32 | TYPE_NAME_RE %[A-Za-z][A-Za-z_0-9]*'? 33 | #in order to match spaces, you have to define an RE. Yes this is ridiculous 34 | CLASS_RE class\ 35 | METACLASS_RE metaclass\ 36 | REQUIRE_RE require\ 37 | ALIAS_RE alias\ 38 | MODULE_RE require\ 39 | END_RE end\ 40 | TYPE_RE type\ 41 | TYPEVAR_RE typevar\ 42 | DOUBLE_HASH \#\# 43 | rule 44 | # rules take the form: 45 | # [:state] pattern [actions] 46 | 47 | # #################### 48 | # tokens 49 | # #################### 50 | 51 | {SPACE_RE}+ # nothing 52 | #{SPACE_RE}*\n{SPACE_RE}*\#\#\% # nothing 53 | ##\% { [:T_BEGIN_LINE, text] } 54 | #(?:[^\#\n][^\n]*)? # nothing 55 | \n{SPACE_RE}*\=begin { @state = :COMMENT } 56 | 57 | # keywords 58 | {CLASS_RE} { [:K_CLASS, text] } 59 | {METACLASS_RE} { [:K_METACLASS, text]} 60 | {MODULE_RE} { [:K_MODULE, text] } 61 | {ALIAS_RE} { [:K_ALIAS, text] } 62 | {REQUIRE_RE} { [:K_REQUIRE, text] } 63 | {END_RE} { [:K_END, text] } 64 | {TYPE_RE} { [:K_TYPE, text] } 65 | %none { [:T_BOTTOM, text] } 66 | %any { [:T_TOP, text] } 67 | %false { [:T_FALSE, text] } 68 | %true { [:T_TRUE, text] } 69 | %bool { [:T_BOOL, text] } 70 | {TYPE_NAME_RE} { [:T_TYPE_NAME, text[1..-1]] } 71 | 72 | 73 | # keywords 74 | or { [:K_OR, text] } 75 | self { [:K_SELF, text] } 76 | Tuple { [:K_TUPLE, text] } 77 | nil { [:K_NIL, text] } 78 | {TYPE_ID_RE} { [:T_LOCAL_ID, text] } 79 | {CONST_ID_RE} { [:T_CONST_ID, text] } 80 | {SCOPED_ID_RE} { [:T_SCOPED_ID, text] } 81 | {SUFFIXED_ID_RE} { [:T_SUFFIXED_ID, text] } 82 | {SYMBOL_RE} { [:T_SYMBOL, text] } 83 | {DOUBLE_HASH} { [:T_DOUBLE_HASH, text] } 84 | 85 | # built in type constructors 86 | \* { [:T_STAR, text] } 87 | \? { [:T_QUESTION, text] } 88 | \^ { [:T_CARROT, text] } 89 | 90 | \@FIXME {fail "ERROR at line #{lineno}: " + 91 | "deprecated @@FIXME in '#{text}', " + 92 | "use !FIXME"} 93 | # text can't contain "'", so gsub is okay 94 | '[^']*' { [:T_STRING, text.gsub("'", "")] } 95 | => { [:T_ASSOC, text] } 96 | 97 | \<\= { [:T_SUBTYPE, text] } 98 | @{INST_ID_RE} { [:T_IVAR, text] } 99 | @@{INST_ID_RE} { [:T_CVAR, text] } 100 | \${INST_ID_RE} { [:T_GVAR, text] } 101 | \! { [:T_BANG, text] } 102 | \:: { [:T_DOUBLE_COLON, text] } 103 | \: { [:T_COLON, text] } 104 | \. { [:T_DOT, text] } 105 | -> { [:T_RARROW, text] } 106 | \( { [:T_LPAREN, text] } 107 | \) { [:T_RPAREN, text] } 108 | \[ { [:T_LBRACKET, text] } 109 | \] { [:T_RBRACKET, text] } 110 | , { [:T_COMMA, text] } 111 | \{ { [:T_LBRACE, text] } 112 | \} { [:T_RBRACE, text] } 113 | < { [:T_LESS, text] } 114 | > { [:T_GREATER, text] } 115 | ; { [:T_SEMICOLON, text] } 116 | \n { } 117 | \= { [:T_EQUAL, text] } 118 | 119 | $ { @state = :END; [:T_EOF, ""] } 120 | :END $ { } 121 | 122 | 123 | # #################### 124 | # comments 125 | # #################### 126 | :COMMENT {SPACE_RE}*\=end[^\n]*\n { state = nil } 127 | :COMMENT [^\n]*\n # nothing 128 | inner 129 | 130 | def set_pos_ctx(this_pos, this_ctx) 131 | @pos = this_pos 132 | @ctx = this_ctx 133 | end 134 | 135 | def unset_pos_ctx 136 | @pos = nil 137 | @ctx = nil 138 | end 139 | 140 | def scan_str(str) 141 | scan_setup(str) 142 | @yydebug = true 143 | begin 144 | r = do_parse 145 | rescue => e 146 | fail e 147 | end 148 | r 149 | end 150 | 151 | end 152 | 153 | end 154 | -------------------------------------------------------------------------------- /lib/rtc/annot_lexer.rex.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # DO NOT MODIFY!!!! 3 | # This file is automatically generated by rex 1.0.5 4 | # from lexical definition file "lib/rtc/annot_lexer.rex". 5 | #++ 6 | 7 | require 'racc/parser' 8 | ######################################################################## 9 | # The latest rexical generator on github supports matching against the 10 | # end of string. For this file to work correctly, you MUST use the 11 | # latest upstream rexical. 12 | ######################################################################## 13 | 14 | 15 | # ###################################################################### 16 | # DRuby annotation language parser 17 | # Adapted directly from DRuby source file typeAnnotationLexer.mll 18 | # Version of GitHub DRuby repo commit 0cda0264851bcdf6b301c3d7f564e9a3ee220e45 19 | # ###################################################################### 20 | module Rtc 21 | class TypeAnnotationParser < Racc::Parser 22 | require 'strscan' 23 | 24 | class ScanError < StandardError ; end 25 | 26 | attr_reader :lineno 27 | attr_reader :filename 28 | attr_accessor :state 29 | 30 | def scan_setup(str) 31 | @ss = StringScanner.new(str) 32 | @lineno = 1 33 | @state = nil 34 | end 35 | 36 | def action 37 | yield 38 | end 39 | 40 | def scan_str(str) 41 | scan_setup(str) 42 | do_parse 43 | end 44 | alias :scan :scan_str 45 | 46 | def load_file( filename ) 47 | @filename = filename 48 | open(filename, "r") do |f| 49 | scan_setup(f.read) 50 | end 51 | end 52 | 53 | def scan_file( filename ) 54 | load_file(filename) 55 | do_parse 56 | end 57 | 58 | 59 | def next_token 60 | 61 | 62 | # skips empty actions 63 | until token = _next_token or @ss.eos?; end 64 | token 65 | end 66 | 67 | def _next_token 68 | text = @ss.peek(1) 69 | @lineno += 1 if text == "\n" 70 | token = case @state 71 | when nil 72 | case 73 | when (text = @ss.scan(/[\t ]+/)) 74 | ; 75 | 76 | when (text = @ss.scan(/\n[\t ]*\=begin/)) 77 | action { @state = :COMMENT } 78 | 79 | when (text = @ss.scan(/class /)) 80 | action { [:K_CLASS, text] } 81 | 82 | when (text = @ss.scan(/metaclass /)) 83 | action { [:K_METACLASS, text]} 84 | 85 | when (text = @ss.scan(/require /)) 86 | action { [:K_MODULE, text] } 87 | 88 | when (text = @ss.scan(/alias /)) 89 | action { [:K_ALIAS, text] } 90 | 91 | when (text = @ss.scan(/require /)) 92 | action { [:K_REQUIRE, text] } 93 | 94 | when (text = @ss.scan(/end /)) 95 | action { [:K_END, text] } 96 | 97 | when (text = @ss.scan(/type /)) 98 | action { [:K_TYPE, text] } 99 | 100 | when (text = @ss.scan(/%none/)) 101 | action { [:T_BOTTOM, text] } 102 | 103 | when (text = @ss.scan(/%any/)) 104 | action { [:T_TOP, text] } 105 | 106 | when (text = @ss.scan(/%false/)) 107 | action { [:T_FALSE, text] } 108 | 109 | when (text = @ss.scan(/%true/)) 110 | action { [:T_TRUE, text] } 111 | 112 | when (text = @ss.scan(/%bool/)) 113 | action { [:T_BOOL, text] } 114 | 115 | when (text = @ss.scan(/%[A-Za-z][A-Za-z_0-9]*'?/)) 116 | action { [:T_TYPE_NAME, text[1..-1]] } 117 | 118 | when (text = @ss.scan(/or/)) 119 | action { [:K_OR, text] } 120 | 121 | when (text = @ss.scan(/self/)) 122 | action { [:K_SELF, text] } 123 | 124 | when (text = @ss.scan(/Tuple/)) 125 | action { [:K_TUPLE, text] } 126 | 127 | when (text = @ss.scan(/nil/)) 128 | action { [:K_NIL, text] } 129 | 130 | when (text = @ss.scan(/[a-z_]+\w*\'?/)) 131 | action { [:T_LOCAL_ID, text] } 132 | 133 | when (text = @ss.scan(/[A-Z]+\w*/)) 134 | action { [:T_CONST_ID, text] } 135 | 136 | when (text = @ss.scan(/([A-Za-z_]+\w*|self)\.(\w|\[|\]|=)+[\?\!\=]?/)) 137 | action { [:T_SCOPED_ID, text] } 138 | 139 | when (text = @ss.scan(/[A-Za-z_]+\w*[\?\!\=]/)) 140 | action { [:T_SUFFIXED_ID, text] } 141 | 142 | when (text = @ss.scan(/:[A-Za-z_][A-Za-z_0-9]*/)) 143 | action { [:T_SYMBOL, text] } 144 | 145 | when (text = @ss.scan(/\#\#/)) 146 | action { [:T_DOUBLE_HASH, text] } 147 | 148 | when (text = @ss.scan(/\*/)) 149 | action { [:T_STAR, text] } 150 | 151 | when (text = @ss.scan(/\?/)) 152 | action { [:T_QUESTION, text] } 153 | 154 | when (text = @ss.scan(/\^/)) 155 | action { [:T_CARROT, text] } 156 | 157 | when (text = @ss.scan(/\@FIXME/)) 158 | action {fail "ERROR at line #{lineno}: " + 159 | "deprecated @@FIXME in '#{text}', " + 160 | "use !FIXME"} 161 | 162 | 163 | when (text = @ss.scan(/'[^']*'/)) 164 | action { [:T_STRING, text.gsub("'", "")] } 165 | 166 | when (text = @ss.scan(/=>/)) 167 | action { [:T_ASSOC, text] } 168 | 169 | when (text = @ss.scan(/\<\=/)) 170 | action { [:T_SUBTYPE, text] } 171 | 172 | when (text = @ss.scan(/@[A-Za-z_]+\w*/)) 173 | action { [:T_IVAR, text] } 174 | 175 | when (text = @ss.scan(/@@[A-Za-z_]+\w*/)) 176 | action { [:T_CVAR, text] } 177 | 178 | when (text = @ss.scan(/\$[A-Za-z_]+\w*/)) 179 | action { [:T_GVAR, text] } 180 | 181 | when (text = @ss.scan(/\!/)) 182 | action { [:T_BANG, text] } 183 | 184 | when (text = @ss.scan(/\::/)) 185 | action { [:T_DOUBLE_COLON, text] } 186 | 187 | when (text = @ss.scan(/\:/)) 188 | action { [:T_COLON, text] } 189 | 190 | when (text = @ss.scan(/\./)) 191 | action { [:T_DOT, text] } 192 | 193 | when (text = @ss.scan(/->/)) 194 | action { [:T_RARROW, text] } 195 | 196 | when (text = @ss.scan(/\(/)) 197 | action { [:T_LPAREN, text] } 198 | 199 | when (text = @ss.scan(/\)/)) 200 | action { [:T_RPAREN, text] } 201 | 202 | when (text = @ss.scan(/\[/)) 203 | action { [:T_LBRACKET, text] } 204 | 205 | when (text = @ss.scan(/\]/)) 206 | action { [:T_RBRACKET, text] } 207 | 208 | when (text = @ss.scan(/,/)) 209 | action { [:T_COMMA, text] } 210 | 211 | when (text = @ss.scan(/\{/)) 212 | action { [:T_LBRACE, text] } 213 | 214 | when (text = @ss.scan(/\}/)) 215 | action { [:T_RBRACE, text] } 216 | 217 | when (text = @ss.scan(//)) 221 | action { [:T_GREATER, text] } 222 | 223 | when (text = @ss.scan(/;/)) 224 | action { [:T_SEMICOLON, text] } 225 | 226 | when (text = @ss.scan(/\n/)) 227 | action { } 228 | 229 | when (text = @ss.scan(/\=/)) 230 | action { [:T_EQUAL, text] } 231 | 232 | when (text = @ss.scan(/$/)) 233 | action { @state = :END; [:T_EOF, ""] } 234 | 235 | when @@ss.scan(/$/) 236 | ; 237 | 238 | else 239 | text = @ss.string[@ss.pos .. -1] 240 | raise ScanError, "can not match: '" + text + "'" 241 | end # if 242 | 243 | when :END 244 | case 245 | when (text = @ss.scan(/$/)) 246 | action { } 247 | 248 | when @@ss.scan(/$/) 249 | ; 250 | 251 | else 252 | text = @ss.string[@ss.pos .. -1] 253 | raise ScanError, "can not match: '" + text + "'" 254 | end # if 255 | 256 | when :COMMENT 257 | case 258 | when (text = @ss.scan(/[\t ]*\=end[^\n]*\n/)) 259 | action { state = nil } 260 | 261 | when (text = @ss.scan(/[^\n]*\n/)) 262 | ; 263 | 264 | when @@ss.scan(/$/) 265 | ; 266 | 267 | else 268 | text = @ss.string[@ss.pos .. -1] 269 | raise ScanError, "can not match: '" + text + "'" 270 | end # if 271 | 272 | else 273 | raise ScanError, "undefined state: '" + state.to_s + "'" 274 | end # case state 275 | token 276 | end # def _next_token 277 | 278 | def set_pos_ctx(this_pos, this_ctx) 279 | @pos = this_pos 280 | @ctx = this_ctx 281 | end 282 | def unset_pos_ctx 283 | @pos = nil 284 | @ctx = nil 285 | end 286 | def scan_str(str) 287 | scan_setup(str) 288 | @yydebug = true 289 | begin 290 | r = do_parse 291 | rescue => e 292 | fail e 293 | end 294 | r 295 | end 296 | end # class 297 | 298 | end 299 | -------------------------------------------------------------------------------- /lib/rtc/annot_parser.racc: -------------------------------------------------------------------------------- 1 | # ###################################################################### 2 | # 3 | # DRuby annotation language parser 4 | # Adapted directly from DRuby source file typeAnnotationParser.mly 5 | # Version of GitHub DRuby repo commit 0cda0264851bcdf6b301c3d7f564e9a3ee220e435 6 | # 7 | # ###################################################################### 8 | 9 | class TypeAnnotationParser 10 | prechigh 11 | left T_COMMA 12 | right T_RARROW 13 | left K_OR 14 | preclow 15 | 16 | start entry 17 | 18 | token T_EOF 19 | token K_CLASS K_METACLASS K_MODULE K_INTERFACE K_TYPE K_TYPEVAR 20 | token K_ALIAS K_REQUIRE K_END 21 | token K_OR K_SELF K_TUPLE 22 | token T_BOTTOM T_TOP 23 | token T_BEGIN_LINE T_SEMICOLON 24 | token T_COLON T_DOUBLE_COLON T_DOT 25 | token T_STAR T_QUESTION 26 | token T_CARROT T_BANG 27 | token T_EQUAL T_ASSOC 28 | 29 | token T_RARROW 30 | token T_LPAREN T_RPAREN 31 | token T_LESS T_GREATER T_COMMA 32 | token T_LBRACKET T_RBRACKET 33 | token T_LBRACE T_RBRACE 34 | 35 | token T_TRUE T_BOOL T_FALSE 36 | token T_TYPE_NAME 37 | 38 | token T_SUBTYPE 39 | 40 | token T_STRING 41 | token T_IVAR T_CVAR T_GVAR 42 | token T_CONST_ID 43 | token T_TYPE_ID 44 | token T_SYMBOL 45 | # token T_METHOD_NAME 46 | token T_LOCAL_ID T_TICKED_ID T_SUFFIXED_ID 47 | token T_DOUBLE_HASH 48 | token K_NIL 49 | 50 | rule 51 | 52 | entry: 53 | e_method { result = val[0] } 54 | | e_field { result = val[0] } 55 | | e_annotation { result = val[0] } 56 | | e_type { result = val[0] } 57 | 58 | 59 | field_sig: 60 | T_IVAR T_COLON type_expr { 61 | result = handle_var(:ivar, val[0], val[2]) } 62 | | T_CVAR T_COLON type_expr { 63 | result = handle_var(:cvar, val[0], val[2]) } 64 | | T_GVAR T_COLON type_expr { 65 | result = handle_var(:gvar, val[0], val[2]) } 66 | 67 | /* entry points, presumably for non-IF annotations */ 68 | 69 | /* NOTE: none of these actually register the types -- that has to be 70 | done at a higher level (where the use for the signature will be known) */ 71 | 72 | e_method: 73 | method_annotation_list T_EOF { result = val[0] } 74 | 75 | e_field: 76 | field_annotation_list T_EOF { result = val[0] } 77 | 78 | e_annotation: 79 | T_DOUBLE_HASH type_expr T_EOF { result = val[1] } 80 | 81 | e_type: 82 | K_TYPE T_TYPE_NAME T_EQUAL type_expr T_EOF { result = Rtc::TypeAbbreviation.new(val[1], val[3]) } 83 | 84 | method_start: 85 | T_BEGIN_LINE { result = nil } 86 | | { result = nil } 87 | 88 | method_annotation_list: 89 | method_start const_method_type { result = [val[1]] } 90 | | method_start const_method_type method_annotation_list { 91 | result = [val[1]] + val[2] 92 | } 93 | 94 | field_start: 95 | T_BEGIN_LINE { result = nil } 96 | | { result = nil } 97 | 98 | field_annotation_list: 99 | field_start field_sig { result = [val[1]] } 100 | | field_start field_sig field_annotation_list { result = [val[1]] + val[2] } 101 | 102 | 103 | method_type: 104 | method_name T_LESS type_id_list T_GREATER T_COLON method_sig 105 | { result = handle_mtype(val[0], val[2], val[6]) } 106 | | method_name T_COLON method_sig 107 | { result = handle_mtype(val[0], nil, val[2]) } 108 | 109 | 110 | const_method_type: 111 | method_name T_LESS type_id_list T_GREATER T_COLON method_sig 112 | { result = handle_mtype(val[0], val[2], val[5]) } 113 | | method_name T_COLON method_sig 114 | { result = handle_mtype(val[0], nil, val[2]) } 115 | | T_CONST_ID T_LESS type_id_list T_GREATER T_COLON method_sig 116 | { result = handle_mtype(val[0], val[2], val[6]) } 117 | | T_CONST_ID T_COLON method_sig 118 | { result = handle_mtype(val[0], nil, val[2]) } 119 | | K_SELF T_DOT T_CONST_ID T_COLON method_sig { 120 | result = handle_mtype(ClassMethodIdentifier.new(val[2]), nil, val[4]) 121 | } 122 | | K_SELF T_DOT T_CONST_ID T_LESS type_id_list T_GREATER T_COLON method_sig 123 | { result = handle_mtype(ClassMethodIdentifier.new(val[2]), val[4], val[7]) } 124 | | method_sig { 125 | result = handle_mtype(MethodIdentifier.new("__rtc_next_method"), nil, val[0]) 126 | } 127 | 128 | relative_method_name: 129 | T_STRING 130 | { result = val[0] } 131 | | T_LOCAL_ID 132 | { result = val[0] } 133 | | T_SUFFIXED_ID 134 | { result = val[0] } 135 | 136 | method_name: 137 | relative_method_name { result = MethodIdentifier.new(val[0]) } 138 | | K_SELF T_DOT relative_method_name { result = ClassMethodIdentifier.new(val[2]) } 139 | 140 | method_sig: 141 | T_LPAREN T_RPAREN block T_RARROW type_expr { 142 | result = construct_msig([], val[2], val[4]) 143 | } 144 | | T_LPAREN method_arg_list T_RPAREN block T_RARROW /*named_*/ type_expr { 145 | result = construct_msig(val[1], val[3], val[5]) 146 | } 147 | | T_LPAREN method_arg_list T_RPAREN block { 148 | result = construct_msig(val[1], val[3], Rtc::Types::TopType.instance) 149 | } 150 | | T_LPAREN T_RPAREN block { 151 | result = construct_msig([], val[2], Rtc::Types::TopType.instance) 152 | } 153 | 154 | # kinda ridiculous 155 | method_arg_list: 156 | method_arg T_COMMA method_arg_list { 157 | result = val[2].unshift(val[0]) 158 | } 159 | | method_arg { 160 | result = [val[0]] 161 | } 162 | method_arg: 163 | T_QUESTION type_expr { 164 | result = Rtc::Types::OptionalArg.new(val[1]) 165 | } 166 | | T_STAR type_expr { 167 | result = Rtc::Types::Vararg.new(val[1]) 168 | } 169 | | type_expr { 170 | result = val[0] 171 | } 172 | 173 | 174 | block: 175 | { result = nil } 176 | | T_LBRACE method_sig T_RBRACE { result = handle_btype(val[1]) } 177 | 178 | type_id_list: 179 | type_var { result = [val[0]] } 180 | | type_var T_COMMA type_id_list { result = [val[0]] + val[2] } 181 | 182 | simple_type_var: 183 | T_LOCAL_ID { result = handle_type_param(:id, val[0]) } 184 | | K_SELF { result = handle_type_param(:self, val[0]) } 185 | 186 | type_var: 187 | simple_type_var { result = val[0] } 188 | | T_CARROT simple_type_var { 189 | result = handle_type_param(:varargs, val[1]) } 190 | 191 | 192 | type_ident_list: 193 | T_CONST_ID { result = [val[0]] } 194 | | T_CONST_ID T_DOUBLE_COLON type_ident_list { 195 | result = [val[0]] + val[2] 196 | } 197 | 198 | type_ident: 199 | T_DOUBLE_COLON type_ident_list { 200 | result = { 201 | :type => :absolute, 202 | :name_list => val[1] 203 | } 204 | } 205 | | type_ident_list { 206 | result = { 207 | :type => :relative, 208 | :name_list => val[0] 209 | } 210 | } 211 | 212 | type_expr: 213 | or_type_list { 214 | list = val[0][:or_list] 215 | if(list.length > 1) 216 | result = Rtc::Types::UnionType.new(list) 217 | else 218 | # flatten if there is no union 219 | result = list[0] 220 | end 221 | } 222 | 223 | or_type_list: 224 | single_type_expr { 225 | # before unions could only be built via this parser rule so we didn't 226 | # need to account for nested unions (they were impossible to construct 227 | # in the grammar). Now with type abbreviations, it's possible to construct 228 | # a nested union as follows: 229 | # type %foo = Fixnum or String 230 | # type %bar = Float or Proc 231 | # type %baz = %foo or %baz 232 | # so we need to flatten it as we parse an or expression, so that the above 233 | # yields the type Fixnum or String or Float or Proc 234 | if val[0].is_a?(Rtc::Types::UnionType) 235 | result = {:or_list => val[0].types.to_a } 236 | else 237 | result = {:or_list => [val[0]]} 238 | end } 239 | | single_type_expr K_OR or_type_list { 240 | if val[0].is_a?(Rtc::Types::UnionType) 241 | result = {:or_list => val[0].types.to_a + val[2][:or_list] } 242 | else 243 | result = {:or_list => [val[0]] + val[2][:or_list] } 244 | end 245 | } 246 | 247 | single_type_expr: 248 | type_var { result = val[0] } 249 | | T_SYMBOL { 250 | result = Rtc::Types::SymbolType.new(eval(val[0])) 251 | } 252 | | type_ident { 253 | result = handle_type_ident(val[0]) 254 | } 255 | | tuple { 256 | result = val[0] 257 | } /* tuples are just arrays */ 258 | | T_TOP { 259 | result = Rtc::Types::TopType.instance 260 | } 261 | | T_BOTTOM { 262 | result = Rtc::Types::BottomType.instance 263 | } 264 | | T_TRUE { 265 | result = Rtc::Types::NominalType.of(TrueClass) 266 | } 267 | | T_FALSE { 268 | result = Rtc::Types::NominalType.of(FalseClass) 269 | } 270 | | T_BOOL { 271 | result = Rtc::Types::UnionType.of([ 272 | Rtc::Types::NominalType.of(FalseClass), 273 | Rtc::Types::NominalType.of(TrueClass) 274 | ]) 275 | } 276 | | T_DOT T_QUESTION { 277 | result = Rtc::Types::TopType.instance 278 | } 279 | | T_LBRACKET field_or_method_list T_RBRACKET { 280 | result = handle_structural_type(val[1]) 281 | } 282 | | type_ident T_LESS type_expr_comma_list T_GREATER { 283 | nominal = handle_type_ident(val[0]) 284 | result = Rtc::Types::ParameterizedType.new(nominal, val[2]) 285 | } 286 | | T_LPAREN type_expr_comma_list T_RPAREN T_RARROW single_type_expr { 287 | result = Rtc::Types::ProceduralType.new(nil, val[4], val[1], nil) 288 | } 289 | | K_NIL { 290 | result = Rtc::Types::NominalType.of(NilClass) 291 | } 292 | | T_LBRACE hash_member_list T_RBRACE { 293 | result = Rtc::Types::HashType.new(val[1]) 294 | } 295 | | T_TYPE_NAME { 296 | result = get_type(val[0]) 297 | } 298 | 299 | hash_member_list: 300 | hash_key T_ASSOC hash_type T_COMMA hash_member_list { 301 | result = val[4] 302 | result[val[0]] = val[2] 303 | } 304 | | hash_key T_ASSOC hash_type { 305 | result = Rtc::NativeHash.new 306 | result[val[0]] = val[2] 307 | } 308 | 309 | hash_key: 310 | T_STRING { result = val[0] } 311 | | T_SYMBOL { result = eval(val[0]) } 312 | 313 | hash_type: 314 | T_QUESTION type_expr { 315 | result = Rtc::Types::OptionalArg.new(val[1]) 316 | } 317 | | type_expr { result = val[0] } 318 | 319 | tuple: 320 | K_TUPLE T_LESS type_expr_comma_list T_GREATER { 321 | result = Rtc::Types::TupleType.new(val[2]) 322 | } 323 | 324 | type_expr_comma_list: 325 | type_expr { result = [val[0]] } 326 | | type_expr T_COMMA type_expr_comma_list { 327 | result = [val[0]] + val[2] } 328 | 329 | 330 | field_type: 331 | T_IVAR T_COLON type_expr { 332 | result = handle_var(:ivar, val[0], val[2]) 333 | } 334 | 335 | field_or_method_nonempty_list: 336 | field_type { 337 | result = {:fields => [val[0]], :methods => []} } 338 | | method_type { 339 | result = {:fields => [], :methods => [val[0]]} } 340 | | field_type T_COMMA field_or_method_nonempty_list { 341 | field_method_hash = val[2] 342 | field_method_hash[:fields] += [val[0]] 343 | result = field_method_hash 344 | } 345 | | method_type T_COMMA field_or_method_nonempty_list { 346 | field_method_hash = val[2] 347 | field_method_hash[:methods] += [val[0]] 348 | result = field_method_hash 349 | } 350 | 351 | field_or_method_list: 352 | { result = {:fields => [], :methods => []} } 353 | | field_or_method_nonempty_list { result = val[0] } 354 | 355 | end 356 | 357 | ---- header ---- 358 | 359 | require 'rtc/annot_lexer.rex' 360 | require 'rtc/parser' 361 | require 'rtc/typing/types.rb' 362 | 363 | module Rtc 364 | 365 | class TypeAnnotationParser < Racc::Parser 366 | 367 | attr_accessor :pos, :proxy 368 | 369 | # FIXME(rwsims): it's not clear what proxy is for, it's used when defining 370 | # class constants for doing class type signatures. 371 | def initialize(proxy) 372 | @proxy = proxy 373 | end 374 | 375 | def strip_quotes(arg) 376 | arg.strip.gsub(/^\"/, "").gsub(/\"$/, "") 377 | end 378 | 379 | # #################### 380 | # helper methods for type constructors from ML parser 381 | # #################### 382 | 383 | 384 | def pragma(key) 385 | fail "Unknown pragma keyword: #{key}" if(key !="FIXME") 386 | end 387 | 388 | end 389 | 390 | ---- inner ---- 391 | 392 | ---- footer ---- 393 | 394 | end 395 | -------------------------------------------------------------------------------- /lib/rtc/annotated.rb: -------------------------------------------------------------------------------- 1 | # Module that annotated classes must extend. 2 | # 3 | # Author:: Ryan W Sims (rwsims@umd.edu) 4 | 5 | require 'rtc/annot_parser.tab' 6 | require 'rtc/runtime/method_wrapper.rb' 7 | require 'rtc/proxy_object' 8 | require 'set' 9 | 10 | class Object 11 | attr_reader :annotated_methods 12 | attr_reader :proxies 13 | attr_writer :proxies 14 | 15 | def rtc_to_str 16 | self.to_s 17 | end 18 | 19 | def proxy_types_to_s 20 | if @proxy_types 21 | return @proxy_types.to_a.map {|i| i.to_s} 22 | else 23 | raise Exception, " object has no proxy_types" 24 | end 25 | end 26 | 27 | def rtc_inst(annotation_string) 28 | sigs = Rtc::TypeAnnotationParser.new(self.class).scan_str(annotation_string) 29 | sig = sigs[0] # what to do when more than one type? 30 | 31 | method_name = sig.id 32 | method_type = sig.type 33 | 34 | if @annotated_methods 35 | @annotated_methods[method_name] = method_type 36 | else 37 | @annotated_methods = {} 38 | @annotated_methods[method_name] = method_type 39 | end 40 | 41 | self 42 | end 43 | 44 | def rtc_cast(annotation_string) 45 | return self if self === false || self === nil || 46 | self.is_a?(Rtc::Types::Type) 47 | status = Rtc::MasterSwitch.is_on? 48 | Rtc::MasterSwitch.turn_off if status == true 49 | 50 | if annotation_string.class == String 51 | parser = Rtc::TypeAnnotationParser.new(self.class) 52 | annotated_type = parser.scan_str("##"+annotation_string) 53 | else 54 | annotated_type = annotation_string 55 | end 56 | 57 | if self.is_proxy_object? 58 | unless Rtc::MethodCheck.check_type(self.object, annotated_type) 59 | raise Rtc::AnnotateException, "object run-time type " + self.object.rtc_type.to_s + " NOT <= rtc_annotate argument type " + annotated_type.to_s 60 | end 61 | if annotated_type.is_tuple 62 | r = Rtc::TupleProxy.new(@object, annotated_type) 63 | elsif annotated_type.is_a?(Rtc::Types::HashType) 64 | r = Rtc::HashProxy.new(@object, annotated_type) 65 | else 66 | r = Rtc::ProxyObject.new(@object, annotated_type) 67 | end 68 | else 69 | unless Rtc::MethodCheck.check_type(self, annotated_type) 70 | raise Rtc::AnnotateException, "object type " + self.rtc_type.to_s + " NOT <= rtc_annotate argument type " + annotated_type.to_s 71 | end 72 | if annotated_type.is_tuple 73 | r = Rtc::TupleProxy.new(self, annotated_type) 74 | elsif annotated_type.is_a?(Rtc::Types::HashType) 75 | r = Rtc::HashProxy.new(self, annotated_type) 76 | else 77 | r = Rtc::ProxyObject.new(self, annotated_type) 78 | end 79 | end 80 | Rtc::MasterSwitch.turn_on if status == true 81 | r 82 | end 83 | 84 | def rtc_annotate(annotation_string) 85 | return self if self === false || self === nil || 86 | self.is_a?(Rtc::Types::Type) 87 | status = Rtc::MasterSwitch.is_on? 88 | Rtc::MasterSwitch.turn_off if status == true 89 | begin 90 | if annotation_string.class == String 91 | parser = Rtc::TypeAnnotationParser.new(self.class) 92 | annotated_type = parser.scan_str("##"+annotation_string) 93 | else 94 | if annotation_string.is_a?(Rtc::Types::TypeVariable) 95 | raise "fatal error, cannot annotate on type variables" 96 | end 97 | annotated_type = annotation_string 98 | end 99 | 100 | if self.is_proxy_object? 101 | if not self.proxy_type <= annotated_type 102 | raise Rtc::AnnotateException, "object proxy type " + self.proxy_type.to_s + " NOT <= rtc_annotate argument type " + annotated_type.to_s 103 | end 104 | if annotated_type.is_tuple 105 | r = Rtc::ProxyObject.new(@object, annotated_type) 106 | elsif annotated_type.is_a?(Rtc::Types::HashType) 107 | r = Rtc::HashProxy.new(@object, annotated_type) 108 | else 109 | r = Rtc::ProxyObject.new(@object, annotated_type) 110 | end 111 | else 112 | unless Rtc::MethodCheck.check_type(self, annotated_type) 113 | raise Rtc::AnnotateException, "object type " + self.rtc_type.to_s + " NOT <= rtc_annotate argument type " + annotated_type.to_s 114 | end 115 | if annotated_type.is_tuple 116 | r = Rtc::TupleProxy.new(self, annotated_type) 117 | elsif annotated_type.is_a?(Rtc::Types::HashType) 118 | r = Rtc::HashProxy.new(self, annotated_type) 119 | else 120 | r = Rtc::ProxyObject.new(self, annotated_type) 121 | end 122 | end 123 | r 124 | ensure 125 | Rtc::MasterSwitch.turn_on if status == true 126 | end 127 | end 128 | end 129 | 130 | 131 | # Mixin for annotated classes. The module defines class methods for declaring 132 | # type annotations and querying a class for the types of various methods. 133 | # 134 | # Note that this should be +extend+ed, not +include+ded. 135 | module Rtc::Annotated 136 | 137 | # Adds a type signature for a method to the class's method type table. 138 | def typesig(string_signature, meta_info={}) 139 | status = Rtc::MasterSwitch.is_on? 140 | Rtc::MasterSwitch.turn_off 141 | begin 142 | if meta_info.has_key?('mutate') 143 | mutate = meta_info['mutate'] 144 | else 145 | mutate = false 146 | end 147 | 148 | if meta_info.has_key?('unwrap') 149 | unwrap = meta_info['unwrap'] 150 | elsif meta_info.has_key?(:unwrap) 151 | unwrap = meta_info[:unwrap] 152 | else 153 | unwrap = [] 154 | end 155 | 156 | signatures = @annot_parser.scan_str(string_signature) 157 | if signatures.is_a?(Rtc::TypeAbbreviation) 158 | raise "Type #{signatures.type_name} already defined in context #{self.to_s}" if @type_names.has_key?(signatures.type_name) 159 | @type_names[signatures.type_name] = signatures.aliased_type 160 | return 161 | end 162 | 163 | signatures.each { 164 | |s| 165 | if s.instance_of?(Rtc::ClassMethodTypeSignature) or 166 | s.instance_of?(Rtc::MethodTypeSignature) 167 | s.type.mutate = mutate 168 | s.type.unwrap = unwrap 169 | end 170 | } 171 | 172 | this_type = Rtc::Types::NominalType.of(self) 173 | 174 | meta_type = self.rtc_type 175 | 176 | (signatures.map { 177 | |sig| 178 | if sig.instance_of?(Rtc::InstanceVariableTypeSignature) 179 | field_name = sig.id.to_s[1..-1] 180 | field_type = sig.type 181 | this_type.add_field(field_name, field_type) 182 | getter_type = Rtc::Types::ProceduralType.new([], field_type, []) 183 | setter_type = Rtc::Types::ProceduralType.new([], field_type, [field_type]) 184 | [Rtc::MethodTypeSignature.new(sig.pos,field_name,getter_type), 185 | Rtc::MethodTypeSignature.new(sig.pos,field_name+"=",setter_type)] 186 | elsif sig.instance_of?(Rtc::ClassVariableTypeSignature) 187 | field_name = sig.id.to_s[2..-1] 188 | field_type = sig.type 189 | meta_type.add_field(field_name, field_type) 190 | getter_type = Rtc::Types::ProceduralType.new([], field_type, []) 191 | setter_type = Rtc::Types::ProceduralType.new([], field_type, [field_type]) 192 | [Rtc::ClassMethodTypeSignature.new(sig.pos, field_name, getter_type), 193 | Rtc::ClassMethodTypeSignature.new(sig.pos, field_name+"=", setter_type)] 194 | else 195 | sig 196 | end 197 | }).flatten.each do |signature| 198 | if signature.instance_of?(Rtc::ClassMethodTypeSignature) 199 | handle_class_typesig(signature) 200 | else 201 | handle_instance_typesig(signature) 202 | end 203 | end 204 | ensure 205 | Rtc::MasterSwitch.set_to(status) 206 | end 207 | end 208 | 209 | def handle_instance_typesig(signature) 210 | if signature.id.to_s == "__rtc_next_method" 211 | @next_methods << signature.type 212 | return 213 | end 214 | this_type = Rtc::Types::NominalType.of(self) 215 | 216 | this_type.add_method(signature.id.to_s, signature.type) 217 | if self.instance_methods(false).include?(signature.id.to_sym) 218 | if not @method_wrappers.keys.include?(signature.id.to_s) 219 | @method_wrappers[signature.id.to_s] = Rtc::MethodWrapper.make_wrapper(self, signature.id.to_s) 220 | end 221 | else 222 | @deferred_methods << signature.id.to_s 223 | end 224 | end 225 | 226 | def handle_class_typesig(signature) 227 | meta_type = self.rtc_type 228 | meta_type.add_method(signature.id.to_s, signature.type) 229 | if self.methods(false).include?(signature.id.to_sym) 230 | @class_method_wrappers[signature.id.to_s] = Rtc::MethodWrapper.make_wrapper(class << self; self; end, signature.id.to_s, true) 231 | else 232 | @deferred_class_methods << signature.id.to_s 233 | end 234 | end 235 | 236 | #FIXME(jtoman): needs a better and catchier name 237 | def no_subtype 238 | self.rtc_meta[:no_subtype] = true 239 | self.instance_eval("alias :new :__rtc_original_new") 240 | def self.__rtc_autowrapped 241 | false 242 | end 243 | end 244 | 245 | def define_iterator(param_name,iterator_name) 246 | rtc_meta.fetch(:iterators)[param_name] = iterator_name 247 | end 248 | 249 | def define_iterators(iter_hash) 250 | if @rtc_autowrap 251 | raise "Auto annotation is not allowed on parameterized classes" 252 | end 253 | rtc_meta.fetch(:iterators).merge!(iter_hash) 254 | end 255 | 256 | def singleton_method_added(method_name) 257 | return if not defined? @annot_parser 258 | if method_name == :singleton_method_added 259 | return 260 | end 261 | if @deferred_class_methods.include?(method_name.to_s) 262 | @deferred_class_methods.delete(method_name.to_s) 263 | @class_method_wrappers[method_name.to_s] = Rtc::MethodWrapper.make_wrapper(class << self; self; end, method_name.to_s, true) 264 | end 265 | end 266 | 267 | def rtc_autowrapped? 268 | return false unless method_defined?(:__rtc_original_new) 269 | return __rtc_autowrapped 270 | end 271 | 272 | def method_added(method_name) 273 | return if not defined? @annot_parser 274 | needs_wrapper = @deferred_methods.include?(method_name.to_s) or 275 | @next_methods.size > 0 276 | if @deferred_methods.include?(method_name.to_s) 277 | @deferred_methods.delete(method_name.to_s) 278 | end 279 | if @next_methods.size != 0 280 | this_type = Rtc::Types::NominalType.of(self) 281 | @next_methods.each { 282 | |m_sig| 283 | this_type.add_method(method_name.to_s, m_sig) 284 | } 285 | @next_methods = [] 286 | end 287 | if needs_wrapper 288 | @method_wrappers[method_name.to_s] = Rtc::MethodWrapper.make_wrapper(self, method_name.to_s) 289 | end 290 | end 291 | 292 | def add_type_parameters(t_params) 293 | return if t_params.empty? 294 | t_parameters = [] 295 | iterators = {} 296 | t_params.each { 297 | |pair| 298 | t_parameters << pair[0] 299 | if pair.length == 1 300 | iterators[pair[0]] = nil 301 | else 302 | iterators[pair[0]] = pair[1] 303 | end 304 | } 305 | Rtc::Types::NominalType.of(self).type_parameters = t_parameters 306 | define_iterators(iterators) 307 | end 308 | 309 | def rtc_autowrap 310 | if respond_to?(:__rtc_original_new) and __rtc_autowrapped 311 | return 312 | end 313 | def self.__rtc_autowrapped 314 | true 315 | end 316 | if not respond_to?(:__rtc_original_new) 317 | self.instance_eval("alias :__rtc_original_new :new") 318 | end 319 | def self.new(*args, &blk) 320 | obj = __rtc_original_new(*args, &blk) 321 | obj.rtc_annotate(obj.rtc_type) 322 | end 323 | end 324 | 325 | def rtc_typed 326 | if defined? @class_proxy 327 | @class_proxy 328 | end 329 | @class_proxy = Rtc::ProxyObject.new(self, self.rtc_type); 330 | end 331 | 332 | def rtc_lookup_type(t_name) 333 | @type_names[t_name] 334 | end 335 | 336 | def self.extended(extendee) 337 | #FIXME: there must be a better way to do this 338 | [[:@annot_parser, Rtc::TypeAnnotationParser.new(extendee)], 339 | [:@method_wrappers,{}], 340 | [:@deferred_methods, Set.new], 341 | [:@next_methods, []], 342 | [:@class_method_wrappers, {}], 343 | [:@deferred_class_methods, Set.new], 344 | [:@type_names, Rtc::NativeHash.new] 345 | ].each { 346 | |i_var, value| 347 | extendee.instance_variable_set(i_var, value) 348 | } 349 | end 350 | end 351 | 352 | 353 | -------------------------------------------------------------------------------- /lib/rtc/logging.rb: -------------------------------------------------------------------------------- 1 | # Common code for creating loggers, verbosity and logging levels should be called here. 2 | # 3 | # The ruby 'logger' library does not seem to have the concept of a root logger, 4 | # from which all other loggers descend. Rather than having to set up the output 5 | # stream, name and verbosity level everywhere we want a logger, this method can 6 | # be used to get alread-configured Logger instances. 7 | # 8 | # Author:: Ryan W Sims (rwsims@umd.edu) 9 | 10 | require 'logger' 11 | 12 | module Rtc 13 | module Logging 14 | @verbose = false 15 | 16 | # Convenience method for wrapping Logger creation. Returns a logger that 17 | # outputs to STDERR and which has its level set per commandline options 18 | # and its progname set to the given name. 19 | def self.get_logger(progname) 20 | logger = Logger.new(STDERR) 21 | logger.progname = progname 22 | if @verbose 23 | logger.level = Logger::DEBUG 24 | else 25 | logger.level = Logger::WARN 26 | end 27 | return logger 28 | end 29 | 30 | # Set the loggers generated by this module to be verbose. Should only be 31 | # called once during initialization. 32 | def self.make_verbose 33 | @verbose = true 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/rtc/options.rb: -------------------------------------------------------------------------------- 1 | #contains the options for controlling the behavior of Rtc 2 | # Note that the Options hash is not for public manipulation, instead 3 | # all options must be set through the provided api 4 | module Rtc 5 | Options = { 6 | :on_type_error => :exception, 7 | :type_error_config => nil, 8 | :ignore_bad_typesig => false, 9 | :no_lazy_loading => false, 10 | } 11 | def self.on_type_error=(behavior) 12 | if behavior == :exception 13 | Options[:on_type_error] = :exception 14 | elsif behavior == nil or behavior == :ignore 15 | Options[:on_type_error] = :ignore 16 | elsif behavior == :exit 17 | Options[:on_type_error] = :exit 18 | elsif behavior.respond_to?(:call) 19 | Options[:on_type_error] = :callback 20 | Options[:type_error_config] = behavior 21 | elsif behavior.respond_to?(:write) 22 | Options[:on_type_error] = :file 23 | Options[:type_error_config] = behavior 24 | elsif behavior == :console 25 | Options[:on_type_error] = :file 26 | Options[:type_error_config] = STDOUT 27 | else 28 | raise "#{behavior} is an invalid error response." 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rtc/parser.rb: -------------------------------------------------------------------------------- 1 | require 'racc' 2 | 3 | require 'rtc/logging' 4 | require 'rtc/position' 5 | require 'rtc/typing/types' 6 | require 'rtc/typing/type_signatures' 7 | require 'rtc/runtime/class_loader' 8 | require 'rtc/runtime/native' 9 | module Rtc 10 | class TypeAnnotationParser < Racc::Parser 11 | @logger = Logging.get_logger('TypeAnnotationParser') 12 | 13 | Types = Rtc::Types 14 | # makes up a hash that contains a method type information for convenience 15 | # in later use 16 | def construct_msig(domain, block, range) 17 | domain = domain ? (domain.kind_of?(Array) ? Rtc::NativeArray.new(domain) : Rtc::NativeArray[domain]) : Rtc::NativeArray.new 18 | return {:domain => domain, :block => block, :range => range} 19 | end 20 | 21 | def handle_btype(msig) 22 | msig 23 | end 24 | 25 | # FIXME(rwsims): kind is unused 26 | def handle_type_param(kind, text) 27 | return Rtc::Types::TypeParameter.new(text.to_sym) 28 | end 29 | 30 | def get_type(type_name) 31 | to_ret = @proxy.rtc_lookup_type(type_name) 32 | if to_ret.nil? 33 | raise "Type #{type_name} not defined in context #{@object}" 34 | end 35 | to_ret 36 | end 37 | 38 | def handle_type_ident(ident) 39 | begin 40 | Rtc::Types::NominalType.of(Rtc::ClassLoader.load_class(ident, @proxy)) 41 | rescue Rtc::ClassNotFoundException => e 42 | Rtc::Types::LazyNominalType.new(ident, @proxy) 43 | end 44 | end 45 | 46 | public 47 | 48 | # constructs a method type (mono or poly) and coalesces type parameters 49 | # appearing in the method type (including the constraints) 50 | def handle_mtype(meth_id, parameters, msig) 51 | # expect msig to be result from construct_msig 52 | domain = msig[:domain] 53 | block = msig[:block] 54 | range = msig[:range] 55 | if block 56 | block = Rtc::Types::ProceduralType.new(parameters, block[:range], 57 | block[:domain], 58 | nil) 59 | end 60 | 61 | 62 | mtype = Types::ProceduralType.new(parameters, range, domain, block) 63 | type_klass = meth_id.instance_of?(Rtc::ClassMethodIdentifier) ? ClassMethodTypeSignature : MethodTypeSignature 64 | return type_klass.new(pos, meth_id, mtype) 65 | end 66 | 67 | def handle_var(kind, name, type) 68 | r = case kind 69 | when :ivar 70 | id = InstanceVariableIdentifier.new(name.to_sym) 71 | InstanceVariableTypeSignature.new(@pos, id, type) 72 | when :cvar 73 | id = ClassVariableIdentifier.new(name.to_sym) 74 | ClassVariableTypeSignature.new(pos, id, type) 75 | when :gvar 76 | err("Global variables not yet supported, " + 77 | "found annotation for #{name}") 78 | else 79 | fatal("Unknown variable annotation kind '#{kind.to_s}'") 80 | end 81 | return r 82 | end 83 | 84 | def handle_structural_type(list) 85 | field_types = {} 86 | method_types = {} 87 | list[:fields].each {|f| field_types[f.id.to_s] = f.type } 88 | list[:methods].each {|m| method_types[m.id.to_s] = m.type } 89 | t = Rtc::Types::StructuralType.new(field_types, method_types) 90 | return t 91 | end 92 | 93 | def warn(msg, lineno = nil) 94 | if(lineno != nil) 95 | msg += " @ line #{lineno.to_s}" 96 | end 97 | logger.warn(msg) 98 | end 99 | 100 | def err(msg, lineno = nil) 101 | if(lineno != nil) 102 | msg += " @ line #{lineno.to_s}" 103 | end 104 | logger.error(msg) 105 | end 106 | 107 | def fatal(msg, lineno = nil) 108 | if(lineno != nil) 109 | msg += " @ line #{lineno.to_s}" 110 | end 111 | raise(RuntimeError, msg) 112 | end 113 | 114 | end 115 | 116 | # Because Rtc's type annotation supports class/instance methods and 117 | # fields, it would be essential to make the identifiers of methods/fields 118 | # not just strings. 119 | class Identifier 120 | 121 | attr_reader :name 122 | 123 | def initialize(name); @name = name.to_sym end 124 | def eql?(other); @name == other.name end 125 | def hash(); @name.hash end 126 | def to_s(); @name.to_s end 127 | def to_sym(); @name end 128 | end 129 | 130 | class MethodIdentifier < Identifier 131 | def initialize(name) 132 | super(name) 133 | end 134 | end 135 | 136 | class ClassMethodIdentifier < Identifier 137 | def initialize(name) 138 | super(name) 139 | end 140 | end 141 | 142 | # TODO 143 | class InstanceVariableIdentifier < Identifier 144 | end 145 | 146 | class ClassVariableIdentifier < Identifier 147 | end 148 | 149 | class ConstantIdentifier < Identifier 150 | end 151 | 152 | end 153 | -------------------------------------------------------------------------------- /lib/rtc/position.rb: -------------------------------------------------------------------------------- 1 | 2 | # Determinig source position at runtime is very tricky *as* it sounds. So I 3 | # allowed several different types of position as legitimate. Refer to each 4 | # class for detail. 5 | # 6 | # NOTE: Because most positions do not have precise source line number, it is 7 | # required to do some sort of post processing. This is a TODO for now. 8 | 9 | require 'rtc/utils' 10 | 11 | module Rtc 12 | 13 | module Positioning 14 | # finds the source location of the caller (of specified level). 15 | def self.caller_pos(callerstack, level=1) 16 | info = callerstack[level].split(":") 17 | LinePosition.new(info[0].to_s, info[1].to_i) 18 | end 19 | 20 | # computes the file/line position of the current control flow but disregards 21 | # any internal files. they appear because we do this patching and evals. 22 | def self.get_smart_pos() 23 | pos = caller_pos(caller(), 0) 24 | i = 0 25 | while (pos.file =~ /(monitors.rb|modifier.rb|\(eval\))/) do 26 | i = i + 1 27 | pos = caller_pos(caller(), i) 28 | end 29 | return pos 30 | end 31 | end 32 | 33 | class Position; end # this is just a dummy abstract class 34 | 35 | class MergedPosition < Position 36 | attr_reader :pos_list 37 | 38 | def initialize(*args) 39 | if args.length == 1 40 | case args[0] 41 | when Array 42 | @pos_list = args[0] 43 | else 44 | raise "Merged position takes more than one individual positions" 45 | end 46 | elsif args.length > 1 47 | @pos_list = args 48 | else 49 | raise "Merged position takes more than one individual positions" 50 | end 51 | end 52 | 53 | def to_s; (@pos_list.map {|pos| pos.to_s}).join(" and ") end 54 | 55 | end 56 | 57 | # represents a position in the source code. 58 | class LinePosition < Position 59 | 60 | attr_accessor :file 61 | attr_accessor :line 62 | 63 | def initialize(file,line=nil) 64 | @file = file 65 | @line = line 66 | end 67 | 68 | def self.dummy_pos(); LinePosition.new("???", -1) end 69 | def to_s(); "#{file ? file : "???"} : #{line ? line : -1}" end 70 | 71 | end 72 | 73 | # this is special. it contains type parameter name (in string) 74 | class TypeParamPosition < LinePosition 75 | 76 | attr_accessor :type_param 77 | 78 | def initialize(type_param, file, line) 79 | super(file, line) 80 | @type_param = type_param 81 | end 82 | 83 | def to_s; "tparam #{type_param.to_s} (#{super()})" end 84 | 85 | end 86 | 87 | # represents a location for a class 88 | class ClassPosition < Position 89 | 90 | attr_accessor :klass 91 | 92 | def initialize(klass); @klass = klass end 93 | def to_s() 94 | @klass.respond_to?(:name) ? @klass.name : @klass.to_s 95 | end 96 | 97 | end 98 | 99 | class FieldPosition < ClassPosition 100 | 101 | attr_accessor :fname 102 | 103 | def initialize(klass, fname) 104 | super(klass) 105 | @fname = fname 106 | end 107 | 108 | def to_s(); "#{super}##{fname}" end 109 | 110 | end 111 | 112 | # represents mname definition location using the class name and the mname 113 | # name 114 | class DefPosition < ClassPosition 115 | 116 | attr_accessor :klass 117 | attr_accessor :mname 118 | 119 | def initialize(klass, mname) 120 | super(klass) 121 | @mname = mname 122 | end 123 | 124 | # TODO: there is a room for improvement here! 125 | def to_s() 126 | "#{super}##{mname}" 127 | end 128 | 129 | def self.dummy_pos(); DefPosition.new("???", "???") end 130 | 131 | end 132 | 133 | # represents a formal argument position using its index. 134 | class FormalArgPosition < DefPosition 135 | 136 | attr_accessor :index 137 | 138 | def initialize(klass, mname, index) 139 | super(klass, mname) 140 | @index = index 141 | end 142 | 143 | def to_s(); "#{super}(#{@index})" end 144 | 145 | def self.dummy_pos(); FormalArgPosition.new("???", "???", -1) end 146 | 147 | end 148 | 149 | # represents a formal return value position. it's same as the mname 150 | # definition but to_s mname indicates that it's for the return value. 151 | class FormalRetValPosition < DefPosition 152 | 153 | def to_s(); "#{super} return" end 154 | 155 | end 156 | 157 | class FormalBlockPosition < DefPosition 158 | 159 | def to_s(); "#{super} block" end 160 | 161 | end 162 | 163 | end 164 | -------------------------------------------------------------------------------- /lib/rtc/runtime/class_loader.rb: -------------------------------------------------------------------------------- 1 | 2 | module Rtc 3 | class ClassNotFoundException < StandardError; end 4 | module ClassLoader 5 | def ClassLoader.load_class(ident, context) 6 | if ident[:type] == :absolute or context.instance_of?(Object) 7 | eval("::" + ident[:name_list].join("::")) 8 | else 9 | scopes = context.name.split("::") 10 | while scopes.size != 0 11 | curr_scope = eval(scopes.join("::")) 12 | obj = find_type_ident(ident[:name_list], curr_scope) 13 | return obj if obj != nil 14 | scopes.pop() 15 | end 16 | obj = find_type_ident(ident[:name_list], Object) 17 | raise ClassNotFoundException,"Could not find class #{ident_to_s(ident)} in context #{context.to_s}" unless obj != nil 18 | return obj 19 | end 20 | end 21 | 22 | def ClassLoader.ident_to_s(ident) 23 | if ident[:type] == :absolute 24 | "::" + ident[:name_list].join("::") 25 | else 26 | ident[:name_list].join("::") 27 | end 28 | end 29 | 30 | def ClassLoader.find_type_ident(name_list, scope) 31 | curr_scope = scope 32 | name_list.each {|id| 33 | if curr_scope.const_defined?(id) 34 | curr_scope = curr_scope.const_get(id) 35 | else 36 | return nil 37 | end 38 | } 39 | return curr_scope 40 | end 41 | 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/rtc/runtime/master_switch.rb: -------------------------------------------------------------------------------- 1 | module Rtc 2 | # Master switch works as a flag for patching code to avoid patching its own 3 | # code. When entering a patching code, this switch should be turned off. 4 | # When exiting, this switch should be turned back on. Notice that when this 5 | # patching code invokes the original method(s), this switch should be turned 6 | # back on. For example, 7 | # 8 | # x.foo 9 | # 1) goes to x.method_missing 10 | # 2) some patching code 11 | # 3) calls the actual method 'foo' 12 | # 4) ... (this foo may call other methods) 13 | # 5) comes back to the patching code 14 | # 15 | # at step 4, we must be able to capture other calls, and thus, this switch 16 | # must be turned on. 17 | class MasterSwitch 18 | @@master_switch = true 19 | def self.is_on?(); return @@master_switch end 20 | def self.turn_on(); @@master_switch = true end 21 | def self.turn_off(); @@master_switch = false end 22 | def self.set_to(state); @@master_switch = state end 23 | def self.ensure_off() 24 | state = @state 25 | @@master_switch = false 26 | state 27 | end 28 | end 29 | 30 | def self.turn_switch_on(); MasterSwitch.turn_on() end 31 | def self.turn_switch_off(); MasterSwitch.turn_off() end 32 | def self.is_switch_on?(); MasterSwitch.is_on?() end 33 | def self.set_switch_to(state); MasterSwitch.set_to(state) end 34 | end 35 | -------------------------------------------------------------------------------- /lib/rtc/runtime/method_check.rb: -------------------------------------------------------------------------------- 1 | require 'rtc/runtime/native' 2 | require 'rtc/runtime/master_switch.rb' 3 | require 'rtc/options' 4 | 5 | module Rtc::MethodCheck 6 | def self.check_type(value, type, check_variables = true) 7 | if value.is_proxy_object? 8 | value.rtc_type <= type 9 | elsif type.is_tuple 10 | return false unless value.is_a?(Array) and value.size == type.ordered_params.size 11 | i = 0 12 | len = value.size 13 | ordered_params = type.ordered_params 14 | while i < len 15 | return false unless self.check_type(value[i], ordered_params[i], check_variables) 16 | i += 1 17 | end 18 | return true 19 | elsif type.is_a?(Rtc::Types::HashType) 20 | return false unless value.is_a?(Hash) 21 | type.required.each { 22 | |k,t| 23 | return false unless value.has_key?(k) 24 | return false unless self.check_type(value[k], t, check_variables) 25 | } 26 | type.optional.each { 27 | |k,t| 28 | if value.has_key?(k) 29 | return false unless self.check_type(value[k], t, check_variables) 30 | end 31 | } 32 | return true 33 | elsif $RTC_STRICT or (not value.rtc_is_complex?) 34 | return value.rtc_type <= type 35 | else 36 | if type.has_variables and check_variables 37 | return value.rtc_type <= type 38 | end 39 | case type 40 | when Rtc::Types::ParameterizedType 41 | return type.nominal.klass == value.class 42 | when Rtc::Types::UnionType 43 | if type.has_variables 44 | solution_found = false 45 | for t in type.types 46 | if self.check_type(value, t, check_variables) 47 | raise Rtc::AmbiguousUnionException, "Ambiguous union detected" if solution_found 48 | solution_found 49 | end 50 | end 51 | solution_found 52 | else 53 | type.types.any? { 54 | |t| 55 | self.check_type(value, t, check_variables) 56 | } 57 | end 58 | when Rtc::Types::TopType 59 | return true 60 | else 61 | return false 62 | end 63 | end 64 | end 65 | 66 | def self.select_and_check_args(method_types, method_name, args, has_block, class_obj) 67 | method_types.each { |mt| 68 | mt.type_variables.each { |tv| tv.start_solve if not tv.instantiated? and not tv.solving? } 69 | } 70 | possible_types = Rtc::NativeArray.new 71 | num_actual_args = args.length 72 | for m in method_types 73 | next if num_actual_args < m.min_args 74 | next if m.max_args != -1 and num_actual_args > m.max_args 75 | next if (not m.block_type.nil?) != has_block 76 | 77 | annotate_now = m.type_variables.empty? 78 | annot_vector = Rtc::NativeArray.new(args.length) 79 | if self.check_arg_impl(m, args, annotate_now, annot_vector) 80 | possible_types.push([m, annotate_now ? annot_vector : false]) 81 | end 82 | end 83 | 84 | if possible_types.size > 1 85 | raise Rtc::TypeMismatchException, "cannot infer type in intersecton type for method #{method_name}, whose types are #{method_types.inspect}" 86 | elsif possible_types.size == 0 87 | arg_types = args.map {|a| 88 | a.rtc_type 89 | } 90 | 91 | raise Rtc::TypeMismatchException, "In method #{method_name}, annotated types are #{method_types.inspect}, but actual arguments are #{args.rtc_to_str}, with argument types #{arg_types.inspect}" + 92 | " for class #{class_obj}" 93 | else 94 | chosen_type, annotations = possible_types[0] 95 | end 96 | 97 | if not annotations 98 | unsolved_variables = update_variables(chosen_type.type_variables) 99 | annotations = annotate_args(chosen_type, args) 100 | else 101 | unsolved_variables = [] 102 | end 103 | 104 | return chosen_type, annotations, unsolved_variables 105 | end 106 | 107 | def self.check_return(m_type, return_value, unsolved_variables) 108 | unsolved_variables = update_variables(unsolved_variables) 109 | return false unless self.check_type(return_value, m_type.return_type) 110 | update_variables(unsolved_variables) 111 | true 112 | end 113 | 114 | def self.check_args(m_type, args, unsolved_variables) 115 | if unsolved_variables.empty? 116 | annot_vector = Rtc::NativeArray.new(args.length) 117 | return self.check_arg_impl(m_type, args, true, annot_vector) ? 118 | [annot_vector,[]] : false 119 | else 120 | return false unless self.check_arg_impl(m_type, args, false) 121 | unsolved_variables = update_variables(unsolved_variables) 122 | annotations = annotate_args(m_type, args) 123 | return annotations, unsolved_variables 124 | end 125 | end 126 | 127 | def self.update_variables(t_vars) 128 | return unless t_vars 129 | unsolved_type_variables = Rtc::NativeArray.new 130 | t_vars.each { 131 | |tvar| 132 | if tvar.solvable? 133 | tvar.solve 134 | elsif tvar.instantiated 135 | next 136 | else 137 | unsolved_type_variables << tvar 138 | end 139 | } 140 | return unsolved_type_variables 141 | end 142 | 143 | # comment this too. it needs it badly 144 | def self.annotate_args(method_type, args) 145 | annot_vector = Rtc::NativeArray.new(args.length) 146 | arg_types = method_type.arg_types 147 | # cached, not so bad 148 | param_layout = method_type.parameter_layout 149 | num_args = args.length 150 | i = 0 151 | unwrap_positions = method_type.unwrap 152 | while i < param_layout[:required][0] 153 | if unwrap_positions.include?(i) 154 | annot_vector[i] = args[i] 155 | else 156 | annot_vector[i] = args[i].rtc_annotate(arg_types[i].to_actual_type) 157 | end 158 | i = i + 1 159 | end 160 | i = num_args - param_layout[:required][1] 161 | while i < num_args 162 | if unwrap_positions.include?(i) 163 | annot_vector[i] = args[i] 164 | else 165 | annot_vector[i] = args[i].rtc_annotate(arg_types[i].to_actual_type) 166 | end 167 | i = i + 1 168 | end 169 | i = param_layout[:required][0] 170 | while i < (param_layout[:required][0] + param_layout[:opt]) and 171 | i < (num_args - param_layout[:required][1]) 172 | if unwrap_positions.include?(i) 173 | annot_vector[i] = args[i] 174 | else 175 | annot_vector[i] = args[i].rtc_annotate(arg_types[i].type.to_actual_type) 176 | end 177 | i = i + 1 178 | end 179 | i = param_layout[:required][0] + param_layout[:opt] 180 | rest_index = i 181 | no_annotate_rest = unwrap_positions.include?(rest_index) 182 | while i < num_args - param_layout[:required][1] 183 | if no_annotate_rest 184 | annot_vector[i] = args[i] 185 | else 186 | annot_vector[i] = args[i].rtc_annotate(arg_types[rest_index].type.to_actual_type) 187 | end 188 | i = i + 1 189 | end 190 | return annot_vector 191 | end 192 | 193 | # returns false on type error or a true value. 194 | # TOOD(jtoman): comment this. it needs it *badly* 195 | def self.check_arg_impl(m_type, args,annotate_now, annot_vector = nil) 196 | expected_arg_types = m_type.arg_types 197 | arg_layout = m_type.parameter_layout 198 | i = 0 199 | valid = true 200 | num_actual_args = args.length 201 | required_indices = [ 202 | # start offset, end index, type offset 203 | [0,arg_layout[:required][0], 0], 204 | [num_actual_args - arg_layout[:required][1], num_actual_args, 205 | arg_layout[:required][0] + arg_layout[:opt] + (arg_layout[:rest]?1 : 0) 206 | ] 207 | ] 208 | unwrap_positions = m_type.unwrap 209 | for arg_range in required_indices 210 | # while loops are faster... 211 | start_offset, end_index, type_offset = arg_range 212 | i = 0 213 | while start_offset + i < end_index 214 | type_index = type_offset + i 215 | value_index = start_offset + i 216 | if expected_arg_types[type_index].instance_of?(Rtc::Types::ProceduralType) 217 | annot_vector[value_index] = Rtc::BlockProxy.new(args[value_index], 218 | expected_arg_types[type_index].to_actual_type, 219 | method_name, class_obj, []) if annotate_now 220 | else 221 | unless self.check_type(args[value_index], expected_arg_types[type_index]) 222 | valid = false 223 | break 224 | end 225 | if annotate_now 226 | if unwrap_positions.include?(type_index) 227 | annot_vector[value_index] = args[value_index] 228 | else 229 | annot_vector[value_index] = args[value_index].rtc_annotate(expected_arg_types[type_index].to_actual_type) 230 | end 231 | end 232 | end 233 | i = i + 1 234 | end 235 | break if not valid 236 | end 237 | 238 | return false if not valid 239 | 240 | # skip the optional shenanigans if we're done 241 | if arg_layout[:opt] == 0 and arg_layout[:rest] == false 242 | return true 243 | end 244 | 245 | # start index of the final required arguments 246 | post_req_start = num_actual_args - arg_layout[:required][1] 247 | non_req_args_count = post_req_start - arg_layout[:required][0] 248 | if non_req_args_count > arg_layout[:opt] 249 | final_opt_index = arg_layout[:required][0] + arg_layout[:opt] 250 | else 251 | final_opt_index = post_req_start 252 | end 253 | i = arg_layout[:required][0] 254 | while i < final_opt_index 255 | if expected_arg_types[i].type.instance_of?(Rtc::Types::ProceduralType) 256 | annot_vector[i] = Rtc::BlockProxy.new(args[i], expected_arg_types[i].type.to_actual_type, method_name, class_obj, []) if annotate_now 257 | else 258 | unless self.check_type(args[i], expected_arg_types[i].type) 259 | valid = false 260 | break 261 | end 262 | if annotate_now 263 | if unwrap_positions.include?(i) 264 | annot_vector[i] = arg[i] 265 | else 266 | annot_vector[i] = args[i].rtc_annotate(expected_arg_types[i].type.to_actual_type) 267 | end 268 | end 269 | end 270 | i = i + 1 271 | end 272 | 273 | return false if not valid 274 | 275 | if not arg_layout[:rest] 276 | return true 277 | end 278 | rest_index = arg_layout[:required][0] + arg_layout[:opt] 279 | rest_type = expected_arg_types[rest_index].type 280 | i = arg_layout[:required][0] + arg_layout[:opt] 281 | if rest_type.instance_of?(Rtc::Types::ProceduralType) 282 | if annotate_now 283 | while i < post_req_start 284 | annot_vector[i] = Rtc::BlockProxy.new(args[i], rest_type.to_actual_type, method_name, class_obj, []) 285 | end 286 | end 287 | else 288 | while i < post_req_start 289 | unless self.check_type(args[i], rest_type) 290 | valid = false 291 | break 292 | end 293 | if annotate_now 294 | if unwrap_positions.include?(rest_index) 295 | annot_vector[i] = args[i] 296 | else 297 | annot_vector[i] = args[i].rtc_annotate(rest_type) 298 | end 299 | end 300 | i = i + 1 301 | end 302 | end 303 | return valid 304 | end 305 | end 306 | -------------------------------------------------------------------------------- /lib/rtc/runtime/method_wrapper.rb: -------------------------------------------------------------------------------- 1 | require 'rtc/runtime/native' 2 | require 'rtc/runtime/master_switch.rb' 3 | require 'rtc/options' 4 | 5 | module Rtc 6 | 7 | class TypeMismatchException < StandardError; end 8 | 9 | class AnnotateException < StandardError; end 10 | 11 | class CastException < StandardError; end 12 | 13 | class AmbiguousUnionException < StandardError; end 14 | 15 | class NoMethodException < StandardError; end 16 | 17 | class MethodWrapper 18 | @call_template = < "__rtc_rtc_op_plus", 93 | "[]=" => "__rtc_rtc_op_elem_set", 94 | "[]" => "__rtc_rtc_op_elem_get", 95 | "**" => "__rtc_rtc_op_exp", 96 | "!" => "__rtc_rtc_op_not", 97 | "!" => "__rtc_rtc_op_complement", 98 | "+@" => "__rtc_rtc_op_un_plus", 99 | "-@" => "__rtc_rtc_op_un_minus", 100 | "*" => "__rtc_rtc_op_mult", 101 | "/" => "__rtc_rtc_op_div", 102 | "%" => "__rtc_rtc_op_mod", 103 | "+" => "__rtc_rtc_op_plus", 104 | "-" => "__rtc_rtc_op_minus", 105 | "~" => "__rtc_rtc_op_telda", 106 | ("<" + "<") => "__rtc_rtc_op_ls", 107 | ">>"=> "__rtc_rtc_op_rs", 108 | "^" => "__rtc_rtc_op_bitxor", 109 | "|" => "__rtc_rtc_op_bitor", 110 | "<=" => "__rtc_rtc_op_lte", 111 | "<" => "__rtc_rtc_op_lt", 112 | ">" => "__rtc_rtc_op_gt", 113 | ">=" => "__rtc_rtc_op_gte", 114 | "<=>" => "__rtc_rtc_op_3comp", 115 | "==" => "__rtc_rtc_op_eq", 116 | "=~" => "__rtc_rtc_op_eq_telda", 117 | "===" => "__rtc_rtc_op_strict_eq", 118 | '&' => "__rtc_rtc_op_bitand", 119 | } 120 | def self.mangle_name(method_name, class_name) 121 | class_name = class_name.sub("::", "_") 122 | if @mangled.has_key?(method_name.to_s) 123 | class_name + @mangled[method_name.to_s] 124 | elsif method_name.to_s =~ /^(.+)=$/ 125 | class_name + "__rtc_rtc_set_" + $1 126 | else 127 | class_name + "__rtc_" + method_name.to_s 128 | end 129 | end 130 | 131 | def self.make_wrapper(class_obj, method_name, is_class = false) 132 | return nil if Rtc::Disabled 133 | if is_class 134 | class_obj.to_s =~ /(?<=Class:)([^>]+)>+$/ 135 | class_name = $1 136 | else 137 | class_name = class_obj.name 138 | end 139 | 140 | class_name.gsub!('::', '_') 141 | mangled_name = self.mangle_name(method_name, class_name) 142 | 143 | if is_class 144 | invokee_fetch = "new_invokee = self" 145 | else 146 | invokee_fetch = "new_invokee = self.rtc_get_proxy" 147 | end 148 | class_obj.module_eval(@call_template % { :method_name => method_name.to_s, 149 | :mangled_name => mangled_name, 150 | :invokee_fetch => invokee_fetch 151 | }, "method_wrapper.rb", 17) 152 | return true 153 | end 154 | 155 | def self.wrap_block(x) 156 | Proc.new {|*v| x.call(*v)} 157 | end 158 | 159 | private 160 | 161 | def on_error(message) 162 | case Rtc::Options[:on_type_error] 163 | when :ignore 164 | ; 165 | when :exception 166 | raise TypeMismatchException,message 167 | when :file 168 | Rtc::Options[:type_error_config].write(message) 169 | when :callback 170 | Rtc::Options[:type_error_config].call(message) 171 | when :exit 172 | exit -1 173 | end 174 | end 175 | 176 | def initialize(class_obj,method_name) 177 | raise "We should never be here" 178 | end 179 | end 180 | 181 | class BlockProxy 182 | attr_reader :proc 183 | attr_accessor :block_type 184 | attr_reader :method_type 185 | attr_reader :method_name 186 | attr_reader :class_obj 187 | 188 | def initialize(proc, type, method_name, class_obj, 189 | unsolved_tvars) 190 | @proc = proc 191 | @block_type = type 192 | @method_name = method_name 193 | @class_obj = class_obj 194 | @unsolved_type_variables = unsolved_tvars 195 | end 196 | 197 | def call(*args) 198 | $CHECK_COUNT+=1 199 | args = Rtc::NativeArray.new(args) 200 | Rtc::MasterSwitch.turn_off 201 | check_result = Rtc::MethodCheck.check_args(@block_type, args, 202 | @unsolved_type_variables) 203 | if not check_result 204 | raise Rtc::TypeMismatchException, "Block arg failed!" 205 | end 206 | annotated_args, @unsolved_type_variables = check_result 207 | Rtc::MasterSwitch.turn_on 208 | ret = @proc.call(*annotated_args) 209 | Rtc::MasterSwitch.turn_off 210 | return_valid = Rtc::MethodCheck.check_return(@block_type, ret, @unsolved_type_variables) 211 | raise Rtc::TypeMismatchException, "Block return type mismatch, expected #{@block_type.return_type}, got #{ret.rtc_type}" unless return_valid 212 | begin 213 | if ret === false or ret === nil or ret.is_a?(Rtc::Types::Type) 214 | ret 215 | else 216 | ret.rtc_annotate(block_type.return_type.to_actual_type) 217 | end 218 | ensure 219 | Rtc::MasterSwitch.turn_on 220 | end 221 | end 222 | 223 | end 224 | end 225 | -------------------------------------------------------------------------------- /lib/rtc/runtime/native.rb: -------------------------------------------------------------------------------- 1 | module Rtc 2 | class NativeArray < Array; end 3 | class NativeHash < Hash; end 4 | end 5 | -------------------------------------------------------------------------------- /lib/rtc/runtime/type_inferencer.rb: -------------------------------------------------------------------------------- 1 | 2 | module Rtc 3 | class TypeInferencer 4 | def self.infer_type(it) 5 | curr_type = Set.new 6 | it.each { 7 | |elem| 8 | elem_type = elem.rtc_type 9 | if curr_type.size == 0 10 | curr_type << elem_type 11 | next 12 | end 13 | super_count = 0 14 | was_subtype = curr_type.any? { 15 | |seen_type| 16 | if elem_type <= seen_type 17 | true 18 | elsif seen_type <= elem_type 19 | super_count = super_count + 1 20 | false 21 | end 22 | } 23 | if was_subtype 24 | next 25 | elsif super_count == curr_type.size 26 | curr_type = Set.new([elem_type]) 27 | else 28 | curr_type << elem_type 29 | end 30 | } 31 | if curr_type.size == 0 32 | Rtc::Types::BottomType.instance 33 | elsif curr_type.size == 1 34 | curr_type.to_a[0] 35 | else 36 | Rtc::Types::UnionType.of(self.unify_param_types(curr_type)) 37 | end 38 | end 39 | 40 | private 41 | 42 | def self.extract_types(param_type) 43 | param_type.instance_of?(Rtc::Types::UnionType) ? param_type.types.to_a : [param_type] 44 | end 45 | 46 | #FIXME(jtoman): see if we can lift this step into the gen_type step 47 | def self.unify_param_types(type_set) 48 | non_param_classes = [] 49 | parameterized_classes = {} 50 | type_set.each { 51 | |member_type| 52 | if member_type.parameterized? 53 | nominal_type = member_type.nominal 54 | tparam_set = parameterized_classes.fetch(nominal_type) { 55 | |n_type| 56 | [].fill([], 0, n_type.type_parameters.size) 57 | } 58 | ((0..(nominal_type.type_parameters.size - 1)).map { 59 | |tparam_index| 60 | extract_types(member_type.parameters[tparam_index]) 61 | }).each_with_index { 62 | |type_parameter,index| 63 | tparam_set[index]+=type_parameter 64 | } 65 | parameterized_classes[nominal_type] = tparam_set 66 | else 67 | non_param_classes << member_type 68 | end 69 | } 70 | parameterized_classes.each { 71 | |nominal,type_set| 72 | non_param_classes << Rtc::Types::ParameterizedType.new(nominal, 73 | type_set.map { 74 | |unioned_type_parameter| 75 | Rtc::Types::UnionType.of(unify_param_types(unioned_type_parameter)) 76 | }, true) 77 | } 78 | non_param_classes 79 | end 80 | 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/rtc/tools/hash-builder.rb: -------------------------------------------------------------------------------- 1 | # A builder class to help create composite hash codes. Idea taken from 2 | # HashCodeBuilder in the Apache Commons library. 3 | # 4 | # Author:: Ryan W Sims (rwsims@umd.edu) 5 | 6 | module Rtc 7 | 8 | # A builder for creating hash codes for classes with internal state that 9 | # needs to influence the hash. 10 | class HashBuilder 11 | 12 | # Initializer. 13 | # 14 | # [+initial+] The initial value for the hash, should be prime and 15 | # different for every class. Must be non-zero. 16 | # [+constant+] The constant use to "fold" each value into the hash; 17 | # should be prime and different for every class. Must be 18 | # non-zero. 19 | def initialize(initial, constant) 20 | if initial == 0 21 | raise(ArgumentError, "Initial value must be nonzero.") 22 | end 23 | if constant == 0 24 | raise(ArgumentError, "Initial value must be nonzero.") 25 | end 26 | @hash = initial 27 | @constant = constant 28 | end 29 | 30 | # Includes +field+ in the hash code. 31 | def include(field) 32 | @hash += @constant * field.hash 33 | end 34 | 35 | # Returns the computed hash. 36 | def get_hash 37 | @hash 38 | end 39 | end 40 | end # module Rtc 41 | -------------------------------------------------------------------------------- /lib/rtc/typing/base_types.rb: -------------------------------------------------------------------------------- 1 | require 'rtc' 2 | 3 | rtc_typesig("class Array") 4 | class Array 5 | rtc_annotated 6 | typesig("'[]': (Fixnum) -> t or nil") 7 | typesig("'[]': (Fixnum, Fixnum) -> Array or nil") 8 | typesig("'[]': (Range) -> Array or nil") 9 | # typesig("'+': (t) -> Array") 10 | typesig("push: (t) -> Array") 11 | end 12 | 13 | rtc_typesig("class Set") 14 | class Set 15 | rtc_annotated 16 | define_iterators :t => :each 17 | typesig("to_a: () -> Array") 18 | typesig("'includes?': (t) -> TrueClass or FalseClass") 19 | end 20 | 21 | rtc_typesig("class Hash") 22 | class Hash 23 | rtc_annotated 24 | end 25 | -------------------------------------------------------------------------------- /lib/rtc/typing/base_types2.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | rtc_annotated [:t, :each] 3 | 4 | typesig("'[]' : (Range) -> Array", {'unwrap'=>[0]}) 5 | typesig("'[]' : (Fixnum, Fixnum) -> Array") 6 | typesig("'[]' : (Fixnum) -> t", {'unwrap'=>[0]}) 7 | typesig("'[]' : (Float) -> t", {'unwrap'=>[0]}) 8 | typesig("'&': (Array) -> Array") 9 | typesig("'*': (Fixnum) -> Array") 10 | typesig("'*': (String) -> String") 11 | typesig("'+': (Array) -> Array") 12 | typesig("'-': (Array) -> Array") 13 | typesig("slice: (Fixnum) -> t") 14 | typesig("slice: (Fixnum,Fixnum) -> Array") 15 | typesig("slice: (Range) -> Array") 16 | typesig("'[]=': (Fixnum, t) -> t") 17 | typesig("'[]=': (Fixnum, Fixnum,t) -> t") 18 | typesig("'[]=': (Fixnum, Fixnum,Array) -> Array") 19 | typesig("'[]=': (Range,Array) -> Array") 20 | typesig("'[]=': (Range,t) -> t") 21 | typesig("assoc: (t) -> Array") 22 | typesig("at: (Fixnum) -> t") 23 | typesig("clear: () -> Array") 24 | typesig("map: () {(t) -> u } -> Array") 25 | typesig("collect: () { (t) -> u } -> Array") 26 | typesig("map: () -> Enumerator") 27 | typesig("collect: () -> Enumerator") 28 | typesig("combination: (Fixnum) { (Array) -> %any } -> Array") 29 | typesig("combination: (Fixnum) -> Enumerator") 30 | typesig("push: (t) -> Array", {'mutate'=>true}) 31 | typesig("compact: () -> Array") 32 | typesig("'compact!': () -> Array") 33 | typesig("concat: (Array) -> Array") 34 | typesig("count: () -> Fixnum") 35 | typesig("count: (t) -> Fixnum") 36 | typesig("count: () { (t) -> %bool } -> Fixnum") 37 | typesig("cycle: (?Fixnum) { (t) -> %any } -> %any") 38 | typesig("cycle: (?Fixnum) -> Enumerator") 39 | typesig("delete: (u) -> t") 40 | typesig("delete: (u) { () -> v } -> t or v") 41 | typesig("delete_at: (Fixnum) -> Array") 42 | typesig("delete_if: () { (t) -> %bool } -> Array") 43 | typesig("delete_if: () -> Enumerator") 44 | typesig("drop: (Fixnum) -> Array") 45 | typesig("drop_while: () { (t) -> %bool } -> Array") 46 | typesig("drop_while: () -> Enumerator") 47 | typesig("each: () { (t) -> %any } -> Array") 48 | typesig("each_index: () { (Fixnum) -> %any } -> Array") 49 | typesig("each_index: () -> Enumerator") 50 | typesig("'empty?': () -> %bool") 51 | typesig("fetch: (Fixnum) -> t") 52 | typesig("fetch: (Fixnum, u) -> u") 53 | typesig("fetch: (Fixnum) { (Fixnum) -> u } -> t or u") 54 | typesig("fill: (t) -> Array") 55 | typesig("fill: (t,Fixnum,?Fixnum) -> Array") 56 | typesig("fill: (t, Range) -> Array") 57 | typesig("fill: () { (Fixnum) -> t } -> Array") 58 | typesig("fill: (Fixnum,?Fixnum) { (Fixnum) -> t } -> Array") 59 | typesig("fill: (Range) { (Fixnum) -> t } -> Array") 60 | typesig("index: (u) -> Fixnum") 61 | typesig("index: () { (t) -> %bool } -> Fixnum") 62 | typesig("index: () -> Enumerator") 63 | typesig("first: () -> t") 64 | typesig("first: (Fixnum) -> Array") 65 | typesig("'include?': (u) -> %bool") 66 | typesig("insert: (Fixnum, *t) -> Array") 67 | typesig("to_s: () -> String") 68 | typesig("inspect: () -> String") 69 | typesig("join: (?String) -> String") 70 | typesig("keep_if: () { (t) -> %bool } -> Array") 71 | typesig("last: () -> t") 72 | typesig("last: (Fixnum) -> Array") 73 | typesig("length: () -> Fixnum") 74 | typesig("permutation: (?Fixnum) -> Enumerator") 75 | typesig("permutation: (?Fixnum) { (Array) -> %any } -> Array") 76 | typesig("pop: (Fixnum) -> Array") 77 | typesig("pop: () -> t") 78 | typesig("product: (*Array) -> Array>") 79 | typesig("rassoc: (u) -> t") 80 | typesig("reject: () { (t) -> %bool } -> Array") 81 | typesig("reject: () -> Enumerator") 82 | typesig("'reject!': () { (t) -> %bool } -> Array") 83 | typesig("'reject!': () -> Enumerator") 84 | typesig("repeated_combination: (Fixnum) { (Array) -> %any } -> Array") 85 | typesig("repeated_combination: (Fixnum) -> Enumerator") 86 | typesig("repeated_permutation: (Fixnum) { (Array) -> %any } -> Array") 87 | typesig("repeated_permutation: (Fixnum) -> Enumerator") 88 | typesig("reverse: () -> Array") 89 | typesig("'reverse!': () -> Array") 90 | typesig("reverse_each: () { (t) -> %any } -> Array") 91 | typesig("reverse_each: () -> Enumerator") 92 | typesig("rindex: (u) -> t") 93 | typesig("rindex: () { (t) -> %bool } -> Fixnum") 94 | typesig("rindex: () -> Enumerator") 95 | typesig("rotate: (?Fixnum) -> Array") 96 | typesig("'rotate!': (?Fixnum) -> Array") 97 | typesig("sample: () -> t") 98 | typesig("sample: (Fixnum) -> Array") 99 | typesig("select: () { (t) -> %bool } -> Array") 100 | typesig("select: () -> Enumerator") 101 | typesig("'select!': () { (t) -> %bool } -> Array") 102 | typesig("'select!': () -> Enumerator") 103 | typesig("shift: () -> t") 104 | typesig("shift: (Fixnum) -> Array") 105 | typesig("shuffle: () -> Array") 106 | typesig("'shuffle!': () -> Array") 107 | typesig("size: () -> Fixnum") 108 | typesig("slice: (Range) -> Array", {'unwrap'=>[0]}) 109 | typesig("slice: (Fixnum, Fixnum) -> Array") 110 | typesig("slice: (Fixnum) -> t", {'unwrap'=>[0]}) 111 | typesig("slice: (Float) -> t", {'unwrap'=>[0]}) 112 | typesig("'slice!': (Range) -> Array", {'unwrap'=>[0]}) 113 | typesig("'slice!': (Fixnum, Fixnum) -> Array") 114 | typesig("'slice!': (Fixnum) -> t", {'unwrap'=>[0]}) 115 | typesig("'slice!': (Float) -> t", {'unwrap'=>[0]}) 116 | typesig("sort: () -> Array") 117 | typesig("sort: () { (t,t) -> Fixnum } -> Array") 118 | typesig("'sort!': () -> Array") 119 | typesig("'sort!': () { (t,t) -> Fixnum } -> Array") 120 | typesig("'sort_by!': () { (t) -> u } -> Array") 121 | typesig("'sort_by!': () -> Enumerator") 122 | typesig("take: (Fixnum) -> Array") 123 | typesig("take_while: () { (t) ->%bool } -> Array") 124 | typesig("take_while: () -> Enumerator") 125 | typesig("to_a: () -> Array", {'unwrap' => [-1]}) 126 | typesig("to_ary: () -> Array", {'unwrap' => [-1]}) 127 | typesig("transponse: () -> Array") 128 | typesig("uniq: () -> Array") 129 | typesig("'uniq!': () -> Array") 130 | typesig("unshift: (*t) -> Array") 131 | typesig("values_at: (*Range or Fixnum) -> Array") 132 | typesig("zip: (*Array) -> Array>", {'unwrap' => [0]}) 133 | typesig("'|': (Array) -> Array") 134 | end 135 | 136 | class Rtc::NativeArray < Array 137 | instance_methods.each { 138 | |m| 139 | if self.method_defined?(Rtc::MethodWrapper.mangle_name(m, Array.name)) 140 | eval("alias :#{m} :#{Rtc::MethodWrapper.mangle_name(m, Array.name)}") 141 | end 142 | } 143 | end 144 | 145 | class Hash 146 | rtc_annotated [:k, :each_key], [:v, :each_value] 147 | 148 | typesig("'[]' : (k) -> v", {'unwrap'=>[0]}) 149 | typesig("'[]=' : (k, v) -> v", {'unwrap'=>[0]}) 150 | typesig("store: (k,v) -> v", {'unwrap' => [0]}) 151 | typesig("assoc: (k) -> Tuple") 152 | typesig("clear: () -> Hash") 153 | typesig("compare_by_identity: () -> Hash") 154 | typesig("'compare_by_indentity?': () -> %bool") 155 | typesig("default: (?k) -> v") 156 | typesig("'default=': (v) -> v") 157 | typesig("default_proc: () -> (Hash,k) -> v") 158 | typesig("'default_proc=': ((Hash,k) -> v) -> (Hash,k) -> v") 159 | typesig("delete: (k) -> v") 160 | typesig("delete: (k) { (k) -> u } -> u or v") 161 | typesig("delete_if: () { (k,v) -> %bool } -> Hash") 162 | typesig("delete_if: () -> Enumerator") 163 | typesig("each: () { (k,v) -> %any } -> Hash") 164 | typesig("each: () -> Enumerator") 165 | typesig("each_pair: () { (k,v) -> %any } -> Hash") 166 | typesig("each_pair: () -> Enumerator") 167 | typesig("each_key: () { (k) -> %any } -> Hash") 168 | typesig("each_key: () -> Enumerator") 169 | typesig("each_value: () { (v) -> %any } -> Hash") 170 | typesig("each_value: () -> Enumerator") 171 | typesig("'empty?': () -> %bool") 172 | typesig("fetch: (k) -> v") 173 | typesig("fetch: (k,u) -> u or v") 174 | typesig("fetch: (k) { (k) -> u } -> u or v") 175 | typesig("'member?': (t) -> %bool") 176 | typesig("'has_key?': (t) -> %bool") 177 | typesig("'key?': (t) -> %bool") 178 | typesig("'has_value?': (t) -> %bool") 179 | typesig("'value?': (t) -> %bool") 180 | typesig("to_s: () -> String") 181 | typesig("inspect: () -> String") 182 | typesig("invert: () -> Hash") 183 | typesig("keep_if: () { (k,v) -> %bool } -> Hash") 184 | typesig("keep_if: () -> Enumerator") 185 | typesig("key: (t) -> k") 186 | typesig("keys: () -> Array") 187 | typesig("length: () -> Fixnum") 188 | typesig("size: () -> Fixnum") 189 | typesig("merge: (Hash) -> Hash") 190 | typesig("merge: (Hash) { (k,v,b) -> v or b } -> Hash") 191 | typesig("rassoc: (k) -> Tuple") 192 | typesig("rehash: () -> Hash") 193 | typesig("reject: () -> Enumerator") 194 | typesig("reject: () { (k,v) -> %bool } -> Hash") 195 | typesig("'reject!': () { (k,v) -> %bool } -> Hash") 196 | typesig("select: () { (k,v) -> %bool } -> Hash") 197 | typesig("'select!': () { (k,v) -> %bool } -> Hash") 198 | typesig("shift: () -> Tuple") 199 | typesig("to_a: () -> Array>") 200 | typesig("to_hash: () -> Hash") 201 | typesig("values: () -> Array") 202 | typesig("values_at: (*k) -> Array") 203 | end 204 | 205 | class Rtc::NativeHash 206 | instance_methods.each { 207 | |m| 208 | if self.method_defined?(Rtc::MethodWrapper.mangle_name(m, Hash.name)) 209 | eval("alias :#{m} :#{Rtc::MethodWrapper.mangle_name(m, Hash.name)}") 210 | end 211 | } 212 | end 213 | -------------------------------------------------------------------------------- /lib/rtc/typing/noncore_base_types/base_csv.rb: -------------------------------------------------------------------------------- 1 | class CSV 2 | end 3 | -------------------------------------------------------------------------------- /lib/rtc/typing/type_signatures.rb: -------------------------------------------------------------------------------- 1 | # Type signature classes. 2 | 3 | module Rtc 4 | class TypeAbbreviation 5 | attr_accessor :type_name, :aliased_type 6 | def initialize(t_name, type) 7 | @type_name = t_name 8 | @aliased_type = type 9 | end 10 | end 11 | class TypeSignature 12 | 13 | attr_accessor :pos 14 | attr_accessor :id 15 | attr_accessor :type 16 | 17 | def initialize(pos, id, type) 18 | @pos = pos 19 | @id = id 20 | @type = type 21 | end 22 | 23 | def to_s() 24 | mname = @id.to_s 25 | mname = "'#{mname}'" unless mname =~ /^\w.*$/ 26 | "#{mname}: #{type}" 27 | end 28 | 29 | def format(ppf) 30 | ppf.text("typesig(\"") 31 | ppf.group(5) do 32 | mname = @id.to_s 33 | mname = "'#{mname}'" unless mname =~ /^\w.*$/ 34 | ppf.text("#{mname} :") 35 | # ppf.group(mname.to_s.length+3) do 36 | ppf.group(2) do 37 | ppf.breakable("") 38 | type.format(ppf) 39 | end 40 | ppf.breakable("") 41 | ppf.text("\")") 42 | end 43 | ppf.breakable("") 44 | end 45 | 46 | end 47 | 48 | class AbstractMethodTypeSignature < TypeSignature 49 | 50 | # intersection type and polymorphic type are special cases 51 | def to_s() 52 | str = 53 | if type.instance_of? Rtc::Types::IntersectionType 54 | then intertype_to_s() 55 | #TODO(jtoman): add in support for polymorphism 56 | #elsif type.instance_of? PolyMethodType 57 | # then polytype_to_s() 58 | else super() # otherwise same 59 | end 60 | return str 61 | end 62 | 63 | def format(ppf) 64 | if type.instance_of? Rtc::Types::IntersectionType 65 | format_intertypes(ppf, @id) 66 | #TODO(jtoman): add in support for polymorphism 67 | #elsif type.instance_of? PolyMethodType 68 | # format_polytypes(ppf, @type) 69 | else 70 | super 71 | end 72 | end 73 | 74 | private 75 | 76 | # TODO 77 | def format_intertypes(ppf, id) 78 | type.types.each do |type| 79 | ppf.breakable() 80 | ppf.text "#{id.to_s} :" 81 | ppf.group(id.to_s.length + 3) do 82 | @type.format(ppf) 83 | end 84 | end 85 | end 86 | 87 | # TODO 88 | def format_polytypes(ppf, sig) 89 | ppf.breakable() 90 | ppf.text polytype_to_s() 91 | end 92 | 93 | # TODO 94 | def intertype_to_s() 95 | strs = @type.types.map do |type| 96 | ( 97 | #TODO(jtoman): add back in for polymorphism 98 | #type.kind_of?(PolyMethodType) ? polytype_to_s() : 99 | "#{@id.to_s} : #{@type}") 100 | end 101 | return strs.join("\n") 102 | end 103 | 104 | def polytype_to_s() 105 | "#{@id.to_s}<#{@type.tparams.join(',')}> : #{@type}" 106 | end 107 | 108 | end 109 | class MethodTypeSignature < AbstractMethodTypeSignature; end 110 | class ClassMethodTypeSignature < AbstractMethodTypeSignature; end 111 | 112 | # no need to do anything different; the name should be starting with @ 113 | # TODO: maybe add this name checking code? 114 | class InstanceVariableTypeSignature < TypeSignature 115 | end 116 | 117 | # no need to do anything different; the name should be starting with @@ 118 | # TODO: maybe add this name checking code? 119 | class ClassVariableTypeSignature < TypeSignature 120 | end 121 | 122 | # no need to do anything different; the name should be starting with a 123 | # captial letter 124 | # TODO: maybe add this name checking code? 125 | class ConstantTypeSignature < TypeSignature 126 | end 127 | 128 | end # module Rtc 129 | 130 | -------------------------------------------------------------------------------- /lib/rtc/typing/types.rb: -------------------------------------------------------------------------------- 1 | # Classes for representing the various types recognized by rtc. 2 | # 3 | # Author:: Ryan W Sims (rwsims@umd.edu) 4 | 5 | require 'set' 6 | require 'singleton' 7 | require 'rtc/runtime/native' 8 | 9 | require 'rtc/runtime/class_loader' 10 | require 'rtc/runtime/type_inferencer' 11 | 12 | Dir.glob(File.dirname(__FILE__) + "/types/*.rb").each { |file| 13 | require_relative file 14 | } 15 | 16 | class Rtc::GlobalCache 17 | @@cache = Rtc::NativeHash.new 18 | def self.cache 19 | @@cache 20 | end 21 | end 22 | 23 | class Object 24 | def rtc_meta 25 | if defined? @_rtc_meta 26 | @_rtc_meta 27 | else 28 | if frozen? and Rtc::GlobalCache.cache[object_id] 29 | return Rtc::GlobalCache.cache[object_id] 30 | end 31 | to_return = Rtc::NativeHash.new; 32 | to_return[:annotated] = false 33 | to_return[:no_subtype] = false 34 | to_return[:iterators] = Rtc::NativeHash.new 35 | to_return[:_type] = nil 36 | to_return[:proxy_context] = Rtc::NativeArray.new 37 | if frozen? 38 | Rtc::GlobalCache.cache[object_id] = to_return 39 | else 40 | @_rtc_meta = to_return 41 | end 42 | end 43 | end 44 | 45 | def rtc_gc 46 | Rtc::GlobalCache.cache[object_id] = nil 47 | end 48 | 49 | @@old_freeze_meth = Object.instance_method(:freeze) 50 | 51 | def freeze 52 | # force the meta property to be intialized 53 | if not frozen? 54 | rtc_meta 55 | end 56 | @@old_freeze_meth.bind(self).call() 57 | end 58 | 59 | def rtc_type 60 | return Rtc::Types::BottomType.new if self == nil 61 | return rtc_get_type 62 | end 63 | 64 | def rtc_typeof(name, which_class = nil) 65 | name = name.to_s 66 | if name[0] == "@" 67 | self.rtc_type.get_field(name[1..-1], which_class) 68 | else 69 | self.rtc_type.get_method(name, which_class) 70 | end 71 | end 72 | 73 | def rtc_is_complex? 74 | if self.nil? 75 | false 76 | end 77 | not Rtc::Types::NominalType.of(self.class).type_parameters.empty? 78 | end 79 | 80 | protected 81 | 82 | def rtc_get_type 83 | if self.class.name == "Symbol" 84 | Rtc::Types::SymbolType.new(self) 85 | else 86 | class_obj = Rtc::Types::NominalType.of(self.class) 87 | 88 | if class_obj.type_parameters.size == 0 89 | class_obj 90 | elsif class_obj.klass == Array 91 | Rtc::Types::ParameterizedType.new(class_obj, Rtc::NativeArray[Rtc::TypeInferencer.infer_type(self.each)], true) 92 | elsif class_obj.klass == Set 93 | Rtc::Types::ParameterizedType.new(class_obj, [Rtc::TypeInferencer.infer_type(self.each)], true) 94 | elsif class_obj.klass == Hash 95 | Rtc::Types::ParameterizedType.new(class_obj, Rtc::NativeArray[ 96 | Rtc::TypeInferencer.infer_type(self.each_key), 97 | Rtc::TypeInferencer.infer_type(self.each_value) 98 | ], true) 99 | else 100 | #user defined parameterized classes 101 | iterators = class_obj.klass.rtc_meta[:iterators] 102 | tv = class_obj.type_parameters.map { 103 | |param| 104 | if iterators[param.symbol].nil? 105 | raise "Cannot infer type of type parameter #{param.symbol} on class #{self.class}" 106 | elsif iterators[param.symbol].is_a?(Proc) 107 | # this is a function call, not a hash lookup by the way 108 | # despite what the weird operator would imply 109 | enum = iterators[param.symbol][self] 110 | else 111 | enum = self.send(iterators[param.symbol]) 112 | end 113 | Rtc::TypeInferencer.infer_type(enum) 114 | } 115 | 116 | Rtc::Types::ParameterizedType.new(class_obj, tv, true) 117 | end 118 | end 119 | end 120 | end 121 | 122 | class Module 123 | def rtc_get_type 124 | return Rtc::Types::NominalType.of(class << self; self; end) 125 | end 126 | end 127 | 128 | class Class 129 | def rtc_instance_typeof(name, include_super = true) 130 | my_type = Rtc::Types::NominalType.of(self) 131 | name = name.to_s 132 | if name[0] == "@" 133 | my_type.get_field(name[1..-1], include_super ? nil : self) 134 | else 135 | my_type.get_method(name, include_super ? nil : self) 136 | end 137 | end 138 | end 139 | 140 | class Rtc::TypeNarrowingError < StandardError; end 141 | 142 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/bottom.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | require_relative './terminal' 3 | 4 | 5 | module Rtc::Types 6 | class BottomType < Type 7 | include TerminalType 8 | def hash 9 | 13 10 | end 11 | 12 | def initialize() 13 | super() 14 | end 15 | 16 | def map 17 | return self 18 | end 19 | 20 | def each 21 | yield self 22 | end 23 | 24 | def has_method?(m) 25 | nil.respond_to?(m) 26 | end 27 | 28 | def to_s 29 | "b" 30 | end 31 | 32 | def eql?(other) 33 | other.instance_of?(BottomType) 34 | end 35 | 36 | def <=(other) 37 | # eql?(other) 38 | true 39 | end 40 | 41 | def self.instance 42 | return @@instance || (@@instance = BottomType.new) 43 | end 44 | 45 | @@instance = nil 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/hash.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | require 'rtc/runtime/native' 3 | 4 | module Rtc::Types 5 | class HashType < Type 6 | attr_reader :optional, :required, :num_required 7 | def initialize(type_mapping) 8 | @type_map = type_mapping 9 | @num_required = 0 10 | @required = Rtc::NativeHash.new 11 | @optional = Rtc::NativeHash.new 12 | type_mapping.each { 13 | |k,t| 14 | if t.is_a?(OptionalArg) 15 | @optional[k] = t.type 16 | else 17 | @required[k] = t 18 | @num_required += 1 19 | end 20 | } 21 | end 22 | 23 | def each 24 | @type_map.each { 25 | |k,t| 26 | yield t 27 | } 28 | end 29 | def map 30 | mapped_types = Rtc::NativeHash.new 31 | @type_map.each { 32 | |k,t| 33 | mapped_types[k] = yield t 34 | } 35 | HashType.new(mapped_types) 36 | end 37 | 38 | def has_member?(name) 39 | return @type_map.has_key?(name) 40 | end 41 | 42 | def get_member_type(name) 43 | return @required[name] if @required.has_key?(name) 44 | return @optional[name] if @optional.has_key?(name) 45 | return nil 46 | end 47 | 48 | def <=(other) 49 | case other 50 | when HashType 51 | other.required.each { 52 | |k,v| 53 | return false unless @required.has_key?(k) 54 | return false unless @required[k] <= v 55 | } 56 | # optional members in the lhs can be "filled" by either 57 | # optional or required members, but we have to make sure we 58 | # don't have a type mismatch 59 | other.optional.each { 60 | |k,v| 61 | next unless has_member?(k) 62 | return false unless get_member_type(k) <= v 63 | } 64 | else 65 | super 66 | end 67 | end 68 | 69 | def to_s 70 | components = Rtc::NativeArray.new 71 | @type_map.each { 72 | |k,v| 73 | components << "#{k.inspect} => #{v.to_s}" 74 | } 75 | return "{"+components.join(", ")+"}" 76 | end 77 | 78 | def ==(other) 79 | return false unless other.is_a?(HashType) 80 | return other.instance_variable_get(:@type_map) == @type_map 81 | end 82 | 83 | def eql?(other) 84 | self == other 85 | end 86 | 87 | # in the interest of correctness, this is actually pretty involved 88 | # unfortunately 89 | def hash 90 | if not defined?(@cached_hash) 91 | # this is really a specialized version of the hash 92 | # builder. Code duplication? Yes. That big a deal? Not really. 93 | current_hash = 269 94 | @optional.each { 95 | |k,t| 96 | current_hash += 503 * ((431 + k.hash * 443) + t.hash * 443) 97 | } 98 | @require.each { 99 | |k,t| 100 | current_hash += 503 * ((497 + k.hash * 419) + t.hash * 419) 101 | } 102 | @cached_hash = current_hash 103 | end 104 | @cached_hash 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/intersection.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | require_relative './type' 3 | 4 | module Rtc::Types 5 | # Type that represents the intersection of two other types. The intersection 6 | # of two types is the type that supports all of the operations of both 7 | # types. 8 | class IntersectionType < Type 9 | # Return a type representing the intersection of all the types in 10 | # +types+. Handles some special cases to simplify the resulting type: 11 | # 1. If +a+ and +b+ are both in +types+ and +a+ <= +b+, only +a+ will 12 | # remain in the returned type. 13 | # 2. If after removing supertypes as in 1 there is only one type left in 14 | # the list, it is returned unchanged. 15 | def self.of(types) 16 | return types[0] if types.size == 1 17 | pairs = types.product(types) 18 | 19 | 20 | pairs.each do |t, u| 21 | #puts "T = #{t.inspect} U = #{u.inspect} #{t <= u} #{u <= t} #{t.equal?(u)}" 22 | # pairs includes [t, t] and [u, u], and since t <= t, skip 23 | # these. 24 | next if t <= u and u <= t 25 | # pairs includes [t, u] and [u, t], so we don't do symmetric 26 | # checks. 27 | if t <= u 28 | types.delete(u) 29 | end 30 | end 31 | return types[0] if types.size == 1 32 | return IntersectionType.new(types) 33 | end 34 | 35 | def contains_free_variables? 36 | types.reduce(false) { |r, t| r |= t.contains_free_variables? } 37 | end 38 | 39 | def free_vars 40 | types.reduce([]) { |r, t| r.concat(t.free_vars).uniq } 41 | end 42 | 43 | def map 44 | IntersectionType.new(types.map { 45 | |t| 46 | yield t 47 | }) 48 | end 49 | 50 | def each 51 | types.each { 52 | |t| 53 | yield t 54 | } 55 | end 56 | 57 | # The set of all the types intersected in this instance. 58 | attr_accessor :types 59 | 60 | # Returns +true+ if +self+ subtypes +other+. Other than TopType, an 61 | # IntersectionType can only subtype another IntersectionType. For two 62 | # IntersectionTypes +t+ and +u+, +t <= u+ iff for all +a+ in +u+, there 63 | # exists +b+ in +t+ such that +b <= a+. 64 | # 65 | # Note that this function is not precise: there could be types A, B and 66 | # C such that Intersection(A, B) < C, but neither A nor B <= C. This 67 | # implementation is conservative: if it returns +true+, then subtyping 68 | # definitely holds, but if it returns +false+, it only probably does not 69 | # hold. 70 | def <=(other) 71 | case other 72 | when IntersectionType 73 | other.types.all? do |t| 74 | @types.any? do |u| 75 | u <= t 76 | end 77 | end 78 | else 79 | @types.all? do |t| 80 | t <= other 81 | end 82 | end 83 | end 84 | 85 | def to_s # :nodoc: 86 | "(#{@types.to_a.join(' and ')})" 87 | end 88 | 89 | def eql?(other) # :nodoc: 90 | return false unless other.instance_of?(IntersectionType) 91 | return false unless other.types == @types 92 | true 93 | end 94 | 95 | def hash # :nodoc: 96 | @types.hash 97 | end 98 | 99 | protected 100 | 101 | def can_subtype?(other) 102 | true 103 | end 104 | 105 | private 106 | 107 | # Create a new IntersectionType that is the intersection of all the 108 | # types in +types+. The initializer is private since IntersectionTypes 109 | # should be created via the IntersectionType#of method. 110 | def initialize(types) 111 | @types = Set.new(types) 112 | super() 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/lazy_nominal.rb: -------------------------------------------------------------------------------- 1 | require_relative './nominal' 2 | 3 | module Rtc::Types 4 | #this class still has a lot of limitations. For a list of the limitations 5 | # see the comment on commit a8c57329554a8616f2f1a742275d66bbc6424923 6 | class LazyNominalType < NominalType 7 | proxied_methods = (NominalType.instance_methods(false) + [:method_names, :field_names]) - [:to_s, :inspect, :eql?, :==, :hash] 8 | proxied_methods.each do 9 | |proxied_mname| 10 | define_method(proxied_mname, lambda { 11 | |*args| 12 | concrete_obj.send(proxied_mname, *args) 13 | }) 14 | end 15 | 16 | def to_s 17 | if @concrete_obj 18 | @concrete_obj.to_s 19 | else 20 | internal_repr 21 | end 22 | end 23 | 24 | def inspect 25 | if @concrete_obj 26 | @concrete_obj.inspect 27 | else 28 | internal_repr 29 | end 30 | end 31 | 32 | def eql?(other) 33 | if other.instance_of?(NominalType) 34 | return @concrete_obj == other if has_obj 35 | false 36 | elsif other.instance_of?(LazyNominalType) 37 | if other.has_obj and self.has_obj 38 | @concrete_obj == other.concrete_obj 39 | elsif not other.has_obj and not self.has_obj 40 | @context == other.context and @ident == other.ident 41 | else 42 | false 43 | end 44 | else 45 | false 46 | end 47 | end 48 | 49 | def hash 50 | hash_builder = Rtc::HashBuilder.new(1009,1601) 51 | hash_builder.include(@context) 52 | hash_builder.include(@ident) 53 | hash_builder.get_hash 54 | end 55 | 56 | def initialize(ident, context) 57 | @ident = ident 58 | @context = context 59 | @concrete_obj = nil 60 | super(nil) 61 | end 62 | 63 | protected 64 | 65 | def has_obj 66 | @concrete_obj != nil 67 | end 68 | 69 | attr_reader :ident, :context 70 | 71 | def internal_repr 72 | "LazyNominalClass(#{id})<#{Rtc::ClassLoader.ident_to_s(@ident)},#{@context}" 73 | end 74 | 75 | def concrete_obj 76 | if @concrete_obj 77 | @concrete_obj 78 | else 79 | @concrete_obj = NominalType.of(Rtc::ClassLoader.load_class(@ident, @context)) 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/nominal.rb: -------------------------------------------------------------------------------- 1 | require 'rtc/runtime/native' 2 | require_relative './structural' 3 | require_relative './terminal' 4 | 5 | module Rtc::Types 6 | 7 | # The simplest kind of type, where the name is the same as the type, such as 8 | # Fixnum, String, Object, etc. 9 | class NominalType < StructuralType 10 | include TerminalType 11 | class InheritanceChainIterator 12 | #TODO(jtoman): add support for Enumerators? 13 | def initialize(class_obj) 14 | @it_class = class_obj 15 | end 16 | 17 | def next 18 | return nil if @it_class.nil? 19 | to_return = @it_class 20 | if @it_class.rtc_meta[:no_subtype] 21 | @it_class = nil 22 | else 23 | @it_class = @it_class.superclass 24 | end 25 | to_return 26 | end 27 | end 28 | 29 | # A cache of NominalType instances, keyed by Class constants. 30 | @@cache = Rtc::NativeHash.new 31 | 32 | # The constant Class instance for this type. 33 | attr_reader :klass 34 | # Return a NominalType instance of type +klass+. Multiple calls with the 35 | # same +klass+ will return the same instance. 36 | # 37 | # The field and method maps will be stored as accessors in the wrapped 38 | # Class instance. This will clobber any existing attributes named 39 | # +field_types+ or +method_types+ in the instance. 40 | def self.of(klass) 41 | t = @@cache[klass] 42 | if not t 43 | t = NominalType.new(klass) 44 | @@cache[klass] = t 45 | end 46 | return t 47 | end 48 | 49 | def map 50 | return self 51 | end 52 | 53 | def has_method?(method) 54 | method = method.to_sym 55 | self.klass.method_defined?(method) 56 | end 57 | 58 | def type_parameters 59 | @type_parameters 60 | end 61 | 62 | def superclass 63 | if @klass.rtc_meta[:no_subtype] or not @klass.superclass 64 | nil 65 | else 66 | NominalType.of(@klass.superclass) 67 | end 68 | end 69 | 70 | def type_parameters=(t_params) 71 | @type_parameters = t_params.map { 72 | |t_param| 73 | if t_param.instance_of?(Symbol) 74 | TypeParameter.new(t_param) 75 | elsif t_param.instance_of?(TypeParameter) 76 | t_param 77 | else 78 | raise "invalid type parameter specified" 79 | end 80 | } 81 | end 82 | 83 | # Return +true+ if +self+ represents a subtype of +other+. 84 | def <=(other) 85 | case other 86 | when NominalType 87 | other_class = other.klass 88 | return true if other_class.name == @name 89 | it_class = @klass 90 | while it_class 91 | return true if it_class == other_class 92 | it_class = it_class.rtc_meta[:no_subtype] ? 93 | nil : it_class.superclass 94 | end 95 | return false 96 | when ParameterizedType 97 | false 98 | when TupleType 99 | false 100 | when TopType 101 | true 102 | when StructuralType 103 | super(other) 104 | else 105 | super(other) 106 | end 107 | end 108 | 109 | def to_s # :nodoc: 110 | return @klass.to_s 111 | end 112 | 113 | def inspect 114 | return "NominalType(#{id})<#{@klass}>" 115 | end 116 | 117 | # Return +true+ if +other+ is a NominalType with the same +klass+ as 118 | # +self. 119 | def eql?(other) 120 | return false unless other.instance_of?(NominalType) 121 | return false unless other.klass == @klass 122 | true 123 | end 124 | 125 | def hash # :nodoc: 126 | return @klass.name.hash 127 | end 128 | 129 | def to_real 130 | self 131 | end 132 | 133 | def add_field(name,type) 134 | if extant_type = @field_types[name] 135 | if extant_type.instance_of?(UnionType) 136 | @field_types[name] = UnionType.of(extant_type.types + [type]) 137 | else 138 | @field_types[name] = UnionType.of([extant_type, type]) 139 | end 140 | else 141 | @field_types[name] = type 142 | end 143 | end 144 | 145 | def add_method(name, type) 146 | if @method_types[name] 147 | extant_type = @method_types[name] 148 | if extant_type.instance_of?(IntersectionType) 149 | type = [type] + extant_type.types.to_a 150 | else 151 | type = [type, extant_type] 152 | end 153 | type = IntersectionType.of(type) 154 | end 155 | 156 | @method_types[name] = type 157 | end 158 | 159 | def get_method(name, which = nil, type_subst = nil) 160 | it = self 161 | if which 162 | while it and which != it.klass 163 | it = it.superclass 164 | end 165 | if it.nil? 166 | return nil 167 | end 168 | type_hash = it.method_types 169 | if m_type = type_hash[name] 170 | m_type.is_a?(IntersectionType) ? 171 | m_type.map { |m| m.instantiate(type_subst) } : 172 | m_type.instantiate(type_subst) 173 | else 174 | nil 175 | end 176 | else 177 | found = false 178 | 179 | klass.ancestors.each {|a| 180 | it = Rtc::Types::NominalType.of(a) 181 | 182 | if it.method_types.has_key?(name) 183 | found = true 184 | break 185 | end 186 | } 187 | 188 | it = nil if not found 189 | 190 | if klass.ancestors[0] != klass 191 | it = self 192 | while it and not it.method_types.has_key?(name) 193 | it = it.superclass 194 | end 195 | end 196 | 197 | return nil if it.nil? 198 | 199 | m_type = it.method_types[name] 200 | 201 | m_type.is_a?(IntersectionType) ? m_type.map { |m| m.instantiate(type_subst) } : 202 | m_type.instantiate(type_subst) 203 | end 204 | end 205 | 206 | def get_field(name, which = nil) 207 | if @field_types[name] && (which.nil? or which == @klass) 208 | @field_types[name] 209 | else 210 | (sc = superclass) ? sc.get_field(name) : nil 211 | end 212 | end 213 | 214 | def method_names 215 | super + ((sc = superclass) ? sc.method_names : []) 216 | end 217 | 218 | def field_names 219 | super + ((sc = superclass) ? sc.field_names : []) 220 | end 221 | 222 | protected 223 | 224 | def method_types 225 | @method_types 226 | end 227 | 228 | def field_types 229 | @field_types 230 | end 231 | 232 | def can_subtype?(other) 233 | (other.instance_of?(StructuralType) or 234 | other.instance_of?(NominalType)) 235 | end 236 | 237 | private 238 | 239 | # Create a new NominalType. 240 | # 241 | # +initialize+ is private since NominalTypes should only be created 242 | # through the NominalType.of method. 243 | # 244 | # [+klass+] The constant Class instance of this type, i.e. Fixnum, 245 | # String, etc. 246 | def initialize(klass) 247 | super(Rtc::NativeHash.new,Rtc::NativeHash.new) 248 | @klass = klass 249 | @type_parameters = [] 250 | @name = klass.name 251 | end 252 | end 253 | end 254 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/optional.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | 3 | module Rtc::Types 4 | 5 | # An object used to wrap a type for an optional argument to a procedure. 6 | # This does not represent a node in the constraint graph; constraints should 7 | # be generated on the +type+ attribute. 8 | class OptionalArg < Type 9 | attr_accessor :type 10 | 11 | def each 12 | yield type 13 | end 14 | 15 | def initialize(type) 16 | @type = type 17 | super() 18 | end 19 | 20 | def map 21 | OptionalArg.new(yield type) 22 | end 23 | 24 | def to_s 25 | "?(#{type})" 26 | end 27 | 28 | def eql?(other) 29 | other.instance_of?(OptionalArg) and type.eql?(other.type) 30 | end 31 | 32 | def hash 33 | 23 + type.hash 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/parameterized.rb: -------------------------------------------------------------------------------- 1 | require_relative './structural' 2 | require 'rtc/runtime/native' 3 | require 'rtc/tools/hash-builder.rb' 4 | 5 | module Rtc::Types 6 | # A type that is parameterized on one or more other types. The base type 7 | # must be a NominalType, while the parameters can be any type. 8 | class ParameterizedType < StructuralType 9 | # The NominalType that is being parameterized. 10 | attr_accessor :nominal 11 | # The list of type parameters, in order. 12 | attr_accessor :parameters 13 | attr_accessor :dynamic 14 | 15 | # Creates a new ParameterizedType, parameterizing +nominal+ with 16 | # +parameters+. Note that +parameters+ must be listed in order. 17 | def initialize(nominal, parameters, dynamic = false) 18 | if nominal.type_parameters.size > 0 19 | raise Exception.new("Type parameter mismatch") unless parameters.size == nominal.type_parameters.length 20 | end 21 | 22 | @nominal = nominal 23 | @parameters = parameters 24 | @method_cache = Rtc::NativeHash.new 25 | @dynamic = dynamic 26 | super({},{}) 27 | end 28 | 29 | def each 30 | yield @nominal 31 | @parameters.each { 32 | |p| 33 | yield p 34 | } 35 | end 36 | 37 | def has_method?(method) 38 | @nominal.has_method?(method) 39 | end 40 | 41 | def map 42 | new_nominal = yield @nominal 43 | new_params = Rtc::NativeArray.new 44 | parameters.each { 45 | |p| 46 | new_params << (yield p) 47 | } 48 | ParameterizedType.new(new_nominal, new_params, dynamic) 49 | end 50 | 51 | def parameterized? 52 | true 53 | end 54 | 55 | def <=(other) 56 | case other 57 | when ParameterizedType 58 | return false unless (@nominal <= other.nominal) 59 | 60 | zipped = @parameters.zip(other.parameters) 61 | if not @dynamic 62 | return false unless zipped.all? do |t, u| 63 | # because type binding is done via the subtyping operationg 64 | # we can't to t <= u, u <= t as uninstantiate type variables 65 | # do not have a meaningful subtype rule 66 | if u.instance_of?(TypeVariable) 67 | t <= u 68 | else 69 | t <= u and u <= t 70 | end 71 | end 72 | else 73 | return false unless zipped.all? do |t, u| 74 | t <= u 75 | end 76 | end 77 | true 78 | when NominalType 79 | if other.klass == Object 80 | true 81 | else 82 | false 83 | end 84 | when TupleType 85 | false 86 | else 87 | super(other) 88 | end 89 | end 90 | 91 | def hash 92 | builder = Rtc::HashBuilder.new(23, 41) 93 | builder.include(@nominal) 94 | @parameters.each do |p| 95 | #builder.include(p.pointed_type) 96 | builder.include(p) 97 | end 98 | builder.get_hash 99 | end 100 | 101 | def eql?(other) 102 | return false unless other.instance_of?(ParameterizedType) 103 | return false unless other.nominal.eql?(@nominal) 104 | return false unless other.parameters.eql?(@parameters) 105 | true 106 | end 107 | 108 | def type_of_param(param) 109 | param = @nominal.type_parameters.index(TypeParameter.new(param)) if param.class.name == "Symbol" 110 | @parameters[param].pointed_type 111 | end 112 | 113 | def get_field(name, which = nil) 114 | return replace_type(@nominal.get_field(name, which)) 115 | end 116 | 117 | def get_method(name, which = nil, tvars = nil) 118 | replacement_map = tvars || Rtc::NativeHash.new 119 | if dynamic 120 | # no caching here folks 121 | @nominal.type_parameters.each_with_index { 122 | |t_param, type_index| 123 | replacement_map[t_param.symbol] ||= TypeVariable.new(t_param.symbol, self, parameters[type_index]) 124 | } 125 | to_ret = @nominal.get_method(name, which).replace_parameters(replacement_map) 126 | if to_ret.is_a?(IntersectionType) 127 | to_ret.each { 128 | |type| 129 | type.type_variables += replacement_map.values 130 | } 131 | else 132 | to_ret.type_variables += replacement_map.values 133 | end 134 | to_ret 135 | else 136 | if @method_cache[name] 137 | return @method_cache[name] 138 | end 139 | @nominal.type_parameters.each_with_index { 140 | |t_param, type_index| 141 | replacement_map[t_param.symbol] ||= parameters[type_index] 142 | } 143 | to_ret = @nominal.get_method(name, which, replacement_map) 144 | has_tvars = 145 | if to_ret.is_a?(IntersectionType) 146 | to_ret.types.any? { 147 | |type| 148 | not type.type_variables.empty? 149 | } 150 | else 151 | not to_ret.type_variables.empty? 152 | end 153 | if not has_tvars 154 | @method_cache[name] = to_ret 155 | end 156 | return to_ret 157 | end 158 | end 159 | 160 | def to_s 161 | "#{@nominal.klass.name}<#{parameters.join(", ")}>" 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/procedural.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | require 'rtc/runtime/native' 3 | 4 | module Rtc::Types 5 | 6 | # A type representing some method or block. ProceduralType has subcomponent 7 | # types for arguments (zero or more), block (optional) and return value 8 | # (exactly one). 9 | class ProceduralType < Type 10 | attr_reader :return_type 11 | attr_reader :arg_types 12 | attr_reader :block_type 13 | attr_reader :parameters 14 | attr_accessor :mutate 15 | attr_accessor :unwrap 16 | 17 | attr_accessor :type_variables 18 | # Create a new ProceduralType. 19 | # 20 | # [+return_type+] The type that the procedure returns. 21 | # [+arg_types+] List of types of the arguments of the procedure. 22 | # [+block_type+] The type of the block passed to this method, if it 23 | # takes one. 24 | # [+tvars+] The list of instantiated type variables that exist in this for this 25 | # method 26 | def initialize(parameters, return_type, arg_types=[], block_type=nil, tvars = [], meta={}) 27 | @parameters = parameters.nil? ? [] : parameters 28 | @return_type = return_type 29 | @arg_types = arg_types 30 | @block_type = block_type 31 | @type_variables = tvars 32 | @mutate = meta['mutate'].nil? ? false : meta['mutate'] 33 | @unwrap = meta['unwrap'].nil? ? [] : meta['unwrap'] 34 | super() 35 | end 36 | 37 | def map 38 | new_arg_types = Rtc::NativeArray.new 39 | arg_types.each { 40 | |p| 41 | new_arg_types << (yield p) 42 | } 43 | ProceduralType.new( 44 | parameters, 45 | (yield return_type), 46 | new_arg_types, 47 | block_type.nil? ? nil : (yield block_type), 48 | type_variables, 49 | { "mutate" => mutate, "unwrap" => unwrap } 50 | ) 51 | end 52 | 53 | def each 54 | yield return_type 55 | yield block_type if block_type 56 | arg_types.each { |a| yield a } 57 | end 58 | 59 | def instantiate(type_replacement = nil) 60 | if type_replacement.nil? and not parameterized? 61 | return self 62 | end 63 | free_variables = [] 64 | if not type_replacement.nil? 65 | duped = false 66 | parameters.each { 67 | |t_param| 68 | unless type_replacement.include?(t_param.symbol) 69 | if not duped 70 | type_replacement = type_replacement.dup 71 | duped = true 72 | end 73 | new_tv = TypeVariable.new(t_param.symbol, self) 74 | type_replacement[t_param.symbol] = new_tv 75 | free_variables.push(new_tv) 76 | end 77 | } 78 | else 79 | type_replacement = Rtc::NativeHash.new 80 | parameters.map { 81 | |t_param| 82 | new_tv = TypeVariable.new(t_param.symbol, self) 83 | type_replacement[t_param.symbol] = new_tv 84 | free_variables.push(new_tv) 85 | } 86 | end 87 | to_return = self.map { 88 | |t| 89 | t.replace_parameters(type_replacement) 90 | }; 91 | to_return.type_variables = free_variables 92 | return to_return 93 | end 94 | 95 | def parameterized? 96 | not parameters.empty? 97 | end 98 | 99 | def contains_free_variables? 100 | not type_variables.empty? 101 | end 102 | 103 | def free_vars 104 | type_variables.map { |v| v.name } 105 | end 106 | 107 | # Return true if +self+ is a subtype of +other+. This follows the usual 108 | # semantics of TopType and BottomType. If +other+ is also an instance of 109 | # ProceduralType, return true iff all of the following hold: 110 | # 1. +self+'s return type is a subtype of +other+'s (covariant). 111 | # 2. +other+'s block type is a subtype of +self+'s (contravariant. 112 | # 3. Both types have blocks, or neither type does. 113 | # 4. Both types have the same number of arguments, and +other+'s 114 | # arguments are subtypes of +self+'s (contravariant). 115 | def <=(other) 116 | case other 117 | when ProceduralType 118 | # Check number of arguments, presence of blocks, etc. 119 | return false unless compatible_with?(other) 120 | 121 | # Return types are covariant. 122 | return false unless @return_type <= other.return_type 123 | # Block types must both exist and are contravariant. 124 | if @block_type 125 | return false unless other.block_type <= @block_type 126 | end 127 | 128 | # Arguments are contravariant. 129 | @arg_types.zip(other.arg_types).each do |a, b| 130 | return false unless b <= a 131 | end 132 | return true 133 | when TupleType 134 | false 135 | else 136 | super(other) 137 | end 138 | end 139 | 140 | def to_s # :nodoc: 141 | if @block_type 142 | "[ (#{@arg_types.join(', ')}) " \ 143 | "{#{@block_type}} -> #{@return_type} ]" 144 | else 145 | if @arg_types 146 | "[ (#{@arg_types.join(', ')}) -> #{@return_type} ]" 147 | else 148 | "[ () -> #{@return_type} ]" 149 | end 150 | end 151 | end 152 | 153 | # Return true if all of the following are true: 154 | # 1. +other+ is a ProceduralType instance. 155 | # 2. Return types compare equal. 156 | # 3. Either neither +self+ nor +other+ have block types or they both do, 157 | # in which case the block types compare equal. 158 | # 4. +other+ has the same number of arguments as +self+. 159 | # 5. Respective argument types, if any, compare equal. 160 | def eql?(other) 161 | return false unless compatible_with?(other) 162 | return false unless @return_type == other.return_type 163 | if @block_type 164 | return false unless @block_type == other.block_type 165 | end 166 | @arg_types.zip(other.arg_types).each do |a, b| 167 | return false unless a == b 168 | end 169 | true 170 | end 171 | #returns the minimum number of arguments required by this function 172 | # i.e. a count of the required arguments. 173 | def min_args 174 | p_layout = parameter_layout 175 | p_layout[:required][0] + p_layout[:required][1] 176 | end 177 | #gets the maximum number of arguments this function can take. If there is a rest 178 | # argument, this function returns -1 (unlimited) 179 | def max_args 180 | p_layout = parameter_layout 181 | if p_layout[:rest] 182 | -1 183 | else 184 | min_args + p_layout[:opt] 185 | end 186 | end 187 | 188 | # gets a hash describing the layout of the arguments to a function 189 | # the requied member is a two member array that indicates the number of 190 | # required arugments at the beginning of the parameter list and the number 191 | # at the end respectively. The opt member indicates the number of optional 192 | # arguments. If rest is true, then there is a rest argument. 193 | # For reference, parameter lists are described by the following grammar 194 | # required*, optional*, rest?, required* 195 | def parameter_layout 196 | return @param_layout_cache if defined? @param_layout_cache 197 | a_list = arg_types + [nil] 198 | to_return = Rtc::NativeHash.new() 199 | to_return[:required] = Rtc::NativeArray[0,0] 200 | to_return[:rest] = false 201 | to_return[:opt] = 0 202 | def param_type(arg_type) 203 | case arg_type 204 | when NilClass 205 | :end 206 | when OptionalArg 207 | :opt 208 | when Vararg 209 | :rest 210 | else 211 | :req 212 | end 213 | end 214 | counter = 0 215 | i = 0 216 | p_type = param_type(a_list[i]) 217 | while p_type == :req 218 | counter+=1 219 | i+=1 220 | p_type = param_type(a_list[i]) 221 | end 222 | to_return[:required][0] = counter 223 | counter = 0 224 | while p_type == :opt 225 | counter+=1 226 | i+=1 227 | p_type = param_type(a_list[i]) 228 | end 229 | to_return[:opt] = counter 230 | if p_type == :rest 231 | to_return[:rest] = true 232 | i+=1 233 | p_type = param_type(a_list[i]) 234 | end 235 | counter = 0 236 | while p_type == :req 237 | counter+=1 238 | i+=1 239 | p_type = param_type(a_list[i]) 240 | end 241 | to_return[:required][1] = counter 242 | raise "Invalid argument string detected" unless p_type == :end 243 | @param_layout_cache = to_return 244 | end 245 | 246 | def hash # :nodoc: 247 | builder = Rtc::HashBuilder.new(17, 31) 248 | builder.include(@return_type) 249 | if @block_type 250 | builder.include(@block_type) 251 | end 252 | @arg_types.each do |arg| 253 | builder.include(arg) 254 | end 255 | builder.get_hash 256 | end 257 | 258 | protected 259 | 260 | def can_subtype?(other) 261 | other.instance_of?(ProceduralType) 262 | end 263 | 264 | private 265 | 266 | # Return true iff all of the following are true: 267 | # 1. +other+ is a ProceduralType. 268 | # 2. +other+ has the same number of arguments as +self+. 269 | # 3. +self+ and +other+ both have a block type, or neither do. 270 | def compatible_with?(other) 271 | return false unless other.instance_of?(ProceduralType) 272 | return false unless @arg_types.size() == other.arg_types.size() 273 | if @block_type 274 | return false unless other.block_type 275 | else 276 | return false if other.block_type 277 | end 278 | return true 279 | end 280 | end 281 | end 282 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/structural.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | require 'rtc/tools/hash-builder.rb' 3 | 4 | module Rtc::Types 5 | # A type defined by a collection of named fields and methods. Supertype of all objects 6 | class StructuralType < Type 7 | # Create a new StructuralType. 8 | # 9 | # [+field_types+] Map from field names as symbols to their types. 10 | # [+method_types+] Map from method names as symbols to their types. 11 | def initialize(field_types, method_types) 12 | @field_types = field_types 13 | @method_types = method_types 14 | super() 15 | end 16 | 17 | def map 18 | new_fields = {} 19 | new_methods = {} 20 | @field_types.each_pair { 21 | |field_name,field_type| 22 | new_fields[field_name] = yield field_type 23 | } 24 | @method_types.each_pair { 25 | |method_name, method_type| 26 | new_methods[method_name] = yield method_type 27 | } 28 | StructuralType.new(new_fields, new_methods) 29 | end 30 | 31 | def is_terminal 32 | false 33 | end 34 | 35 | def each 36 | @method_types.each_value { 37 | |m| 38 | yield m 39 | } 40 | @field_types.each_value { 41 | |f| 42 | yield f 43 | } 44 | end 45 | 46 | def field_names 47 | @field_types.keys 48 | end 49 | 50 | def method_names 51 | @method_types.keys 52 | end 53 | 54 | def has_method?(name) 55 | @method_types.has_key?(name) 56 | end 57 | 58 | def any? 59 | yield self 60 | end 61 | 62 | # Return +true+ if +self+ is a subtype of +other+. This follows the 63 | # usual semantics of BottomType and TopType; if +other+ is another 64 | # StructralType, all of the following must be true: 65 | # 1. +self+ has at least all the fields and methods in +other+. 66 | # 2. Corresponding field types in +self+ and +other+ must be the same 67 | # type. 68 | # 3. The method types in +self+ and +other+ are covariant (the types in 69 | # +self+ are subtypes of the types with the same key in +other+). 70 | def <=(other) 71 | case other 72 | when TupleType 73 | false 74 | when StructuralType 75 | other.method_names.each do |m_name| 76 | return false unless method_names.include?(m_name) 77 | mine = get_method(m_name) 78 | theirs = other.get_method(m_name) 79 | return false unless mine <= theirs 80 | end 81 | return true 82 | else 83 | super(other) 84 | end 85 | end 86 | 87 | def to_s # :nodoc: 88 | fields = map_to_string(@field_types) 89 | methods = map_to_string(@method_types) 90 | "{ #{fields} | #{methods} }" 91 | end 92 | 93 | def eql?(other) # :nodoc: 94 | return false unless other.instance_of?(StructuralType) 95 | 96 | return false unless type_map_eql?(@field_types, other.field_types) 97 | return false unless type_map_eql?(@method_types, other.method_types) 98 | true 99 | end 100 | 101 | def hash # :nodoc: 102 | hash = type_map_hash(@field_types) 103 | hash += 23 * type_map_hash(@method_types) 104 | end 105 | 106 | # can be overriden in subclasses 107 | def get_method(name) 108 | return @method_types[name] 109 | end 110 | 111 | # can be overriden in subclasses 112 | def get_field(name) 113 | return @field_types[name] 114 | end 115 | 116 | protected 117 | 118 | def can_subtype?(other) 119 | (other.instance_of?(StructuralType) or 120 | other.instance_of?(NominalType)) 121 | end 122 | 123 | 124 | private 125 | 126 | # Compute a hash code for a symbol-to-type map. 127 | def type_map_hash(map) 128 | builder = Rtc::HashBuilder.new(19, 313) 129 | map.each_pair do |sym, type| 130 | builder.include(sym) 131 | builder.include(type) 132 | end 133 | builder.get_hash 134 | end 135 | 136 | # Return +true+ if +left+ and +right+ have the same set of symbols and 137 | # have equal types for corresponding symbols. 138 | def type_map_eql?(left, right) 139 | return false unless left.size == right.size 140 | left.each_pair do |sym, left_type| 141 | return false unless right.has_key?(sym) 142 | right_type = right[sym] 143 | return false unless right_type == left_type 144 | end 145 | end 146 | 147 | # Return a string representation of a symbol-to-type map. 148 | def map_to_string(type_map) 149 | if type_map.empty? 150 | return "" 151 | end 152 | pairs = type_map.each_pair.map do |symbol, type| 153 | "#{symbol}: #{type}" 154 | end 155 | pairs.join(", ") 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/symbol.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | require_relative './terminal' 3 | 4 | module Rtc::Types 5 | class SymbolType < Type 6 | include TerminalType 7 | attr_reader :symbol 8 | def initialize(sym) 9 | @symbol = sym 10 | super() 11 | end 12 | 13 | def has_method?(method) 14 | Symbol.method_defined?(method) 15 | end 16 | 17 | def eql?(other) 18 | other.instance_of?(SymbolType) and other.symbol == symbol 19 | end 20 | 21 | def map 22 | return self 23 | end 24 | 25 | def ==(other) 26 | eql?(other) 27 | end 28 | 29 | def hash 30 | symbol.to_s.hash 31 | end 32 | 33 | def each 34 | yield self 35 | end 36 | 37 | def to_s 38 | ":#{@symbol}" 39 | end 40 | 41 | def <=(other) 42 | if other.instance_of?(SymbolType) 43 | return eql?(other) 44 | elsif other.instance_of?(NominalType) and other.klass == Symbol 45 | return true 46 | else 47 | super 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/terminal.rb: -------------------------------------------------------------------------------- 1 | module Rtc::Types 2 | module TerminalType 3 | def replace_parameters(_t_vars) 4 | self 5 | end 6 | 7 | def _to_actual_type 8 | self 9 | end 10 | 11 | def each 12 | yield self 13 | end 14 | 15 | def is_terminal; true; end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/top.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | require_relative './terminal' 3 | 4 | module Rtc::Types 5 | class TopType < Type 6 | include TerminalType 7 | def initialize() 8 | super() 9 | end 10 | 11 | def to_s 12 | "t" 13 | end 14 | 15 | def self.instance 16 | return @@instance || (@@instance = TopType.new) 17 | end 18 | 19 | def eql?(other) 20 | other.instance_of?(TopType) 21 | end 22 | 23 | def map 24 | return self 25 | end 26 | 27 | def each 28 | yield self 29 | end 30 | 31 | def ==(other) 32 | eql?(other) 33 | end 34 | 35 | def hash 36 | 17 37 | end 38 | 39 | def <=(other) 40 | if other.instance_of?(TopType) 41 | true 42 | else 43 | false 44 | end 45 | end 46 | 47 | @@instance = nil 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/touch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plum-umd/rtc/e0a2106bd0d2804246d71ed32cfd98e9da61391d/lib/rtc/typing/types/touch -------------------------------------------------------------------------------- /lib/rtc/typing/types/tuple.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | require 'rtc/tools/hash-builder.rb' 3 | 4 | module Rtc::Types 5 | class TupleType < Type 6 | attr_reader :ordered_params 7 | attr_reader :size 8 | 9 | def initialize(arr) 10 | @ordered_params = arr 11 | @size = arr.size 12 | super() 13 | end 14 | 15 | def map 16 | TupleType.new(ordered_params.map { 17 | |p| 18 | yield p 19 | }) 20 | end 21 | 22 | def is_tuple 23 | true 24 | end 25 | 26 | def to_s 27 | "Tuple<[#{ordered_params.join(", ")}]>" 28 | end 29 | 30 | def inspect 31 | "#{self.class.name}(#{@id}): #{@ordered_params.inspect}" 32 | end 33 | 34 | def each 35 | @ordered_params.each { 36 | |p| 37 | yield p 38 | } 39 | end 40 | 41 | def <=(other) 42 | case other 43 | when TupleType 44 | return false unless self.size == other.size 45 | 46 | i = 0 47 | 48 | for t in self.ordered_params 49 | return false if not t <= other.ordered_params[i] 50 | i += 1 51 | end 52 | 53 | true 54 | else 55 | super 56 | end 57 | end 58 | 59 | def hash 60 | builder = Rtc::HashBuilder.new(107, 157) 61 | builder.include("tuple") 62 | @ordered_params.each do |p| 63 | builder.include(p) 64 | end 65 | builder.get_hash 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/type.rb: -------------------------------------------------------------------------------- 1 | module Rtc::Types 2 | # Abstract base class for all types. Takes care of assigning unique ids to 3 | # each type instance and maintains sets of constraint edges. This class 4 | # should never be instantiated directly. 5 | class Type 6 | @@next_id = 1 7 | 8 | # The unique id of this Type instance. 9 | attr_reader :id 10 | 11 | # Create a new Type instance. 12 | # 13 | # [+position+] the file & line where this type was instantiated. 14 | def initialize() 15 | @id = @@next_id 16 | @@next_id += 1 17 | end 18 | 19 | # Return true if +self+ is a subtype of +other+. Implemented in 20 | # subclasses. 21 | def <=(other) 22 | case other 23 | when UnionType 24 | if other.has_variables 25 | solution_found = false 26 | for i in other.types 27 | if self <= i 28 | raise Rtc::AmbiguousUnionException, "Ambiguous union detected" if solution_found 29 | solution_found = true 30 | end 31 | end 32 | solution_found 33 | else 34 | other.types.any? do |a| 35 | self <= a 36 | end 37 | end 38 | when IntersectionType 39 | other.types.any? do |a| 40 | self <= a 41 | end 42 | when TypeVariable 43 | return self <= other.get_type if other.instantiated 44 | return false unless other.solving 45 | other.add_constraint(self) 46 | true 47 | when TopType 48 | true 49 | when TupleType 50 | raise Exception, '<= TupleType NOT implemented' 51 | else 52 | false 53 | end 54 | end 55 | 56 | def parameterized? 57 | false 58 | end 59 | 60 | def contains_free_variables? 61 | false 62 | end 63 | 64 | def free_vars 65 | [] 66 | end 67 | 68 | def is_tuple 69 | false 70 | end 71 | 72 | def ==(other) # :nodoc: 73 | eql?(other) 74 | end 75 | 76 | def eql?(other) # :nodoc: 77 | return false unless other.instance_of?(Type) 78 | return @id == other.id 79 | end 80 | 81 | def hash # :nodoc: 82 | @id 83 | end 84 | 85 | def to_s # :nodoc: 86 | "Type #{@id}" 87 | end 88 | 89 | def inspect() 90 | "#{self.class.name}(#{@id}): #{to_s}" 91 | end 92 | 93 | def replace_parameters(type_vars) 94 | map { 95 | |t| 96 | t.replace_parameters(type_vars) 97 | } 98 | end 99 | 100 | def has_variables 101 | self.each { 102 | |t| 103 | return true if t.is_a?(TypeVariable) and t.solving? 104 | t.has_variables unless t.is_terminal 105 | } 106 | false 107 | end 108 | 109 | def is_terminal 110 | false 111 | end 112 | 113 | def each 114 | raise "you must implement this method" 115 | end 116 | 117 | def map 118 | raise "you must implement this method" 119 | end 120 | 121 | def to_actual_type 122 | if not defined?(@actual_type) 123 | @actual_type = _to_actual_type 124 | end 125 | @actual_type 126 | end 127 | 128 | def _to_actual_type 129 | map { 130 | |t| 131 | t.to_actual_type 132 | } 133 | end 134 | 135 | # Used to simplify the type when proxying an object. Namely removing 136 | # NilClass or FalseClass from unions, since nil and false cannot be 137 | # proxied. 138 | def proxy_simplify 139 | self 140 | end 141 | 142 | protected 143 | 144 | # Returns +true+ if +self+ could possibly be a subtype of +other+. It 145 | # need not be the case that +self+ *is* a subtype. This is used as a 146 | # heuristic when propagating constraints into an IntersectionType or out 147 | # of a Uniontype. 148 | # 149 | # By default, this just checks if +other+ is the same type as +self+. 150 | # Subclasses can override for more nuanced checking. 151 | def can_subtype?(other) 152 | other.instance_of?(self.class) 153 | end 154 | 155 | end 156 | 157 | 158 | 159 | 160 | 161 | end # module Rtc::Types 162 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/type_parameter.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | 3 | module Rtc::Types 4 | 5 | # Represents a type parameter, for example, in Array, t is a type 6 | # parameter. 7 | class TypeParameter < Type 8 | 9 | # The symbol used to define the type parameter. 10 | attr_accessor :symbol 11 | 12 | # Create a new type parameter with the given symbol. 13 | def initialize(symbol) 14 | @symbol = symbol 15 | super() 16 | end 17 | 18 | def is_terminal 19 | true 20 | end 21 | 22 | def replace_parameters(type_vars) 23 | if type_vars.has_key? symbol 24 | return type_vars[symbol] 25 | end 26 | self 27 | end 28 | 29 | def _to_actual_type 30 | self 31 | end 32 | 33 | def each 34 | yield self 35 | end 36 | 37 | # Return true if self is a subtype of other. 38 | #-- 39 | # TODO(rwsims): Refine this as use cases become clearer. 40 | def <=(other) 41 | return other.instance_of?(TopType) 42 | end 43 | 44 | def map 45 | return self 46 | end 47 | 48 | def to_s 49 | "TParam<#{symbol.to_s}>" 50 | end 51 | 52 | def eql?(other) 53 | other.is_a?(TypeParameter) and other.symbol.eql?(@symbol) 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/type_variables.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | 3 | module Rtc::Types 4 | 5 | class TypeVariable < Type 6 | attr_reader :solving 7 | attr_reader :instantiated 8 | attr_reader :name 9 | attr_reader :parent 10 | def initialize(param_name, parent, initial_type = nil) 11 | @instantiated = false 12 | @solving = false 13 | @constraints = initial_type ? [initial_type] : [] 14 | @name = param_name 15 | @parent = parent 16 | super() 17 | end 18 | alias solving? solving 19 | alias instantiated? instantiated 20 | def replace_parameters(_t_vars) 21 | self 22 | end 23 | def has_variables 24 | return @type.has_variables if instantiated? 25 | return false 26 | end 27 | def map 28 | yield self 29 | end 30 | 31 | def each 32 | yield self 33 | end 34 | 35 | def get_type 36 | @type 37 | end 38 | 39 | def _to_actual_type 40 | if @instantiated 41 | @type 42 | elsif @solving 43 | @instantiated = true 44 | @solving = false 45 | @type = BottomType.instance 46 | else 47 | raise "attempt to coerce and unsolved type variable to a real type. this is an error" 48 | end 49 | end 50 | 51 | def add_constraint(type) 52 | @constraints.push(type) 53 | end 54 | 55 | def start_solve 56 | @solving = true 57 | end 58 | 59 | def solve 60 | @instantiated = true 61 | @solving = false 62 | if @constraints.empty? 63 | raise "Error, could not infer the types #{id}" 64 | else 65 | @type = UnionType.of(@constraints) 66 | end 67 | end 68 | 69 | def solvable? 70 | return false if @instatianted 71 | return false unless @solving 72 | not @constraints.empty? 73 | end 74 | 75 | def to_s 76 | if @instantiated 77 | @type.to_s 78 | elsif @solving 79 | "[#{name} = #{@constraints}]" 80 | else 81 | name 82 | end 83 | end 84 | 85 | def is_tuple 86 | return @type.is_tuple if instantiated? 87 | false 88 | end 89 | 90 | def <=(other) 91 | if @instantiated 92 | return @type <= other 93 | end 94 | if self.instance_of?(TypeVariable) and other.instance_of?(TypeVariable) and other.parent.object_id == self.parent.object_id 95 | return true 96 | end 97 | #TODO(jtoman): refine this later, what to do during solving, etc 98 | false 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/union.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | require_relative './type' 3 | 4 | module Rtc::Types 5 | 6 | # A type that is the union of a set of types. Values of a union type may be 7 | # of the type of any type in the set. 8 | class UnionType < Type 9 | # Returns a type representing the union of all the types in +types+. 10 | # Handles some special cases to simplify the resulting type: 11 | # 1. If +a+ and +b+ are both in +types+ and +a+ <= +b+, only +b+ will 12 | # remain in the returned type. 13 | # 2. If after removing supertypes as in 1 there is only one type left in 14 | # the list, it is returned directly. 15 | def self.of(types) 16 | return types[0] if types.size == 1 17 | 18 | t0 = types[0] 19 | types.each{|t| types.delete(t) if t.instance_of?(BottomType)} 20 | pairs = types.product(types) 21 | 22 | pairs.each do |t, u| 23 | # pairs includes [t, t] and [u, u], and since t <= t, skip 24 | # these. 25 | # next if t.eql?(u) 26 | next if t <= u and u <= t 27 | 28 | # pairs includes [t, u] and [u, t], so we don't do symmetric 29 | # checks. 30 | if t <= u 31 | types.delete(t) 32 | end 33 | end 34 | 35 | return t0 if types == [] 36 | return types[0] if types.size == 1 37 | return UnionType.new(types) 38 | end 39 | 40 | def each 41 | types.each { 42 | |t| 43 | yield t 44 | } 45 | end 46 | 47 | def has_method?(method) 48 | @types.all? {|t| t.has_method?(method)} 49 | end 50 | 51 | def map 52 | UnionType.of(types.map { |t| yield t }) 53 | end 54 | 55 | # The set of all types in the union. 56 | attr_accessor :types 57 | 58 | def to_s # :nodoc: 59 | "(#{@types.to_a.join(' or ')})" 60 | end 61 | 62 | def eql?(other) # :nodoc: 63 | return false unless other.instance_of?(UnionType) 64 | return false unless other.types == @types 65 | true 66 | end 67 | def hash # :nodoc: 68 | @types.hash 69 | end 70 | 71 | # Returns +true+ if +self+ subtypes +other+. A UnionType subtypes 72 | # another type if all the values in the union are subtypes of +other+. 73 | #-- 74 | # This has the effect of deferring dealing with unions on the rhs of a 75 | # relation into the Type class. There may be better ways of doing this. 76 | def <=(other) 77 | @types.all? do |t| 78 | t <= other 79 | end 80 | end 81 | 82 | # You can't proxy nil or false, so remove those possibilities 83 | # if they're in the type. 84 | def proxy_simplify 85 | ts = @types.dup.delete_if do |t| 86 | t.is_a? NominalType and 87 | (t.klass == NilClass or t.klass == FalseClass) 88 | end 89 | if ts.length == @types.length 90 | then self 91 | else if ts.length == 1 92 | then ts.to_a[0] 93 | else UnionType.new(ts) 94 | end 95 | end 96 | end 97 | 98 | private 99 | 100 | # Creates a new UnionType that is the union of all the types in +type+. 101 | # The initializer is private since UnionTypes should be created via the 102 | # UnionType#of method. 103 | def initialize(types) 104 | @types = Set.new(types) 105 | super() 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/rtc/typing/types/vararg.rb: -------------------------------------------------------------------------------- 1 | require_relative './type' 2 | 3 | module Rtc::Types 4 | # An object used to represent a variable number of arguments of a single type. 5 | # This doesn't represent a node in the constraint graph; constraints 6 | # should be generated on the +type+ attribute. 7 | class Vararg < Type 8 | attr_accessor :type 9 | 10 | def initialize(type) 11 | @type = type 12 | super() 13 | end 14 | 15 | def to_s 16 | "*(#{type})" 17 | end 18 | 19 | def eql?(other) 20 | other.instanceof?(VarargType) and type.eql?(other.type) 21 | end 22 | 23 | def hash 24 | 31 + type.hash 25 | end 26 | 27 | def each 28 | yield type 29 | end 30 | 31 | def map 32 | Vararg.new(yield type) 33 | end 34 | 35 | def <=(other) 36 | if other.instance_of(Vararg) 37 | type <= other.type 38 | else 39 | super(other) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/rtc/utils.rb: -------------------------------------------------------------------------------- 1 | 2 | module Rtc 3 | 4 | 5 | module HelperMethods 6 | 7 | private 8 | def self.demetaize_name(mod) 9 | if mod.to_s =~ /(.*)Class:((\w|::)+)>+$/ 10 | return "<< #{$~[2]}" 11 | else 12 | return nil 13 | end 14 | end 15 | 16 | public 17 | def self.mod_name(mod) 18 | if mod.respond_to?(:name) 19 | if mod.name == "" 20 | # mod is a meta class 21 | demetaize_name(mod) 22 | else mod.name 23 | end 24 | else 25 | return nil 26 | end 27 | end 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /lib/rtc_lib.rb: -------------------------------------------------------------------------------- 1 | require 'rtc' 2 | 3 | Rtc::MasterSwitch.turn_off 4 | require 'rtc/typing/base_types2.rb' 5 | Rtc::MasterSwitch.turn_on 6 | 7 | # constant set of all noncore classes currently annotated 8 | NONCORE_ANNOTATED_CLASSES = Set.new ["csv"] 9 | NONCORE_ANNOTATIONS_BASE_PATH = File.expand_path File.dirname(__FILE__) + \ 10 | "/rtc/typing/noncore_base_types/base_" 11 | 12 | loaded_classes = Set.new(ObjectSpace.each_object(Class)).collect! { 13 | |c| 14 | c.to_s.downcase 15 | } 16 | 17 | NONCORE_ANNOTATED_CLASSES.intersection(loaded_classes).each { 18 | |c| 19 | load(NONCORE_ANNOTATIONS_BASE_PATH + c + ".rb") 20 | } 21 | 22 | # intercept require and load class's type annotations, if they exist 23 | alias :old_require :require 24 | def require(name) 25 | old_require name 26 | 27 | if NONCORE_ANNOTATED_CLASSES.member?(name) 28 | load(NONCORE_ANNOTATIONS_BASE_PATH + name + ".rb") 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/rtc_profile.rb: -------------------------------------------------------------------------------- 1 | require 'rtc_profiler' 2 | 3 | RubyVM::InstructionSequence.compile_option = { 4 | :trace_instruction => true, 5 | :specialized_instruction => false 6 | } 7 | END { 8 | Rtc_Profiler__::print_profile(STDERR) 9 | } 10 | Rtc_Profiler__::start_profile 11 | -------------------------------------------------------------------------------- /lib/rtc_profiler.rb: -------------------------------------------------------------------------------- 1 | require 'rtc_lib' 2 | 3 | module Rtc_Profiler__ 4 | # internal values 5 | @@start = @@stack = @@map = nil 6 | PROFILE_PROC = proc{|event, file, line, id, binding, klass| 7 | status = Rtc::MasterSwitch.is_on? 8 | Rtc::MasterSwitch.turn_off 9 | case event 10 | when "call", "c-call" 11 | now = Process.times[0] 12 | @@stack.push [now, 0.0] 13 | when "return", "c-return" 14 | now = Process.times[0] 15 | key = [klass, id] 16 | if tick = @@stack.pop 17 | data = (@@map[key] ||= [0, 0.0, 0.0, key]) 18 | data[0] += 1 19 | cost = now - tick[0] 20 | data[1] += cost 21 | data[2] += cost - tick[1] 22 | @@stack[-1][1] += cost if @@stack[-1] 23 | end 24 | end 25 | Rtc::MasterSwitch.turn_on if status 26 | } 27 | module_function 28 | def start_profile 29 | Rtc::MasterSwitch.turn_off 30 | @@start = Process.times[0] 31 | @@stack = [] 32 | @@map = {} 33 | set_trace_func PROFILE_PROC 34 | Rtc::MasterSwitch.turn_on 35 | end 36 | def stop_profile 37 | set_trace_func nil 38 | end 39 | def print_profile(f) 40 | Rtc::MasterSwitch.turn_off 41 | stop_profile 42 | total = Process.times[0] - @@start 43 | if total == 0 then total = 0.01 end 44 | data = @@map.values 45 | data = data.sort_by{|x| -x[2]} 46 | sum = 0 47 | f.printf " %% cumulative self self total\n" 48 | f.printf " time seconds seconds calls ms/call ms/call name\n" 49 | for d in data 50 | sum += d[2] 51 | f.printf "%6.2f %8.2f %8.2f %8d ", d[2]/total*100, sum, d[2], d[0] 52 | f.printf "%8.2f %8.2f %s\n", d[2]*1000/d[0], d[1]*1000/d[0], get_name(*d[3]) 53 | end 54 | f.printf "%6.2f %8.2f %8.2f %8d ", 0.0, total, 0.0, 1 # ??? 55 | f.printf "%8.2f %8.2f %s\n", 0.0, total*1000, "#toplevel" # ??? 56 | end 57 | def get_name(klass, id) 58 | name = klass.to_s || "" 59 | if klass.kind_of? Class 60 | name += "#" 61 | else 62 | name += "." 63 | end 64 | name + id.id2name 65 | end 66 | private :get_name 67 | end 68 | -------------------------------------------------------------------------------- /rtc.gemspec: -------------------------------------------------------------------------------- 1 | # In the top directory 2 | # gem build rtc.gemspec 3 | # gem install rtc-0.0.0.gem 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'rtc' 7 | s.version = '0.0.0' 8 | s.date = '2011-02-16' 9 | s.summary = "Ruby Type Checker" 10 | s.description = "Ruby Type Checker" 11 | s.authors = ["NA"] 12 | s.email = 'NA' 13 | s.files = Dir.glob("*") + Dir.glob("*/*") + Dir.glob("*/*/*") + Dir.glob("*/*/*/*") + Dir.glob("*/*/*/*/*") 14 | s.homepage = 15 | 'https://github.com/jeffrey-s-foster/rtc' 16 | s.add_dependency 'racc' 17 | end 18 | -------------------------------------------------------------------------------- /test/test_lazy_types.rb: -------------------------------------------------------------------------------- 1 | require 'rtc' 2 | require 'test/unit' 3 | module Foo 4 | class MyClass 5 | rtc_annotated 6 | typesig("foo: (Bar) -> Fixnum") 7 | typesig("bar: (Gorp) -> Fixnum") 8 | end 9 | end 10 | 11 | class Bar; end 12 | 13 | class TestLazy < Test::Unit::TestCase 14 | @@bar_ident = { 15 | :type => :absoluet, 16 | :name_list => ["Bar"] 17 | } 18 | def test_lazy() 19 | my_instance = Foo::MyClass.new 20 | assert_nothing_raised do 21 | my_instance.rtc_typeof("foo") 22 | end 23 | 24 | assert_nothing_raised do 25 | my_instance.rtc_typeof("bar").to_s 26 | end 27 | 28 | assert_raise Rtc::ClassNotFoundException do 29 | # forces the lazy class 30 | my_instance.rtc_typeof("foo") <= my_instance.rtc_typeof("bar") 31 | end 32 | end 33 | 34 | def test_safe_methods 35 | my_lazy_type = Rtc::Types::LazyNominalType.new({ 36 | :type => :absolute, 37 | :name_list => ["FizBiz"] 38 | }, Object) 39 | 40 | assert_nothing_raised do 41 | my_lazy_type.inspect 42 | my_lazy_type.to_s 43 | my_lazy_type.eql?(my_lazy_type) 44 | my_lazy_type.hash 45 | my_lazy_type == my_lazy_type 46 | end 47 | end 48 | 49 | def test_lazy_equality 50 | my_lazy_1 = Rtc::Types::LazyNominalType.new(@@bar_ident, Object) 51 | my_lazy_2 = Rtc::Types::LazyNominalType.new(@@bar_ident, Object) 52 | 53 | my_nominal_type = Rtc::Types::NominalType.of(Bar) 54 | 55 | assert(my_lazy_2 == my_lazy_1) 56 | assert(my_lazy_2 != my_nominal_type) 57 | assert_nothing_raised do 58 | assert(my_lazy_2 <= my_lazy_2) 59 | end 60 | 61 | assert(my_lazy_1 != my_lazy_2) 62 | assert(my_lazy_2 == my_nominal_type) 63 | assert_nothing_raised do 64 | assert(my_lazy_1 <= my_lazy_1) 65 | end 66 | 67 | assert(my_lazy_1 == my_lazy_2) 68 | end 69 | 70 | def test_lazy_forwards 71 | my_lazy_1 = Rtc::Types::LazyNominalType.new(@@bar_ident, Object) 72 | assert_nothing_raised do 73 | my_lazy_1.add_method("foo", Rtc::Types::ProceduralType.new(Rtc::Types::NominalType.of(Fixnum), [])) 74 | end 75 | 76 | assert(Rtc::Types::NominalType.of(Bar).get_method("foo") != nil) 77 | 78 | end 79 | 80 | end -------------------------------------------------------------------------------- /test/test_options.rb: -------------------------------------------------------------------------------- 1 | require 'rtc' 2 | require 'test/unit' 3 | 4 | require 'stringio' 5 | class TestOptions < Test::Unit::TestCase 6 | class BadClass 7 | rtc_annotated 8 | typesig("bad_function: () -> String") 9 | def bad_function 10 | 4 11 | end 12 | end 13 | 14 | attr_accessor :bad_class 15 | 16 | def initialize(*) 17 | @bad_class = BadClass.new 18 | super 19 | end 20 | 21 | def test_ignore 22 | Rtc.on_type_error = :ignore 23 | assert_nothing_raised do 24 | bad_class.bad_function 25 | end 26 | Rtc.on_type_error = :exception 27 | end 28 | 29 | def test_callback 30 | flag = false 31 | Rtc.on_type_error = lambda { 32 | |error_message| 33 | flag = true 34 | } 35 | assert_nothing_raised do 36 | bad_class.bad_function 37 | end 38 | assert(flag) 39 | Rtc.on_type_error = :exception 40 | end 41 | 42 | def test_log 43 | Rtc.on_type_error = (io_str = StringIO.new()) 44 | assert_nothing_raised do 45 | bad_class.bad_function 46 | end 47 | assert(io_str.string.size > 0) 48 | Rtc.on_type_error = :exception 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /test/test_proxy_call.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require 'rtc_lib' 3 | 4 | Rtc::MasterSwitch.turn_off 5 | 6 | class B 7 | end 8 | 9 | 10 | class A < B 11 | rtc_annotated 12 | 13 | class << self 14 | alias :old_new :new 15 | end 16 | 17 | def self.new 18 | s = self.old_new 19 | return Rtc::ProxyObject.new(s, s.rtc_type) 20 | end 21 | 22 | typesig("bar: () -> String") 23 | def bar() 24 | "hello" 25 | end 26 | 27 | typesig("baz: (B) -> String") 28 | def baz(x) 29 | x.proxy_type.to_s 30 | end 31 | 32 | typesig("foo_2: (B) -> String") 33 | def foo_2(x) 34 | x.proxy_type.to_s 35 | end 36 | 37 | typesig("foo_1: (A) -> String") 38 | def foo_1(x) 39 | z1 = foo_2(x) 40 | z2 = x.proxy_type.to_s 41 | z1 + z2 42 | end 43 | end 44 | 45 | rtc_typesig("class Array") 46 | class Array 47 | rtc_annotated 48 | 49 | typesig("f3: (Fixnum) -> Fixnum") 50 | def f3(x) 51 | 0 52 | end 53 | 54 | typesig("f2: (Fixnum) -> Fixnum") 55 | def f2(x) 56 | f3(x) 57 | end 58 | 59 | typesig("f1: (Fixnum) -> Fixnum") 60 | def f1(x) 61 | f2(x) 62 | end 63 | 64 | typesig("my_push: (Fixnum or String) -> Array") 65 | def my_push(x) 66 | push(x) 67 | end 68 | end 69 | 70 | class MyClass 71 | rtc_annotated 72 | 73 | typesig("fb: (Fixnum) {(Fixnum) -> String} -> String") 74 | def fb(blk) 75 | x = yield(blk) 76 | x 77 | end 78 | 79 | typesig("fb_wrong: (Fixnum) {(Fixnum) -> Fixnum} -> String") 80 | def fb_wrong(blk) 81 | x = yield(blk) 82 | x 83 | end 84 | 85 | typesig("fbp: (t) {(t) -> t} -> String") 86 | def fbp(blk) 87 | x = yield(blk) 88 | x 89 | end 90 | 91 | typesig("fhi: (Fixnum, (Fixnum) -> String) -> TrueClass", {'unwrap'=>[0]}) 92 | def fhi(x, p) 93 | if p.call(x) == "0" 94 | true 95 | else 96 | true 97 | end 98 | end 99 | 100 | typesig("f1: (Fixnum) -> Fixnum") 101 | typesig("f1: (String) -> String") 102 | typesig("f1: (TrueClass) -> TrueClass") 103 | def f1(x) 104 | x 105 | end 106 | 107 | typesig("no_arg_method: () -> .?") 108 | def no_arg_method() 109 | "boo" 110 | end 111 | 112 | typesig("bad_method: () -> Fixnum") 113 | typesig("bad_method: () -> String") 114 | def bad_method() 115 | end 116 | 117 | typesig("weird_method: (t) -> t") 118 | def weird_method(x) 119 | x 120 | end 121 | 122 | typesig("weird_method2: (.?) -> .?") 123 | def weird_method2(x) 124 | x 125 | end 126 | end 127 | 128 | class Misc 129 | rtc_annotated 130 | 131 | typesig("addNum: (Array) -> Array", {'mutate'=>true}) 132 | def addNum(a) 133 | a << "foo" 134 | a 135 | end 136 | end 137 | 138 | class A1 139 | class N1 140 | end 141 | 142 | class N2 143 | end 144 | end 145 | 146 | class TestProxy < Test::Unit::TestCase 147 | def test_calls1 148 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 149 | 150 | x = [3].rtc_annotate("Array") 151 | n = 0 152 | x.push(n) 153 | 154 | x_str = "{ProxyObject @object: [3, 0], @proxy_type: Array<(Fixnum or String)>}" 155 | 156 | assert_equal(x_str, x.rtc_to_str) 157 | Rtc::MasterSwitch.turn_off 158 | x0 = x[0] 159 | x1 = x[1] 160 | x1_str = "{ProxyObject @object: 0, @proxy_type: (Fixnum or String)}" 161 | Rtc::MasterSwitch.turn_on 162 | 163 | assert_equal(3, x0) 164 | assert_equal(x1_str, x1.rtc_to_str) 165 | 166 | x.push("s") 167 | x_str = "{ProxyObject @object: [3, 0, \"s\"], @proxy_type: Array<(Fixnum or String)>}" 168 | assert_equal(x_str, x.rtc_to_str) 169 | 170 | assert_raise Rtc::TypeMismatchException do 171 | x.push(true) 172 | end 173 | 174 | x = [3].rtc_annotate("Array") 175 | n = 0.rtc_annotate("Fixnum or String") 176 | y = x.push(n) 177 | 178 | Rtc::MasterSwitch.turn_off 179 | x0 = x[0] 180 | x1 = x[1] 181 | x1_str = "{ProxyObject @object: 0, @proxy_type: (Fixnum or String)}" 182 | 183 | Rtc::MasterSwitch.turn_on 184 | assert_equal(3, x0) 185 | assert_equal(x1_str, x1.rtc_to_str) 186 | 187 | assert_raise Rtc::TypeMismatchException do 188 | x.push("s") 189 | end 190 | 191 | x = [3] 192 | n = 0.rtc_annotate("Fixnum") 193 | y = x.push(n) 194 | 195 | Rtc::MasterSwitch.turn_off 196 | x1 = x[1] 197 | Rtc::MasterSwitch.turn_on 198 | x1_str = "{ProxyObject @object: 0, @proxy_type: (Fixnum)}" 199 | assert_equal(x1_str, x1.rtc_to_str) 200 | 201 | r = [1,2].push(3) 202 | r_str = "{ProxyObject @object: [1, 2, 3], @proxy_type: Array}" 203 | assert_equal(r_str, r.rtc_to_str) 204 | 205 | r2 = r[2] 206 | r2_str = "{ProxyObject @object: 3, @proxy_type: Fixnum}" 207 | assert_equal(r2_str, r2.rtc_to_str) 208 | 209 | r = [1, 2, "s"].rtc_annotate("Array") 210 | r2 = r[2] 211 | r2_str = "{ProxyObject @object: s, @proxy_type: (Fixnum or String)}" 212 | assert_equal(r2_str, r2.rtc_to_str) 213 | end 214 | 215 | def test_weird_methods 216 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 217 | 218 | r = MyClass.new.weird_method(0) 219 | r_to_str = "{ProxyObject @object: 0, @proxy_type: Fixnum}" 220 | assert_equal(r_to_str, r.rtc_to_str) 221 | 222 | r = MyClass.new.weird_method2(0) 223 | r_to_str = "{ProxyObject @object: 0, @proxy_type: t}" 224 | assert_equal(r_to_str, r.rtc_to_str) 225 | 226 | r = MyClass.new.no_arg_method 227 | r_to_str = "{ProxyObject @object: boo, @proxy_type: t}" 228 | assert_equal(r_to_str, r.rtc_to_str) 229 | end 230 | 231 | def test_intersection 232 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 233 | 234 | assert_raise Rtc::TypeMismatchException do 235 | MyClass.new.bad_method 236 | end 237 | 238 | x = MyClass.new.f1("s") 239 | x_str = "{ProxyObject @object: s, @proxy_type: String}" 240 | assert_equal(x_str, x.rtc_to_str) 241 | end 242 | 243 | def test_block 244 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 245 | 246 | x = [100, 200] 247 | 248 | Rtc::MasterSwitch.turn_off 249 | x = Rtc::ProxyObject.new(x, [0,1].rtc_type) 250 | Rtc::MasterSwitch.turn_on 251 | 252 | x = x.map {|i| i + 1} 253 | x_str = "{ProxyObject @object: [101, 201], @proxy_type: Array}" 254 | assert_equal(x_str, x.rtc_to_str) 255 | 256 | x = [100] 257 | y = x.push(200) 258 | x = y.map {|i| i + 1} 259 | x_str = "{ProxyObject @object: [101, 201], @proxy_type: Array}" 260 | assert_equal(x_str, x.rtc_to_str) 261 | 262 | assert_raise Rtc::TypeMismatchException do 263 | MyClass.new.fbp(3) {|x| x.to_s} 264 | end 265 | 266 | x = MyClass.new.fbp("3") {|x| x} 267 | x_str = "{ProxyObject @object: 3, @proxy_type: String}" 268 | assert_equal(x_str, x.rtc_to_str) 269 | 270 | x = MyClass.new.fb(3) {|x| x.to_s} 271 | assert_equal("3", x.object) 272 | 273 | assert_raise Rtc::TypeMismatchException do 274 | MyClass.new.fb_wrong(3) {|x| x.to_s} 275 | end 276 | 277 | x = MyClass.new.fhi(2, Proc.new {|v| v.to_s + "hello"}) 278 | x_str = "{ProxyObject @object: true, @proxy_type: TrueClass}" 279 | assert_equal(x_str, x.rtc_to_str) 280 | 281 | assert_raise Rtc::TypeMismatchException do 282 | MyClass.new.fb("3") {|x| x + 1} 283 | end 284 | end 285 | 286 | def ttest_calls1 287 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 288 | # x = [3] 289 | # n = 0.rtc_annotate("Fixnum") 290 | # y = x.push(n) 291 | 292 | # n = 0.rtc_annotate("Array") 293 | # x = [3] 294 | # x.push(n) 295 | 296 | # n = 0 297 | # x = [3].rtc_annotate("Array") 298 | # x.push(n) 299 | 300 | x = [100,200,300] 301 | y = x.map {|i| i+5} 302 | return 303 | puts "Y = #{y.inspect}" 304 | 305 | end 306 | 307 | def ttest_stuff 308 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 309 | x = [3] 310 | n = 0.rtc_annotate("Fixnum") 311 | 312 | r = x.push(n) 313 | 314 | puts "--------------------------------------------------------------------------------------------------" 315 | 316 | # r = [1, 2] 317 | # r = r.map{|x| x + 1} 318 | # puts "RRRRRRRRR = #{r.inspect}" 319 | # exit 320 | end 321 | 322 | def ttest_annotate 323 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 324 | 325 | x = [0] 326 | y = x.rtc_annotate("Array") 327 | 328 | assert_equal("{ProxyObject @object: [0], @proxy_type: Array}", y.rtc_to_str) 329 | assert_equal("#}}>", x.proxies.inspect) 330 | 331 | y = x.rtc_annotate("Array") 332 | assert_equal("{ProxyObject @object: [0], @proxy_type: Array<(Fixnum or String)>}", y.rtc_to_str) 333 | 334 | x_proxies_str = "#}, {ProxyObject @object: [0], @proxy_type: Array<(Fixnum or String)>}}>" 335 | assert_equal(x_proxies_str, x.proxies.inspect) 336 | 337 | z = y.rtc_annotate("Array") 338 | y_proxies_str = "#}}>" 339 | assert_equal(y_proxies_str, y.proxies.inspect) 340 | 341 | x.push(1) 342 | assert_equal([0, 1], x) 343 | 344 | x2 = x.push(2) 345 | x2_proxies_str = "{ProxyObject @object: [0, 1, 2], @proxy_type: Array<(Fixnum)>}" 346 | assert_equal(x2_proxies_str, x2.rtc_to_str) 347 | 348 | assert_raise Rtc::TypeMismatchException do 349 | x.push("s") 350 | end 351 | 352 | y2 = y.push("s") 353 | y2_proxies_str = "{ProxyObject @object: [0, 1, 2, \"s\", \"s\"], @proxy_type: Array<(Fixnum or String)>}" 354 | assert_equal(y2_proxies_str, y2.rtc_to_str) 355 | end 356 | 357 | def ttest_array_calls 358 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 359 | 360 | x = [0] 361 | n = 1 362 | x.push(n) 363 | assert_equal([0, 1], x) 364 | 365 | np = n.rtc_annotate("Fixnum") 366 | x.push(np) 367 | assert_equal([0, 1, 1], x) 368 | end 369 | 370 | def ttest_misc_class 371 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 372 | 373 | x = [] 374 | y = Misc.new 375 | 376 | assert_raise Rtc::TypeMismatchException do 377 | y.addNum(x) 378 | end 379 | end 380 | 381 | def ttest_simple_calls 382 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 383 | 384 | a = [1] 385 | w = a.rtc_annotate("Array") 386 | n = 2 387 | w.push(n) 388 | w_str = "{ProxyObject @object: [1, 2], @proxy_type: Array<(Fixnum or String)>}" 389 | assert_equal(w_str, w.rtc_to_str) 390 | assert_equal(2, n) 391 | 392 | w = [1].rtc_annotate("Array") 393 | n = 2 394 | w = w.push(n) 395 | w_str = "{ProxyObject @object: [1, 2], @proxy_type: Array<(Fixnum or String)>}" 396 | assert_equal(w_str, w.rtc_to_str) 397 | assert_equal(2, n) 398 | end 399 | end 400 | 401 | -------------------------------------------------------------------------------- /test/test_proxy_call_tmp.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require 'rtc_lib' 3 | 4 | Rtc::MasterSwitch.turn_off 5 | 6 | class B 7 | end 8 | 9 | 10 | class A < B 11 | rtc_annotated 12 | 13 | class << self 14 | alias :old_new :new 15 | end 16 | 17 | def self.new 18 | s = self.old_new 19 | return Rtc::ProxyObject.new(s, s.rtc_type) 20 | end 21 | 22 | typesig("bar: () -> String", false) 23 | def bar() 24 | "hello" 25 | end 26 | 27 | typesig("baz: (B) -> String", false) 28 | def baz(x) 29 | x.proxy_type.to_s 30 | end 31 | 32 | typesig("foo_2: (B) -> String", false) 33 | def foo_2(x) 34 | x.proxy_type.to_s 35 | end 36 | 37 | typesig("foo_1: (A) -> String", false) 38 | def foo_1(x) 39 | z1 = foo_2(x) 40 | z2 = x.proxy_type.to_s 41 | z1 + z2 42 | end 43 | end 44 | 45 | rtc_typesig("class Array") 46 | class Array 47 | rtc_annotated 48 | 49 | typesig("my_push: (Fixnum or String) -> Array", true) 50 | def my_push(x) 51 | push(x) 52 | end 53 | end 54 | 55 | class Misc 56 | rtc_annotated 57 | 58 | typesig("addNum: (Array) -> Array", true) 59 | def addNum(a) 60 | a << "foo" 61 | a 62 | end 63 | end 64 | 65 | class TestProxy < Test::Unit::TestCase 66 | def test_annotate 67 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 68 | 69 | x = [0] 70 | y = x.rtc_annotate("Array") 71 | 72 | assert_equal("{ProxyObject @object: [0], @proxy_type: Array}", y.rtc_to_str) 73 | assert_equal("#}}>", x.proxies.inspect) 74 | 75 | y = x.rtc_annotate("Array") 76 | assert_equal("{ProxyObject @object: [0], @proxy_type: Array<(Fixnum or String)>}", y.rtc_to_str) 77 | 78 | x_proxies_str = "#}, {ProxyObject @object: [0], @proxy_type: Array<(Fixnum or String)>}}>" 79 | assert_equal(x_proxies_str, x.proxies.inspect) 80 | 81 | z = y.rtc_annotate("Array") 82 | y_proxies_str = "#}}>" 83 | assert_equal(y_proxies_str, y.proxies.inspect) 84 | 85 | x.push(1) 86 | assert_equal([0, 1], x) 87 | 88 | x2 = x.push(2) 89 | x2_proxies_str = "{ProxyObject @object: [0, 1, 2], @proxy_type: Array<(Fixnum)>}" 90 | assert_equal(x2_proxies_str, x2.rtc_to_str) 91 | 92 | assert_raise Rtc::TypeMismatchException do 93 | x.push("s") 94 | end 95 | 96 | y2 = y.push("s") 97 | y2_proxies_str = "{ProxyObject @object: [0, 1, 2, \"s\", \"s\"], @proxy_type: Array<(Fixnum or String)>}" 98 | assert_equal(y2_proxies_str, y2.rtc_to_str) 99 | end 100 | 101 | def test_array_calls 102 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 103 | 104 | x = [0] 105 | n = 1 106 | x.push(n) 107 | assert_equal([0, 1], x) 108 | 109 | np = n.rtc_annotate("Fixnum") 110 | x.push(np) 111 | assert_equal([0, 1, 1], x) 112 | end 113 | 114 | def test_misc_class 115 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 116 | 117 | x = [] 118 | y = Misc.new 119 | 120 | assert_raise Rtc::TypeMismatchException do 121 | y.addNum(x) 122 | end 123 | end 124 | 125 | def ttest_simple_calls 126 | Rtc::MasterSwitch.turn_on if not Rtc::MasterSwitch.is_on? 127 | 128 | a = [1] 129 | w = a.rtc_annotate("Array") 130 | n = 2 131 | w.push(n) 132 | w_str = "{ProxyObject @object: [1, 2], @proxy_type: Array<(Fixnum or String)>}" 133 | assert_equal(w_str, w.rtc_to_str) 134 | assert_equal(2, n) 135 | 136 | w = [1].rtc_annotate("Array") 137 | n = 2 138 | w = w.push(n) 139 | w_str = "{ProxyObject @object: [1, 2], @proxy_type: Array<(Fixnum or String)>}" 140 | assert_equal(w_str, w.rtc_to_str) 141 | assert_equal(2, n) 142 | end 143 | end 144 | 145 | -------------------------------------------------------------------------------- /test/unit/test_autowrap.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rtc_lib' 3 | 4 | class TestAutoWrap < Test::Unit::TestCase 5 | class Wrapped 6 | rtc_annotated 7 | rtc_autowrap 8 | end 9 | 10 | class SubClass < Wrapped; end 11 | class SubClassNoSubtype < Wrapped 12 | no_subtype 13 | end 14 | class NoSubtypeWrapped < SubClassNoSubtype 15 | rtc_autowrap 16 | end 17 | def test_autowrapping 18 | assert(Wrapped.new.is_proxy_object?) 19 | assert(SubClass.new.is_proxy_object?) 20 | assert_equal(SubClass.new.rtc_type,Rtc::Types::NominalType.of(SubClass)) 21 | assert(!SubClassNoSubtype.new.is_proxy_object?) 22 | assert(NoSubtypeWrapped.new.is_proxy_object?) 23 | assert_equal(NoSubtypeWrapped.new.rtc_type,Rtc::Types::NominalType.of(NoSubtypeWrapped)) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/unit/test_blocks.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require 'rtc_lib' 3 | 4 | class TestBlockClass 5 | rtc_annotated 6 | typesig("trailing_return: () { (Fixnum) -> u } -> u") 7 | def trailing_return() 8 | yield 3 9 | end 10 | typesig("block_argument: () { (Fixnum) -> %any } -> %any") 11 | def block_argument() 12 | yield "asdf" 13 | end 14 | 15 | typesig("requires_block: () { (Fixnum) -> Fixnum } -> Fixnum") 16 | def requires_block 17 | yield 2 18 | end 19 | end 20 | 21 | class TestBlocks < Test::Unit::TestCase 22 | def test_block_arguments() 23 | t = TestBlockClass.new.rtc_annotate("TestBlockClass") 24 | assert_raise Rtc::TypeMismatchException do 25 | t.block_argument { 26 | |f| 27 | f 28 | } 29 | end 30 | end 31 | 32 | def test_trailing_return() 33 | t = TestBlockClass.new.rtc_annotate("TestBlockClass") 34 | return_val = nil 35 | assert_nothing_raised do 36 | return_val = t.trailing_return { 37 | |f| 38 | return f.to_s 39 | } 40 | end 41 | assert_equals("a".rtc_type, return_val.rtc_type) 42 | end 43 | 44 | def test_require_block 45 | assert_raise Rtc::TypeMismatchException do 46 | TestBlockClass.new.rtc_annotate("TestBlockClass").requires_block 47 | end 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /test/unit/test_class_annotations.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require 'rtc' 3 | 4 | class MyClass 5 | rtc_annotated 6 | typesig("@@foo: Fixnum") 7 | def self.foo=(a) 8 | @@foo = a 9 | end 10 | def self.foo(a) 11 | @@foo 12 | end 13 | typesig("self.bar: (Fixnum) -> Fixnum") 14 | def self.bar(x) 15 | x + 1 16 | end 17 | end 18 | 19 | class MySubClass < MyClass 20 | rtc_annotated 21 | typesig("self.bar: (String) -> String") 22 | def self.bar(x) 23 | super(Integer x).to_s 24 | end 25 | end 26 | 27 | class TestClassAnnot < Test::Unit::TestCase 28 | def test_typeof 29 | assert_equal(Rtc::Types::NominalType.of(Fixnum), MyClass.rtc_typeof(:@foo)) 30 | meth_typesig = Rtc::Types::ProceduralType.new([], Rtc::Types::NominalType.of(Fixnum),[ 31 | Rtc::Types::NominalType.of(Fixnum)]) 32 | assert_equal(meth_typesig, MyClass.rtc_typeof(:bar)) 33 | assert_equal(Rtc::Types::ProceduralType.new([],"".rtc_type, ["".rtc_type]), MySubClass.rtc_typeof(:bar)) 34 | end 35 | 36 | def test_class_field_type_check 37 | assert_raise Rtc::TypeMismatchException do 38 | MyClass.foo = "bar" 39 | end 40 | assert_nothing_raised do 41 | MyClass.foo = 3 42 | end 43 | end 44 | 45 | def test_subclass_type_check 46 | assert_nothing_raised do 47 | MySubClass.bar("2") 48 | end 49 | 50 | assert_raise Rtc::TypeMismatchException do 51 | MySubClass.bar(2) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/unit/test_frozen.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require 'rtc_lib' 3 | require 'weakref' 4 | class TestFrozen < Test::Unit::TestCase 5 | def test_no_reinit 6 | test_obj = [] 7 | old_type = test_obj.rtc_type 8 | old_meta = test_obj.rtc_meta 9 | #assert_same(old_type, test_obj.rtc_type) 10 | assert_same(old_meta, test_obj.rtc_meta) 11 | end 12 | 13 | def test_no_reinit_pre_freeze 14 | test_obj = [] 15 | old_type = test_obj.rtc_type 16 | old_meta = test_obj.rtc_meta 17 | test_obj.freeze 18 | #assert_same(old_type, test_obj.rtc_type) 19 | assert_same(old_meta, test_obj.rtc_meta) 20 | end 21 | 22 | def test_no_reinit_post_free 23 | test_obj = [] 24 | test_obj.freeze 25 | assert_same(test_obj.rtc_meta, test_obj.rtc_meta) 26 | #assert_same(test_obj.rtc_type, test_obj.rtc_type) 27 | end 28 | 29 | def test_init_mixed 30 | test_obj = [] 31 | old_type = test_obj.rtc_type 32 | test_obj.freeze 33 | old_meta = test_obj.rtc_meta 34 | assert_same(old_meta, test_obj.rtc_meta) 35 | #assert_same(old_type, test_obj.rtc_type) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/unit/test_frozen_prertc.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | 3 | class TestFrozenPreRtc < Test::Unit::TestCase 4 | 5 | def initialize(*) 6 | @a = "MyString" 7 | @a.freeze 8 | require 'rtc_lib' 9 | super 10 | end 11 | def test_prefreeze 12 | old_meta = nil 13 | assert_nothing_raised do 14 | old_meta = @a.rtc_meta 15 | end 16 | assert_equal(old_meta.object_id, @a.rtc_meta.object_id) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/unit/test_hash_types.rb: -------------------------------------------------------------------------------- 1 | require 'rtc_lib' 2 | require "test/unit" 3 | 4 | class HashTester 5 | rtc_annotated 6 | typesig("symbol_hash_arg: ({:a => String})") 7 | def symbol_hash_arg(a) 8 | 9 | end 10 | 11 | typesig("string_hash_arg: ({'a' => String})") 12 | def string_hash_arg(a) 13 | end 14 | 15 | typesig("optional_arg: ({'a' => Fixnum, 'b' => ?String })") 16 | def optional_arg(a) 17 | end 18 | 19 | typesig("poly_hash: ({'a' => t}) -> t") 20 | def poly_hash(a) 21 | 1 22 | end 23 | 24 | typesig("nested_poly_hash: ({'a' => Array}) { (t) -> t } -> t") 25 | def nested_poly_hash(a) 26 | yield a["a"][0] 27 | end 28 | 29 | typesig("bad_proxy: ({'a' => Fixnum}, :method or :set or :bad_key)") 30 | def bad_proxy(hash,behavior) 31 | if behavior == :method 32 | hash.each 33 | elsif behavior == :set 34 | hash["a"] = "foo" 35 | elsif behavior == :bad_key 36 | hash["b"] = "foo" 37 | end 38 | end 39 | 40 | typesig("good_proxy: ({'a' => Fixnum})") 41 | def good_proxy(hash) 42 | hash.has_key?("a") 43 | hash.member?("a") 44 | hash.key?("a") 45 | hash.include?("a") 46 | hash["a"] = 4 47 | end 48 | 49 | typesig("raw_hash_arg: ({'a' => Fixnum, 'b' => Fixnum}, :to_wide or :to_narrow or :to_opt)") 50 | def raw_hash_arg(hash,behavior) 51 | if behavior == :to_wide 52 | # this should throw a type error 53 | wider_proxy_arg(hash) 54 | elsif behavior == :to_narrow 55 | # this should succeed 56 | narrow_proxy_arg(hash) 57 | elsif behavior == :to_opt 58 | # this should succeed (tests the ability of non-optional members to fullfill optional args 59 | optional_proxy_arg(hash) 60 | end 61 | end 62 | 63 | typesig("opt_to_full: ({'a' => Fixnum, 'b' => ?Fixnum, 'c' => Fixnum})") 64 | def opt_to_full(hash) 65 | # this should throw a type error 66 | wider_proxy_arg(hash) 67 | end 68 | 69 | typesig("optional_proxy_arg: ({'a' => Fixnum, 'b' => ?Fixnum })") 70 | def optional_proxy_arg(a); end 71 | 72 | typesig("wider_proxy_arg: ({'a' => Fixnum, 'b' => Fixnum, 'c' => Fixnum})") 73 | def wider_proxy_arg(a); end 74 | typesig("narrow_proxy_arg: ({'a' => Fixnum})") 75 | def narrow_proxy_arg(a); end 76 | end 77 | 78 | class TestHashTypes < Test::Unit::TestCase 79 | attr_reader :test_instance 80 | def initialize(*) 81 | @test_instance = HashTester.new.rtc_annotate("HashTester") 82 | super 83 | end 84 | 85 | def test_optional 86 | assert_nothing_raised do 87 | test_instance.optional_arg({"a" => 1}) 88 | test_instance.optional_arg({"a" => 1, "b" => "foo"}) 89 | end 90 | 91 | assert_raise Rtc::TypeMismatchException do 92 | test_instance.optional_arg({"a" => 1, "b" => 1}) 93 | end 94 | 95 | assert_raise Rtc::TypeMismatchException do 96 | test_instance.optional_arg({"c" => 1}) 97 | end 98 | end 99 | 100 | def test_wider_hash 101 | assert_nothing_raised do 102 | test_instance.symbol_hash_arg({:a => "foo", :b => 2}) 103 | end 104 | end 105 | 106 | def test_string_hash 107 | assert_nothing_raised do 108 | test_instance.string_hash_arg({"a" => "foo"}) 109 | end 110 | 111 | assert_raise Rtc::TypeMismatchException do 112 | test_instance.string_hash_arg({:a => "foo"}) 113 | end 114 | 115 | assert_raise Rtc::TypeMismatchException do 116 | test_instance.string_hash_arg({"a" => 1}) 117 | end 118 | 119 | assert_nothing_raised do 120 | test_instance.string_hash_arg({"a" => "foo", :a => 1}) 121 | end 122 | 123 | end 124 | 125 | def test_symbol_hash 126 | assert_nothing_raised do 127 | test_instance.symbol_hash_arg({:a => "foo"}) 128 | end 129 | 130 | 131 | assert_raise Rtc::TypeMismatchException do 132 | test_instance.symbol_hash_arg({:a => 1}) 133 | end 134 | 135 | assert_raise Rtc::TypeMismatchException do 136 | test_instance.symbol_hash_arg({"a" => "foo"}) 137 | end 138 | end 139 | 140 | def test_polymorphic_hash 141 | assert_raise Rtc::TypeMismatchException do 142 | test_instance.poly_hash({"a" => "foo"}) 143 | end 144 | 145 | assert_nothing_raised do 146 | test_instance.poly_hash({"a" => 1}) 147 | end 148 | 149 | assert_nothing_raised do 150 | test_instance.poly_hash({"a" => 1, "b" => "a"}) 151 | end 152 | 153 | assert_raise Rtc::TypeMismatchException do 154 | test_instance.poly_hash({"a" => ["a"]}) 155 | end 156 | end 157 | 158 | def test_nested_polymorphic_hash 159 | assert_nothing_raised do 160 | test_instance.nested_poly_hash({"a" => [1]}) { 161 | |t| t 162 | } 163 | end 164 | 165 | assert_raise Rtc::TypeMismatchException do 166 | test_instance.nested_poly_hash({"a" => ["a"]}) { 167 | |t| 1 168 | } 169 | end 170 | end 171 | 172 | def test_hash_proxy 173 | assert_raise RuntimeError do 174 | test_instance.bad_proxy({"a" => 1}, :method) 175 | end 176 | assert_raise Rtc::TypeMismatchException do 177 | test_instance.bad_proxy({"a" => 1}, :set) 178 | end 179 | assert_raise RuntimeError do 180 | test_instance.bad_proxy({"a" => 1}, :bad_key) 181 | end 182 | 183 | assert_nothing_raised do 184 | test_instance.good_proxy({"a" => 1}) 185 | end 186 | 187 | test_arg = {"a" => 1, "b" => 2 } 188 | 189 | assert_nothing_raised do 190 | test_instance.raw_hash_arg(test_arg, :to_narrow) 191 | test_instance.raw_hash_arg(test_arg, :to_opt) 192 | end 193 | assert_raise Rtc::TypeMismatchException do 194 | test_instance.raw_hash_arg(test_arg, :to_wide) 195 | end 196 | assert_raise Rtc::TypeMismatchException do 197 | test_instance.raw_hash_arg({"a" => 1, "b" => 2, "c" => 3}, :to_wide) 198 | end 199 | 200 | assert_raise Rtc::TypeMismatchException do 201 | test_instance.opt_to_full({"a" => 1, "b" => 3}) 202 | end 203 | assert_raise Rtc::TypeMismatchException do 204 | test_instance.opt_to_full({"a" => 1, "b" => 2, "c" => 3}) 205 | end 206 | 207 | end 208 | end 209 | -------------------------------------------------------------------------------- /test/unit/test_parser.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require 'rtc_lib' 3 | 4 | module Foo 5 | module Bar 6 | class B; end 7 | class C; end 8 | end 9 | module Baz 10 | class D; end 11 | end 12 | end 13 | 14 | class ParserTestClass 15 | rtc_annotated 16 | typesig("(Fixnum, Fixnum) -> Fixnum") 17 | def my_add(a,b) 18 | a + b 19 | end 20 | 21 | typesig("(Fixnum,Fixnum) -> Fixnum") 22 | typesig("(String, String) -> String") 23 | def my_intersection(a,b) 24 | a + b 25 | end 26 | 27 | typesig("mixed_annotations: (Fixnum) -> Fixnum") 28 | typesig("(String) -> String") 29 | def mixed_annotations(a) 30 | a 31 | end 32 | 33 | typesig("no_return: (Fixnum)") 34 | def no_return(a) 35 | 36 | end 37 | typesig("type %t = :kind or :subscriber") 38 | typesig("simple_type_abbreviation: (%t) -> Fixnum") 39 | def simple_type_abbreviation(a); end 40 | 41 | typesig("nested_type_abbreviation: (Array<%t>) -> Fixnum") 42 | def nested_type_abbreviation(a); end 43 | 44 | typesig("type %f = %t or Fixnum") 45 | typesig("composed_type_abbreviation: (%f) -> Fixnum") 46 | def composed_type_abbreviation(a); end 47 | 48 | typesig("type %t' = :date or :talks") 49 | typesig("unioned_unions: (%t') -> Fixnum") 50 | def unioned_unions(a); end 51 | 52 | #TODO(jtoman): tests for vararg and rest types. That is 53 | #we need to make sure that forms like "*String or *Fixnum" are not allowed 54 | end 55 | 56 | class TestParser < Test::Unit::TestCase 57 | 58 | def test_anon_typesig 59 | my_add_sig = ParserTestClass.new.rtc_typeof("my_add") 60 | assert_equal(my_add_sig.arg_types, [ 61 | Rtc::Types::NominalType.of(Fixnum), Rtc::Types::NominalType.of(Fixnum) 62 | ]) 63 | assert_equal(my_add_sig.return_type, Rtc::Types::NominalType.of(Fixnum)) 64 | assert(ParserTestClass.new.rtc_typeof("my_intersection").instance_of?(Rtc::Types::IntersectionType)) 65 | assert(ParserTestClass.new.rtc_typeof("mixed_annotations").instance_of?(Rtc::Types::IntersectionType)) 66 | end 67 | 68 | def test_name_resolution 69 | my_parser = Rtc::TypeAnnotationParser.new(Foo::Bar::B) 70 | assert_nothing_raised do 71 | my_parser.scan_str("(C) -> Fixnum") 72 | my_parser.scan_str("(Baz::D) -> Fixnum") 73 | my_parser.scan_str("(B) -> Fixnum") 74 | my_parser.scan_str("(Bar::C) -> Bar::B") 75 | end 76 | end 77 | 78 | def test_no_return 79 | assert_equal(ParserTestClass.rtc_instance_typeof("no_return").return_type, Rtc::Types::TopType.instance) 80 | end 81 | 82 | def test_structural_annotations 83 | my_parser = Rtc::TypeAnnotationParser.new(Object) 84 | assert_nothing_raised do 85 | my_parser.scan_str("foo: ([ to_s: () -> String ]) -> String") 86 | my_parser.scan_str("'%': (Array<[to_s : () -> String]>) -> String") 87 | end 88 | end 89 | 90 | def test_type_abbreviations 91 | parser = Rtc::TypeAnnotationParser.new(Object) 92 | simple_msig = ParserTestClass.rtc_instance_typeof("simple_type_abbreviation") 93 | assert(simple_msig.arg_types[0] <= parser.scan_str("##:kind or :subscriber")) 94 | 95 | nested_msig = ParserTestClass.rtc_instance_typeof("nested_type_abbreviation") 96 | assert(nested_msig.arg_types[0] <= parser.scan_str("##Array<:kind or :subscriber>")) 97 | 98 | composed_msig = ParserTestClass.rtc_instance_typeof("composed_type_abbreviation") 99 | assert(composed_msig.arg_types[0] <= parser.scan_str("##:kind or :subscriber or Fixnum")) 100 | 101 | unioned_msig = ParserTestClass.rtc_instance_typeof("unioned_unions") 102 | assert(unioned_msig.arg_types[0] <= parser.scan_str("##:kind or :subscriber or :date or :talks")) 103 | 104 | assert_raise RuntimeError do 105 | ParserTestClass.instance_exec { 106 | typesig("type %t = Fixnum") 107 | } 108 | end 109 | 110 | assert_raise RuntimeError do 111 | ParserTestClass.instance_exec { 112 | typesig("foo: (%undefined)") 113 | } 114 | end 115 | 116 | reserved_symbols = [ "true", "false", "none", "any", "bool" ] 117 | reserved_symbols.each { 118 | |r_sym| 119 | assert_raise Racc::ParseError do 120 | ParserTestClass.instance_exec { 121 | typesig("type %#{r_sym} = FalseClass") 122 | } 123 | end 124 | } 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /test/unit/test_polymorphic_methods.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "rtc_lib" 3 | 4 | class PolymorphicTests 5 | rtc_annotated 6 | typesig("trailing_return: () -> t") 7 | def trailing_return 8 | return 2 9 | end 10 | 11 | typesig("unsolved_arg: (Fixnum or Array) -> Fixnum or Array") 12 | def unsolved_arg(f) 13 | 3 14 | end 15 | 16 | typesig("two_unsolved_args: (u or Array) -> Array or u") 17 | def two_unsolved_args(r) 18 | 4 19 | end 20 | 21 | typesig("identity: (u) -> u") 22 | def identity(i) 23 | i 24 | end 25 | 26 | typesig("bad_identity: (u) -> u") 27 | def bad_identity(arg) 28 | 3 29 | end 30 | 31 | typesig("fixnum_block: (Fixnum) { (Fixnum) -> t } -> Array") 32 | def fixnum_block(limit) 33 | to_return = [] 34 | for i in 0..limit 35 | val = yield i 36 | to_return.push(val) 37 | end 38 | to_return 39 | end 40 | end 41 | 42 | class TestPolymorphicMethod < Test::Unit::TestCase 43 | attr_accessor :instance 44 | def setup 45 | @instance = PolymorphicTests.new.rtc_annotate("PolymorphicTests") 46 | end 47 | 48 | def test_bad_identity 49 | assert_raise Rtc::TypeMismatchException do 50 | instance.bad_identity("foo") 51 | end 52 | end 53 | 54 | def test_identity 55 | to_return = nil 56 | assert_nothing_raised do 57 | to_return = instance.identity("foo") 58 | end 59 | assert_equal(to_return.rtc_type, "asdf".rtc_type) 60 | assert_nothing_raised do 61 | to_return = instance.identity(:foo) 62 | end 63 | assert_equal(to_return.rtc_type, :foo.rtc_type) 64 | end 65 | 66 | def test_unsolved_arg 67 | to_return = nil 68 | assert_nothing_raised do 69 | to_return = instance.unsolved_arg(4) 70 | end 71 | assert_equal(Rtc::Types::UnionType.of([ 72 | 4.rtc_type, 73 | Rtc::Types::ParameterizedType.new(Rtc::Types::NominalType.of(Array), [ Rtc::Types::BottomType.instance ]) 74 | ]),to_return.rtc_type) 75 | assert_nothing_raised do 76 | to_return = instance.unsolved_arg(["1"]) 77 | end 78 | assert_equal(to_return.rtc_type, Rtc::Types::UnionType.of([4.rtc_type,Rtc::Types::ParameterizedType.new(Rtc::Types::NominalType.of(Array), [ "4".rtc_type ])])) 79 | end 80 | 81 | def test_instantiation 82 | instantiated_proc = instance.rtc_instantiate(:fixnum_block, 83 | :t => "Fixnum or String or Float"); 84 | assert_nothing_raised do 85 | my_ret = instantiated_proc.call(5) { 86 | |i| 87 | if i % 3 == 0 88 | 3.1 89 | elsif i % 3 == 1 90 | "foo" 91 | else 92 | 3 93 | end 94 | } 95 | # ensure we don't get type errors due to the return value being 96 | # annotated incorrectly 97 | while my_ret.length > 0 98 | my_ret.pop 99 | end 100 | end 101 | assert_raise Rtc::TypeMismatchException do 102 | instantiated_proc.call(6) { 103 | |i| 104 | if i % 4 == 0 105 | 3.1 106 | elsif i % 4 == 1 107 | "foo" 108 | elsif i % 4 == 2 109 | :bar 110 | else 111 | 1 112 | end 113 | } 114 | end 115 | 116 | assert_raise Rtc::TypeMismatchException do 117 | instance.rtc_instantiate(:identity, :u => "Fixnum")["foo"] 118 | end 119 | 120 | assert_raise RuntimeError do 121 | # instantiate only one arg, this is an error 122 | instance.rtc_instantiate(:two_unsolved_args, :u => "Fixnum") 123 | end 124 | 125 | assert_nothing_raised do 126 | # test case from our paper 127 | a = [1,2,3].rtc_annotate("Array") 128 | m = a.rtc_instantiate(:map, :u=>"Fixnum or String") 129 | r = m.call() { |n| if (n % 2 == 0) then n else n.to_s end } 130 | r.pop 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /test/unit/test_structural_types.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rtc_lib' 3 | 4 | class StructuralClass 5 | rtc_annotated 6 | typesig("join_strings: (Array<[ to_s: () -> String ]>) -> String") 7 | def join_strings(f) 8 | strings = f.map { 9 | |t| 10 | t.to_s 11 | } 12 | strings.join(",") 13 | end 14 | 15 | typesig("wider_type: ([ to_s: () -> String ]) -> String") 16 | def wider_type(arg) 17 | arg.other_method 18 | end 19 | end 20 | 21 | class ProperToS 22 | rtc_annotated 23 | typesig("to_s: () -> String") 24 | def to_s 25 | "asdf" 26 | end 27 | 28 | typesig("other_method: () -> String") 29 | def other_method 30 | "foo" 31 | end 32 | end 33 | 34 | class BadToS 35 | rtc_annotated 36 | typesig("to_s: () -> Fixnum") 37 | def to_s 38 | 1 39 | end 40 | end 41 | 42 | class TestStructuralTypes < Test::Unit::TestCase 43 | class A; end 44 | def test_array_of_structures 45 | test_instance = StructuralClass.new.rtc_annotate("StructuralClass") 46 | assert_nothing_raised do 47 | test_instance.join_strings([ProperToS.new, ProperToS.new]) 48 | end 49 | assert_raise Rtc::TypeMismatchException do 50 | test_instance.join_strings([ProperToS.new, BadToS.new]) 51 | end 52 | assert_raise Rtc::TypeMismatchException do 53 | test_instance.join_strings([ProperToS.new, A.new ]) 54 | end 55 | end 56 | 57 | def test_no_type_widening 58 | test_instance = StructuralClass.new.rtc_annotate("StructuralClass") 59 | assert_raise NoMethodError do 60 | test_instance.wider_type(ProperToS.new) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/unit/test_type_checking.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require 'rtc_lib' 3 | 4 | class TypeCheckingTestClass 5 | rtc_annotated 6 | 7 | typesig("simple_method: (Fixnum) -> Fixnum") 8 | def simple_method(arg) 9 | arg + 1 10 | end 11 | 12 | typesig("bad_return: () -> Fixnum") 13 | def bad_return 14 | "foo" 15 | end 16 | 17 | typesig("bad_return_2: (.?) -> String") 18 | def bad_return_2(my_obj) 19 | return my_obj && true 20 | end 21 | 22 | typesig("subtype_arg: (Integer) -> Integer") 23 | def subtype_checking(arg) 24 | arg + 1 25 | end 26 | 27 | # a method where the formal parameter lists does not match the 28 | # given type annotation 29 | typesig("different_formals: (Fixnum,?String,*Fixnum,Fixnum) -> Fixnum") 30 | def different_formals(*a) 31 | a[0] + a[-1] 32 | end 33 | 34 | typesig("union_arg: (:foo or :bar) -> Fixnum") 35 | def union_arg(b) 36 | 5 37 | end 38 | 39 | typesig("parameterized_arg: (Array<:subscribers or :talks or :subscribed_talks>) -> Fixnum") 40 | def parameterized_arg(b) 41 | 6 42 | end 43 | 44 | typesig("union_return: (Fixnum) -> Fixnum or String") 45 | def union_return(a) 46 | if a < 10 47 | return 0 48 | else 49 | return "hello world!" 50 | end 51 | end 52 | 53 | typesig("intersection_type: (Fixnum, Fixnum) -> Fixnum") 54 | typesig("intersection_type: (String) -> String") 55 | def intersection_type(a,b = nil) 56 | if b.nil? 57 | a + "1" 58 | else 59 | a + b + 5 60 | end 61 | end 62 | end 63 | 64 | class FieldClass 65 | rtc_annotated 66 | typesig('@foo: Fixnum') 67 | attr_accessor :foo 68 | 69 | typesig('@bar: String') 70 | typesig('@bar: Fixnum') 71 | attr_accessor :bar 72 | end 73 | 74 | class TestTypeChecking < Test::Unit::TestCase 75 | 76 | attr_reader :test_instance 77 | 78 | def initialize(*) 79 | @test_instance = TypeCheckingTestClass.new.rtc_annotate("TypeCheckingTestClass") 80 | super 81 | end 82 | 83 | def check_failure(method,arg_vectors) 84 | arg_vectors.each { 85 | |arg_vector| 86 | assert_raise Rtc::TypeMismatchException do 87 | method.call(*arg_vector) 88 | end 89 | } 90 | end 91 | 92 | def setup 93 | assert(Rtc::MasterSwitch.is_on?) 94 | end 95 | 96 | def test_simple 97 | assert_nothing_raised do 98 | test_instance.simple_method(5) 99 | end 100 | assert_raise Rtc::TypeMismatchException do 101 | test_instance.simple_method("foo") 102 | end 103 | end 104 | 105 | def test_bad_return 106 | assert_raise Rtc::TypeMismatchException do 107 | test_instance.bad_return 108 | end 109 | 110 | assert_raise Rtc::TypeMismatchException do 111 | test_instance.bad_return_2(3) 112 | end 113 | end 114 | 115 | def test_union_return() 116 | assert_nothing_raised do 117 | test_instance.union_return(1) 118 | test_instance.union_return(11) 119 | end 120 | end 121 | 122 | def test_union_args 123 | assert_nothing_raised do 124 | test_instance.union_arg(:foo) 125 | test_instance.union_arg(:bar) 126 | end 127 | end 128 | 129 | def test_subtype_checking 130 | assert_nothing_raised do 131 | test_instance.subtype_checking(4) 132 | end 133 | end 134 | 135 | def test_different_formals 136 | assert_nothing_raised do 137 | test_instance.different_formals(1,5) 138 | end 139 | end 140 | 141 | def test_complex_parameters 142 | assert_nothing_raised do 143 | test_instance.different_formals(1,"foo",5) 144 | test_instance.different_formals(1,4) 145 | test_instance.different_formals(1,"foo",4,5,6,7,8) 146 | end 147 | 148 | [ 149 | [1,2,5], 150 | [1,"foo","bar",5], 151 | [1,"foo",4,"bar",5] 152 | ].each { 153 | |arg_vector| 154 | assert_raise Rtc::TypeMismatchException do 155 | test_instance.different_formals(*arg_vector) 156 | end 157 | } 158 | end 159 | 160 | def test_parameterized_arg 161 | $RTC_STRICT = true 162 | assert_nothing_raised do 163 | test_instance.parameterized_arg([:subscribers, :talks]) 164 | test_instance.parameterized_arg([:subscribers]) 165 | test_instance.parameterized_arg([:talks]) 166 | test_instance.parameterized_arg([:subscribed_talks]) 167 | end 168 | 169 | [ 170 | [:tals], 171 | [:talks,:foorbar] 172 | ].each { 173 | |arg_vector| 174 | assert_raise Rtc::TypeMismatchException do 175 | test_instance.parameterized_arg(*arg_vector) 176 | end 177 | } 178 | $RTC_STRICT = false 179 | end 180 | 181 | def test_intersection_type 182 | assert_nothing_raised do 183 | test_instance.intersection_type(4,4) 184 | test_instance.intersection_type("foo") 185 | end 186 | [ 187 | [4], 188 | ["foo","bar"], 189 | [4,"boo"], 190 | ].each { 191 | |arg_vector| 192 | assert_raise Rtc::TypeMismatchException do 193 | test_instance.intersection_type(*arg_vector) 194 | end 195 | } 196 | end 197 | def test_field_checking 198 | field_instance = FieldClass.new.rtc_annotate("FieldClass") 199 | assert_nothing_raised do 200 | field_instance.foo = 4 201 | end 202 | [:foo, "4"].each { 203 | |v| 204 | assert_raise Rtc::TypeMismatchException do 205 | field_instance.foo = v 206 | end 207 | } 208 | end 209 | 210 | def test_intersection_field 211 | field_instance = FieldClass.new.rtc_annotate("FieldClass") 212 | assert_nothing_raised do 213 | field_instance.bar = 4 214 | field_instance.bar = "3" 215 | end 216 | 217 | assert_raise Rtc::TypeMismatchException do 218 | field_instance.bar = :foo 219 | end 220 | end 221 | 222 | # also tests for automatic unioning 223 | def test_field_query 224 | field_instance = FieldClass.new 225 | assert_equal(field_instance.rtc_typeof(:@foo),Rtc::Types::NominalType.of(Fixnum)) 226 | assert_equal(field_instance.rtc_typeof(:@bar),Rtc::Types::UnionType.of([ 227 | Rtc::Types::NominalType.of(Fixnum), 228 | Rtc::Types::NominalType.of(String) 229 | ])) 230 | assert_equal(field_instance.rtc_typeof("@foo"),Rtc::Types::NominalType.of(Fixnum)) 231 | assert_equal(field_instance.rtc_typeof("@bar"),Rtc::Types::UnionType.of([ 232 | Rtc::Types::NominalType.of(Fixnum), 233 | Rtc::Types::NominalType.of(String) 234 | ])) 235 | end 236 | 237 | 238 | def test_parameterized_instance_typeof 239 | expected_method_type = Rtc::Types::ProceduralType.new([],Rtc::Types::TypeParameter.new(:t), [ 240 | Rtc::Types::NominalType.of(Fixnum) 241 | ]) 242 | assert_equal(expected_method_type, Array.rtc_instance_typeof("at")) 243 | end 244 | 245 | 246 | def test_instance_typeof 247 | expected_method_type = Rtc::Types::ProceduralType.new([], Rtc::Types::NominalType.of(Fixnum),[ 248 | Rtc::Types::NominalType.of(Fixnum) 249 | ]) 250 | test_instance = TypeCheckingTestClass.new.rtc_annotate("TypeCheckingTestClass") 251 | assert_equal(test_instance.rtc_typeof("simple_method"),expected_method_type) 252 | assert_equal(test_instance.rtc_typeof("simple_method"),TypeCheckingTestClass.rtc_instance_typeof("simple_method")) 253 | assert_equal(FieldClass.rtc_instance_typeof("@foo"),(fixnum_type = Rtc::Types::NominalType.of(Fixnum))) 254 | assert_equal(FieldClass.rtc_instance_typeof(:@foo),fixnum_type) 255 | assert_equal(TypeCheckingTestClass.rtc_instance_typeof(:simple_method),expected_method_type) 256 | assert_equal(TypeCheckingTestClass.rtc_instance_typeof("simple_method"),expected_method_type) 257 | end 258 | 259 | 260 | class SuperClass 261 | rtc_annotated 262 | typesig("foo: (Fixnum) -> String") 263 | typesig("bar: (Fixnum) -> Fixnum") 264 | end 265 | 266 | class ChildClass < SuperClass 267 | rtc_annotated 268 | typesig("bar: (String) -> String") 269 | end 270 | 271 | class NoSubtypeChildClass < SuperClass 272 | rtc_no_subtype 273 | end 274 | 275 | def test_inheritance_typeof 276 | rtc_of = Rtc::Types::NominalType.method(:of) 277 | proc_type = Rtc::Types::ProceduralType 278 | foo_type = proc_type.new([], rtc_of[String], [ 279 | rtc_of[Fixnum] 280 | ]) 281 | child_instance = ChildClass.new 282 | assert_equal(child_instance.rtc_typeof("foo"), foo_type) 283 | assert_equal(ChildClass.rtc_instance_typeof("foo"), foo_type) 284 | 285 | bar_type = proc_type.new([], rtc_of[String],[ 286 | rtc_of[String] 287 | ]) 288 | assert_equal(ChildClass.rtc_instance_typeof("bar"), bar_type) 289 | assert_equal(child_instance.rtc_typeof("bar"), bar_type) 290 | assert_equal("adsf".rtc_typeof("adfadfadfsasdf"), nil) 291 | assert_equal(String.rtc_instance_typeof("adfadfadfadf"), nil) 292 | 293 | assert_equal(NoSubtypeChildClass.rtc_instance_typeof("foo"), nil) 294 | end 295 | 296 | def test_constrained() 297 | my_arr = [1, "foo"] 298 | annotated_arr = nil 299 | assert_nothing_raised do 300 | annotated_arr = my_arr.rtc_annotate("Array") 301 | end 302 | assert_raise Rtc::AnnotateException do 303 | annotated_arr.rtc_annotate("Array") 304 | end 305 | assert_raise Rtc::TypeMismatchException do 306 | annotated_arr.push(4.0) 307 | end 308 | end 309 | 310 | 311 | class ParentSig 312 | rtc_annotated 313 | typesig("foo: (Fixnum) -> Fixnum") 314 | def foo(x) 315 | x + 1 316 | end 317 | typesig("bar: () -> Fixnum") 318 | def bar 319 | 4 320 | end 321 | typesig("@baz_field: Fixnum") 322 | end 323 | 324 | class OverrideSig < ParentSig 325 | rtc_annotated 326 | typesig("foo: (String) -> String") 327 | def foo(x) 328 | super(Integer x).to_s 329 | end 330 | end 331 | 332 | def test_override_typesig 333 | assert_nothing_raised do 334 | OverrideSig.new.rtc_annotate("OverrideSig").foo("2") 335 | end 336 | assert_raise Rtc::TypeMismatchException do 337 | OverrideSig.new.rtc_annotate("OverrideSig").foo(4) 338 | end 339 | 340 | assert_raise Rtc::TypeMismatchException do 341 | ParentSig.new.rtc_annotate("ParentSig").foo("2") 342 | end 343 | 344 | assert_nothing_raised do 345 | ParentSig.new.rtc_annotate("ParentSig").foo(4) 346 | end 347 | end 348 | 349 | def test_typeof_include_super 350 | expected_type = Rtc::Types::ProceduralType.new([], Rtc::Types::NominalType.of(Fixnum), []) 351 | assert_equal(expected_type,OverrideSig.rtc_instance_typeof("bar")) 352 | assert_equal(nil, OverrideSig.rtc_instance_typeof("bar", false)) 353 | assert_equal(Rtc::Types::NominalType.of(Fixnum), OverrideSig.rtc_instance_typeof(:@baz_field)) 354 | assert_equal(nil, OverrideSig.rtc_instance_typeof(:baz_field, false)) 355 | end 356 | end 357 | -------------------------------------------------------------------------------- /test/unit/test_types.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require 'rtc_lib' 3 | 4 | class TestTypeSystem < Test::Unit::TestCase 5 | include Rtc::Types 6 | class A 7 | end 8 | 9 | class B < A 10 | end 11 | 12 | class C 13 | end 14 | 15 | class D 16 | end 17 | def make_union(*r) 18 | Rtc::Types::UnionType.of(r.map { 19 | |klass| 20 | Rtc::Types::NominalType.of(klass) 21 | }) 22 | end 23 | 24 | class ParentClass 25 | def my_method() 26 | puts "I'm a method!" 27 | end 28 | end 29 | 30 | class BreakingClass < ParentClass 31 | rtc_no_subtype 32 | undef_method :my_method 33 | end 34 | 35 | def initialize(*) 36 | [A,B,C,D].each do 37 | |klass| 38 | class_obj = Rtc::Types::NominalType.of(klass) 39 | accessor_name = (klass.name.sub(self.class.to_s + "::", "").downcase + "_class").to_sym 40 | define_singleton_method(accessor_name) { 41 | || 42 | class_obj 43 | } 44 | end 45 | @boolean_type = UnionType.of([NominalType.of(TrueClass), NominalType.of(FalseClass)]) 46 | super 47 | end 48 | 49 | attr_reader :boolean_type 50 | 51 | def test_nominal 52 | assert_equal(false, a_class <= b_class) 53 | assert(b_class <= a_class) 54 | assert_equal(false, c_class <= b_class) 55 | [a_class,b_class,c_class].each { 56 | |klass_obj| 57 | assert(klass_obj <= klass_obj) 58 | } 59 | end 60 | 61 | def test_no_subtype 62 | assert_equal(false, NominalType.of(BreakingClass) <= NominalType.of(ParentClass)) 63 | end 64 | 65 | def test_proc_type 66 | proc_type_a = Rtc::Types::ProceduralType.new([], c_class, [a_class, a_class]) 67 | proc_type_b = Rtc::Types::ProceduralType.new([], c_class, [b_class, b_class]) 68 | 69 | assert(proc_type_a <= proc_type_b) 70 | end 71 | 72 | 73 | def test_union 74 | union_type = UnionType.of([c_class, a_class]) 75 | assert(a_class <= union_type) 76 | 77 | union_type2 = UnionType.of([NominalType.of(D), a_class, c_class]) 78 | union_type3 = UnionType.of([b_class, c_class]) 79 | assert(union_type3 <= union_type2) 80 | end 81 | 82 | def test_rtc_type 83 | my_type = A.new.rtc_type 84 | assert_equal("TestTypeSystem::A", my_type.to_s) 85 | end 86 | def test_dynamic_types 87 | my_array = [] 88 | my_array << 2 89 | assert_equal("Array", my_array.rtc_type.to_s) 90 | my_array << "hi!" 91 | assert_equal("Array<(Fixnum or String)>", my_array.rtc_type.to_s) 92 | my_array.delete_at(0) 93 | assert_equal("Array", my_array.rtc_type.to_s) 94 | 95 | num_str_arr = [ "foo", 2 ] 96 | assert_equal("Array<(String or Fixnum)>", num_str_arr.rtc_type.to_s) 97 | 98 | string_array = [ "bar" ] 99 | assert_equal("Array", string_array.rtc_type.to_s) 100 | 101 | assert(string_array.rtc_type <= num_str_arr.rtc_type) 102 | annotated_str_arr = string_array.rtc_annotate("Array") 103 | assert_equal(false, annotated_str_arr.rtc_type <= num_str_arr.rtc_type) 104 | 105 | assert_equal(false, ["bar", 4, 4.0].rtc_type <= num_str_arr.rtc_type) 106 | end 107 | 108 | def test_symbol 109 | sym1 = SymbolType.new(:a) 110 | sym2 = SymbolType.new(:b) 111 | sym3 = SymbolType.new(:c) 112 | 113 | assert_equal(false, sym1 <= sym2) 114 | assert(sym1 <= SymbolType.new(:a)) 115 | end 116 | 117 | 118 | def test_nested_polytypes 119 | #FIXME(jtoman): these tests rely very much on the ordering of types within type parameters, and should 120 | # be changed from string comparisons 121 | nested_type = [[:foo],[:bar]] 122 | assert_equal( "Array>", nested_type.rtc_type.to_s) 123 | assert_equal("Array)>>", [[:foo,:bar], [[:baz,:gorp]]].rtc_type.to_s) 124 | assert_equal("Array<(:qux or Array<:bar>)>", [:qux, [:bar]].rtc_type.to_s) 125 | end 126 | 127 | def test_nested_polytypes_subtype 128 | a_instance = A.new 129 | single_arr = [a_instance] 130 | double_arr = [[a_instance]] 131 | assert_equal(single_arr.rtc_type <= double_arr.rtc_type, false) 132 | assert_equal(double_arr.rtc_type <= single_arr.rtc_type, false) 133 | end 134 | 135 | 136 | class MyClass 137 | rtc_annotated 138 | typesig("foo: (A, A) -> C") 139 | end 140 | 141 | class StructuralPartA 142 | rtc_annotated 143 | typesig("foo: (C) -> C") 144 | end 145 | 146 | class StructuralPartB < StructuralPartA 147 | rtc_annotated 148 | typesig("bar: (A) -> A") 149 | end 150 | 151 | def test_structural_types 152 | my_class_type = NominalType.of(MyClass) 153 | foo_method_type = ProceduralType.new([], c_class, [a_class, a_class]) 154 | structural_type = StructuralType.new({}, {"foo"=>foo_method_type}) 155 | # test basic subtype rules 156 | assert(my_class_type <= structural_type) 157 | 158 | # depth subtyping 159 | foo_super_method_type = ProceduralType.new([], c_class, [b_class, b_class]) 160 | assert(my_class_type <= StructuralType.new({}, {"foo" => foo_super_method_type})) 161 | 162 | #failure cases, when the structual type is wider 163 | assert_equal(false, my_class_type <= StructuralType.new({}, { 164 | "foo" => foo_method_type, 165 | "bar" => ProceduralType.new(c_class, []) 166 | })) 167 | 168 | assert_equal(false, my_class_type <= StructuralType.new({},{ 169 | "foo" => ProceduralType.new(c_class, []) 170 | })) 171 | end 172 | 173 | def test_structural_respects_inheritance 174 | struct_type = StructuralType.new({},{ 175 | "foo" => ProceduralType.new([], c_class, [c_class]), 176 | "bar" => ProceduralType.new([], a_class, [a_class]) 177 | }) 178 | assert(NominalType.of(StructuralPartB) <= struct_type) 179 | end 180 | 181 | def test_symbol_subtype 182 | assert(:foo.rtc_type <= NominalType.of(Symbol)) 183 | assert(UnionType.of([:foo.rtc_type, :bar.rtc_type, :baz.rtc_type]) <= NominalType.of(Symbol)) 184 | end 185 | end 186 | --------------------------------------------------------------------------------