rbs 0.6.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -126,7 +126,7 @@ module RBS
126
126
 
127
127
  def cache_name(cache, name:, decl:, outer:)
128
128
  if cache.key?(name)
129
- raise DuplicatedDeclarationError.new(name, decl, cache[name])
129
+ raise DuplicatedDeclarationError.new(name, decl, cache[name].decl)
130
130
  end
131
131
 
132
132
  cache[name] = SingleEntry.new(name: name, decl: decl, outer: outer)
@@ -141,6 +141,31 @@ module RBS
141
141
  end
142
142
  end
143
143
 
144
+ class NoSelfTypeFoundError < StandardError
145
+ attr_reader :type_name
146
+ attr_reader :location
147
+
148
+ def initialize(type_name:, location:)
149
+ @type_name = type_name
150
+ @location = location
151
+
152
+ super "#{Location.to_string location}: Could not find self type: #{type_name}"
153
+ end
154
+
155
+ def self.check!(self_type, env:)
156
+ type_name = self_type.name
157
+
158
+ dic = case
159
+ when type_name.class?
160
+ env.class_decls
161
+ when type_name.interface?
162
+ env.interface_decls
163
+ end
164
+
165
+ dic.key?(type_name) or raise new(type_name: type_name, location: self_type.location)
166
+ end
167
+ end
168
+
144
169
  class NoMixinFoundError < StandardError
145
170
  attr_reader :type_name
146
171
  attr_reader :member
@@ -77,6 +77,21 @@ module RBS
77
77
  locations.inject {|l1, l2| l1 + l2 }
78
78
  end
79
79
 
80
+ def concat(*others)
81
+ others.each { |other| self << other }
82
+ self
83
+ end
84
+
85
+ def <<(other)
86
+ if other
87
+ raise "Invalid concat: buffer=#{buffer.name}, other.buffer=#{other.buffer.name}" unless other.buffer == buffer
88
+ @end_pos = other.end_pos
89
+ @source = nil
90
+ @end_loc = nil
91
+ end
92
+ self
93
+ end
94
+
80
95
  def pred?(loc)
81
96
  loc.is_a?(Location) &&
82
97
  loc.name == name &&
@@ -4,9 +4,9 @@ class RBS::Parser
4
4
  tANNOTATION
5
5
  tSTRING tSYMBOL tINTEGER tWRITE_ATTR
6
6
  kLPAREN kRPAREN kLBRACKET kRBRACKET kLBRACE kRBRACE
7
- kVOID kNIL kTRUE kFALSE kANY kUNTYPED kTOP kBOT kSELF kSELFQ kINSTANCE kCLASS kBOOL kSINGLETON kTYPE kDEF kMODULE kSUPER
7
+ kVOID kNIL kTRUE kFALSE kANY kUNTYPED kTOP kBOT kSELF kSELFQ kINSTANCE kCLASS kBOOL kSINGLETON kTYPE kDEF kMODULE
8
8
  kPRIVATE kPUBLIC kALIAS
9
- kCOLON kCOLON2 kCOMMA kBAR kAMP kHAT kARROW kQUESTION kEXCLAMATION kSTAR kSTAR2 kFATARROW kEQ kDOT kLT
9
+ kCOLON kCOLON2 kCOMMA kBAR kAMP kHAT kARROW kQUESTION kEXCLAMATION kSTAR kSTAR2 kFATARROW kEQ kDOT kDOT3 kLT
10
10
  kINTERFACE kEND kINCLUDE kEXTEND kATTRREADER kATTRWRITER kATTRACCESSOR tOPERATOR tQUOTEDMETHOD tQUOTEDIDENT
11
11
  kPREPEND kEXTENSION kINCOMPATIBLE
12
12
  type_TYPE type_SIGNATURE type_METHODTYPE tEOF
@@ -425,7 +425,12 @@ rule
425
425
  comment: leading_comment(val[0].first&.location || location))
426
426
  }
427
427
 
428
- overload: { result = nil } | kOVERLOAD
428
+ overload:
429
+ { result = nil }
430
+ | kOVERLOAD {
431
+ RBS.logger.warn "`overload def` syntax is deprecated. Use `...` syntax instead."
432
+ result = val[0]
433
+ }
429
434
 
430
435
  method_member:
431
436
  annotations attributes overload kDEF method_kind def_name method_types {
@@ -438,15 +443,24 @@ rule
438
443
  type
439
444
  end
440
445
  end
446
+
447
+ last_type = val[6].last
448
+ if last_type.is_a?(LocatedValue) && last_type.value == :dot3
449
+ overload = true
450
+ val[6].pop
451
+ else
452
+ overload = false
453
+ end
454
+
441
455
  result = Members::MethodDefinition.new(
442
456
  name: val[5].value,
443
457
  kind: val[4],
444
- types: types,
458
+ types: val[6],
445
459
  annotations: val[0],
446
460
  location: location,
447
461
  comment: leading_comment(val[0].first&.location || val[1].first&.location || val[2]&.location || val[3].location),
448
462
  attributes: val[1].map(&:value),
449
- overload: !!val[2]
463
+ overload: overload || !!val[2]
450
464
  )
451
465
  }
452
466
 
@@ -463,7 +477,7 @@ rule
463
477
 
464
478
  method_types:
465
479
  method_type { result = [val[0]] }
466
- | kSUPER { result = [LocatedValue.new(value: :super, location: val[0].location)] }
480
+ | kDOT3 { result = [LocatedValue.new(value: :dot3, location: val[0].location)] }
467
481
  | method_type kBAR method_types {
468
482
  result = val[2].unshift(val[0])
469
483
  }
@@ -520,7 +534,7 @@ rule
520
534
 
521
535
  method_name:
522
536
  tOPERATOR
523
- | kAMP | kHAT | kSTAR | kLT | kEXCLAMATION | kSTAR2 | kBAR | kOUT | kIN
537
+ | kAMP | kHAT | kSTAR | kLT | kEXCLAMATION | kSTAR2 | kBAR
524
538
  | method_name0
525
539
  | method_name0 kQUESTION {
526
540
  unless val[0].location.pred?(val[1].location)
@@ -548,7 +562,7 @@ rule
548
562
  kCLASS | kVOID | kNIL | kTRUE | kFALSE | kANY | kUNTYPED | kTOP | kBOT | kINSTANCE | kBOOL | kSINGLETON
549
563
  | kTYPE | kMODULE | kPRIVATE | kPUBLIC | kEND | kINCLUDE | kEXTEND | kPREPEND
550
564
  | kATTRREADER | kATTRACCESSOR | kATTRWRITER | kDEF | kEXTENSION | kSELF | kINCOMPATIBLE
551
- | kUNCHECKED | kINTERFACE | kSUPER | kALIAS | kOUT | kIN | kOVERLOAD
565
+ | kUNCHECKED | kINTERFACE | kALIAS | kOUT | kIN | kOVERLOAD
552
566
 
553
567
  module_type_params:
554
568
  { result = nil }
@@ -1150,15 +1164,13 @@ def leading_comment(location)
1150
1164
  end
1151
1165
 
1152
1166
  def push_comment(string, location)
1153
- new_comment = AST::Comment.new(string: string+"\n", location: location)
1154
-
1155
- if (prev_comment = leading_comment(location)) && prev_comment.location.start_column == location.start_column
1156
- @comments.delete prev_comment.location.end_line
1157
- new_comment = AST::Comment.new(string: prev_comment.string + new_comment.string,
1158
- location: prev_comment.location + new_comment.location)
1167
+ if (comment = leading_comment(location)) && comment.location.start_column == location.start_column
1168
+ comment.concat(string: "#{string}\n", location: location)
1169
+ @comments[comment.location.end_line] = comment
1170
+ else
1171
+ new_comment = AST::Comment.new(string: "#{string}\n", location: location)
1172
+ @comments[new_comment.location.end_line] = new_comment
1159
1173
  end
1160
-
1161
- @comments[new_comment.location.end_line] = new_comment
1162
1174
  end
1163
1175
 
1164
1176
  def new_token(type, value = input.matched)
@@ -1218,7 +1230,6 @@ KEYWORDS = {
1218
1230
  "attr_reader" => :kATTRREADER,
1219
1231
  "attr_writer" => :kATTRWRITER,
1220
1232
  "attr_accessor" => :kATTRACCESSOR,
1221
- "super" => :kSUPER,
1222
1233
  "public" => :kPUBLIC,
1223
1234
  "private" => :kPRIVATE,
1224
1235
  "alias" => :kALIAS,
@@ -1267,6 +1278,7 @@ PUNCTS = {
1267
1278
  "!" => :kEXCLAMATION,
1268
1279
  "**" => :kSTAR2,
1269
1280
  "*" => :kSTAR,
1281
+ "..." => :kDOT3,
1270
1282
  "." => :kDOT,
1271
1283
  "<" => :kLT,
1272
1284
  "-@" => :tOPERATOR,
@@ -134,7 +134,7 @@ module RBS
134
134
  overload: false
135
135
  )
136
136
 
137
- decls.push member
137
+ decls.push member unless decls.include?(member)
138
138
 
139
139
  when :FCALL
140
140
  # Inside method definition cannot reach here.
@@ -11,7 +11,7 @@ module RBS
11
11
  mapping[from] = to
12
12
  end
13
13
 
14
- def self.build(variables, types, instance_type: Types::Bases::Instance.new(location: nil), &block)
14
+ def self.build(variables, types, instance_type: nil, &block)
15
15
  unless variables.size == types.size
16
16
  raise "Broken substitution: variables=#{variables}, types=#{types}"
17
17
  end
@@ -33,7 +33,11 @@ module RBS
33
33
  when Types::Variable
34
34
  mapping[ty.name] || ty
35
35
  when Types::Bases::Instance
36
- instance_type
36
+ if instance_type
37
+ instance_type
38
+ else
39
+ ty
40
+ end
37
41
  else
38
42
  ty
39
43
  end
@@ -5,6 +5,7 @@ require "rbs/test/errors"
5
5
  require "rbs/test/type_check"
6
6
  require "rbs/test/tester"
7
7
  require "rbs/test/hook"
8
+ require "rbs/test/setup_helper"
8
9
 
9
10
  module RBS
10
11
  module Test
@@ -28,7 +28,11 @@ module RBS
28
28
  end
29
29
 
30
30
  def self.inspect_(obj)
31
- Test::INSPECT.bind(obj).call
31
+ if obj.respond_to?(:inspect)
32
+ obj.inspect
33
+ else
34
+ Test::INSPECT.bind(obj).call # For the case inspect is not defined (like BasicObject)
35
+ end
32
36
  end
33
37
 
34
38
  def self.to_string(error)
@@ -7,6 +7,7 @@ module RBS
7
7
  OPERATORS = {
8
8
  :== => "eqeq",
9
9
  :=== => "eqeqeq",
10
+ :!= => "noteq",
10
11
  :+ => "plus",
11
12
  :- => "minus",
12
13
  :* => "star",
@@ -1,24 +1,30 @@
1
1
  require "rbs"
2
2
  require "rbs/test"
3
-
4
3
  require "optparse"
5
4
  require "shellwords"
6
5
 
6
+ include RBS::Test::SetupHelper
7
+
7
8
  logger = Logger.new(STDERR)
8
9
 
9
10
  begin
10
11
  opts = Shellwords.shellsplit(ENV["RBS_TEST_OPT"] || "-I sig")
11
- filter = ENV.fetch("RBS_TEST_TARGET").split(",")
12
- skips = (ENV["RBS_TEST_SKIP"] || "").split(",")
13
- sampling = !ENV.key?("RBS_TEST_NO_SAMPLE")
12
+ filter = ENV.fetch('RBS_TEST_TARGET', "").split(',').map! { |e| e.strip }
13
+ skips = (ENV['RBS_TEST_SKIP'] || '').split(',').map! { |e| e.strip }
14
14
  RBS.logger_level = (ENV["RBS_TEST_LOGLEVEL"] || "info")
15
- rescue
15
+ sample_size = get_sample_size(ENV['RBS_TEST_SAMPLE_SIZE'] || '')
16
+ rescue InvalidSampleSizeError => exception
17
+ RBS.logger.error exception.message
18
+ exit 1
19
+ end
20
+
21
+ if filter.empty?
16
22
  STDERR.puts "rbs/test/setup handles the following environment variables:"
17
23
  STDERR.puts " [REQUIRED] RBS_TEST_TARGET: test target class name, `Foo::Bar,Foo::Baz` for each class or `Foo::*` for all classes under `Foo`"
18
24
  STDERR.puts " [OPTIONAL] RBS_TEST_SKIP: skip testing classes"
19
25
  STDERR.puts " [OPTIONAL] RBS_TEST_OPT: options for signatures (`-r` for libraries or `-I` for signatures)"
20
26
  STDERR.puts " [OPTIONAL] RBS_TEST_LOGLEVEL: one of debug|info|warn|error|fatal (defaults to info)"
21
- STDERR.puts " [OPTIONAL] RBS_TEST_NO_SAMPLE: if set, the type checker tests all the values of a collection"
27
+ STDERR.puts " [OPTIONAL] RBS_TEST_SAMPLE_SIZE: sets the amount of values in a collection to be type-checked (Set to `ALL` to type check all the values)"
22
28
  exit 1
23
29
  end
24
30
 
@@ -32,26 +38,38 @@ env = RBS::Environment.from_loader(loader).resolve_type_names
32
38
 
33
39
  def match(filter, name)
34
40
  if filter.end_with?("*")
35
- name.start_with?(filter[0, filter.size - 1]) || name == filter[0, filter.size-3]
41
+ size = filter.size
42
+ name.start_with?(filter[0, size - 1]) || name == filter[0, size-3]
36
43
  else
37
44
  filter == name
38
45
  end
39
46
  end
40
47
 
41
- factory = RBS::Factory.new()
48
+ def to_absolute_typename(type_name)
49
+ RBS::Factory.new().type_name(type_name).absolute!
50
+ end
51
+
42
52
  tester = RBS::Test::Tester.new(env: env)
43
53
 
54
+ module_name = Module.instance_method(:name)
55
+
44
56
  TracePoint.trace :end do |tp|
45
- class_name = tp.self.name&.yield_self {|name| factory.type_name(name).absolute! }
57
+ class_name = module_name.bind(tp.self).call&.yield_self {|name| to_absolute_typename name }
46
58
 
47
59
  if class_name
48
- if filter.any? {|f| match(f, class_name.to_s) } && skips.none? {|f| match(f, class_name.to_s) }
49
- if tester.checkers.none? {|hook| hook.klass == tp.self }
50
- if env.class_decls.key?(class_name)
51
- logger.info "Setting up hooks for #{class_name}"
52
- tester.install!(tp.self, sampling: sampling)
53
- end
60
+ if filter.any? {|f| match(to_absolute_typename(f).to_s, class_name.to_s) } && skips.none? {|f| match(f, class_name.to_s) }
61
+ if env.class_decls.key?(class_name)
62
+ logger.info "Setting up hooks for #{class_name}"
63
+ tester.install!(tp.self, sample_size: sample_size)
54
64
  end
55
65
  end
56
66
  end
57
67
  end
68
+
69
+ at_exit do
70
+ if $!.nil? || $!.is_a?(SystemExit) && $!.success?
71
+ if tester.targets.empty?
72
+ logger.debug { "No type checker was installed!" }
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,29 @@
1
+ module RBS
2
+ module Test
3
+ module SetupHelper
4
+ class InvalidSampleSizeError < StandardError
5
+ attr_reader :string
6
+
7
+ def initialize(string)
8
+ @string = string
9
+ super("Sample size should be a positive integer: `#{string}`")
10
+ end
11
+ end
12
+
13
+ DEFAULT_SAMPLE_SIZE = 100
14
+
15
+ def get_sample_size(string)
16
+ case string
17
+ when ""
18
+ DEFAULT_SAMPLE_SIZE
19
+ when 'ALL'
20
+ nil
21
+ else
22
+ int_size = string.to_i
23
+ raise InvalidSampleSizeError.new(string) unless int_size.positive?
24
+ int_size
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,11 +2,15 @@ module RBS
2
2
  module Test
3
3
  class Tester
4
4
  attr_reader :env
5
- attr_reader :checkers
5
+ attr_reader :targets
6
+ attr_reader :instance_testers
7
+ attr_reader :singleton_testers
6
8
 
7
9
  def initialize(env:)
8
10
  @env = env
9
- @checkers = []
11
+ @targets = []
12
+ @instance_testers = {}
13
+ @singleton_testers = {}
10
14
  end
11
15
 
12
16
  def factory
@@ -17,34 +21,74 @@ module RBS
17
21
  @builder ||= DefinitionBuilder.new(env: env)
18
22
  end
19
23
 
20
- def install!(klass, sampling:)
24
+ def skip_method?(type_name, method)
25
+ if method.implemented_in == type_name
26
+ if method.annotations.any? {|a| a.string == "rbs:test:skip" }
27
+ :skip
28
+ else
29
+ false
30
+ end
31
+ else
32
+ if method.annotations.any? {|a| a.string == "rbs:test:target" }
33
+ false
34
+ else
35
+ :implemented_in
36
+ end
37
+ end
38
+ end
39
+
40
+ def install!(klass, sample_size:)
21
41
  RBS.logger.info { "Installing runtime type checker in #{klass}..." }
22
42
 
23
43
  type_name = factory.type_name(klass.name).absolute!
24
44
 
25
45
  builder.build_instance(type_name).tap do |definition|
26
46
  instance_key = new_key(type_name, "InstanceChecker")
27
- Observer.register(instance_key, MethodCallTester.new(klass, builder, definition, kind: :instance, sampling: sampling))
47
+ tester, set = instance_testers[klass] ||= [
48
+ MethodCallTester.new(klass, builder, definition, kind: :instance, sample_size: sample_size),
49
+ Set[]
50
+ ]
51
+ Observer.register(instance_key, tester)
28
52
 
29
53
  definition.methods.each do |name, method|
30
- if method.implemented_in == type_name
31
- RBS.logger.info { "Setting up method hook in ##{name}..." }
32
- Hook.hook_instance_method klass, name, key: instance_key
54
+ if reason = skip_method?(type_name, method)
55
+ unless reason == :implemented_in
56
+ RBS.logger.info { "Skipping ##{name} because of `#{reason}`..." }
57
+ end
58
+ else
59
+ if klass.instance_methods(false).include?(name) && !set.include?(name)
60
+ RBS.logger.info { "Setting up method hook in ##{name}..." }
61
+ Hook.hook_instance_method klass, name, key: instance_key
62
+ set << name
63
+ end
33
64
  end
34
65
  end
35
66
  end
36
67
 
37
68
  builder.build_singleton(type_name).tap do |definition|
38
69
  singleton_key = new_key(type_name, "SingletonChecker")
39
- Observer.register(singleton_key, MethodCallTester.new(klass.singleton_class, builder, definition, kind: :singleton, sampling: sampling))
70
+ tester, set = singleton_testers[klass] ||= [
71
+ MethodCallTester.new(klass.singleton_class, builder, definition, kind: :singleton, sample_size: sample_size),
72
+ Set[]
73
+ ]
74
+ Observer.register(singleton_key, tester)
40
75
 
41
76
  definition.methods.each do |name, method|
42
- if method.implemented_in == type_name || name == :new
43
- RBS.logger.info { "Setting up method hook in .#{name}..." }
44
- Hook.hook_singleton_method klass, name, key: singleton_key
77
+ if reason = skip_method?(type_name, method)
78
+ unless reason == :implemented_in
79
+ RBS.logger.info { "Skipping .#{name} because of `#{reason}`..." }
80
+ end
81
+ else
82
+ if klass.methods(false).include?(name) && !set.include?(name)
83
+ RBS.logger.info { "Setting up method hook in .#{name}..." }
84
+ Hook.hook_singleton_method klass, name, key: singleton_key
85
+ set << name
86
+ end
45
87
  end
46
88
  end
47
89
  end
90
+
91
+ targets << klass
48
92
  end
49
93
 
50
94
  def new_key(type_name, prefix)
@@ -66,14 +110,14 @@ module RBS
66
110
  attr_reader :definition
67
111
  attr_reader :builder
68
112
  attr_reader :kind
69
- attr_reader :sampling
113
+ attr_reader :sample_size
70
114
 
71
- def initialize(self_class, builder, definition, kind:, sampling:)
115
+ def initialize(self_class, builder, definition, kind:, sample_size:)
72
116
  @self_class = self_class
73
117
  @definition = definition
74
118
  @builder = builder
75
119
  @kind = kind
76
- @sampling = sampling
120
+ @sample_size = sample_size
77
121
  end
78
122
 
79
123
  def env
@@ -81,7 +125,7 @@ module RBS
81
125
  end
82
126
 
83
127
  def check
84
- @check ||= TypeCheck.new(self_class: self_class, builder: builder, sampling: sampling)
128
+ @check ||= TypeCheck.new(self_class: self_class, builder: builder, sample_size: sample_size)
85
129
  end
86
130
 
87
131
  def format_method_name(name)