Module: RDL::Annotate

Instance Method Summary collapse

Instance Method Details

#attr_accessor_type(*args) ⇒ Object

In the following three methods

+ args +

is a sequence of symbol, typ. attr_reader is called for each symbol,

and var_type is called to assign the immediately following type to the attribute named after that symbol. Note these three methods are duplicated in RDLAnnotate



405
406
407
408
409
410
411
412
413
# File 'lib/rdl/wrap.rb', line 405

def attr_accessor_type(*args)
  args.each_slice(2) { |name, typ|
    attr_accessor name
    var_type ("@" + name.to_s), typ
    type name, "() -> #{typ}"
    type name.to_s + "=", "(#{typ}) -> #{typ}"
  }
  nil
end

#attr_reader_type(*args) ⇒ Object Also known as: attr_type



415
416
417
418
419
420
421
422
# File 'lib/rdl/wrap.rb', line 415

def attr_reader_type(*args)
  args.each_slice(2) { |name, typ|
    attr_reader name
    var_type ("@" + name.to_s), typ
    type name, "() -> #{typ}"
  }
  nil
end

#attr_writer_type(*args) ⇒ Object



426
427
428
429
430
431
432
433
# File 'lib/rdl/wrap.rb', line 426

def attr_writer_type(*args)
  args.each_slice(2) { |name, typ|
    attr_writer name
    var_type ("@" + name.to_s), typ
    type name.to_s + "=", "(#{typ}) -> #{typ}"
  }
  nil
end

#post(*args) ⇒ Object

Add a postcondition to a method. Same possible invocations as pre.



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/rdl/wrap.rb', line 294

def post(*args, wrap: RDL::Config.instance.post_defaults[:wrap], version: nil, &blk)
  return if version && !(Gem::Requirement.new(version).satisfied_by? Gem.ruby_version)
  klass, meth, contract = RDL::Wrap.process_pre_post_args(self, "Postcondition", *args, &blk)
  if meth
    RDL::Globals.info.add(klass, meth, :post, contract)
    if wrap
      if RDL::Util.method_defined?(klass, meth) || meth == :initialize
        RDL::Wrap.wrap(klass, meth)
      else
        RDL::Globals.to_wrap << [klass, meth]
      end
    end
  else
    RDL::Globals.deferred << [klass, :post, contract, {wrap: wrap}]
  end
  nil
end

#pre(*args) ⇒ Object

klass

may be Class, Symbol, or String

method

may be Symbol or String

contract

must be a Contract

wrap

indicates whether the contract should be enforced (true) or just recorded (false)

+ version +

is a rubygems version requirement string (or array of such requirement strings)

if the current Ruby version does not satisfy the version, the type call will be ignored

Add a precondition to a method. Possible invocations: pre(klass, meth, contract) pre(klass, meth) { block } = pre(klass, meth, FlatContract.new { block }) pre(meth, contract) = pre(self, meth, contract) pre(meth) { block } = pre(self, meth, FlatContract.new { block }) pre(contract) = pre(self, next method, contract) pre { block } = pre(self, next method, FlatContract.new { block })



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/rdl/wrap.rb', line 275

def pre(*args, wrap: RDL::Config.instance.pre_defaults[:wrap], version: nil, &blk)
  return if version && !(Gem::Requirement.new(version).satisfied_by? Gem.ruby_version)
  klass, meth, contract = RDL::Wrap.process_pre_post_args(self, "Precondition", *args, &blk)
  if meth
    RDL::Globals.info.add(klass, meth, :pre, contract)
    if wrap
      if RDL::Util.method_defined?(klass, meth) || meth == :initialize # there is always an initialize
        RDL::Wrap.wrap(klass, meth)
      else
        RDL::Globals.to_wrap << [klass, meth]
      end
    end
  else
    RDL::Globals.deferred << [klass, :pre, contract, {wrap: wrap}]
  end
  nil
end

#rdl_alias(*args) ⇒ Object

Aliases contracts for meth_old and meth_new. Currently, this must be called for any aliases or they will not be wrapped with contracts. Only creates aliases in the current class.



438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/rdl/wrap.rb', line 438

def rdl_alias(klass=self, new_name, old_name)
  klass = klass.to_s
  klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
  RDL::Globals.aliases[klass] = {} unless RDL::Globals.aliases[klass]
  if RDL::Globals.aliases[klass][new_name]
    raise RuntimeError,
          "Tried to alias #{new_name}, already aliased to #{RDL::Globals.aliases[klass][new_name]}"
  end
  RDL::Globals.aliases[klass][new_name] = old_name

  if Module.const_defined?(klass) && RDL::Util.to_class(klass).method_defined?(new_name)
    RDL::Wrap.wrap(klass, new_name)
  else
    RDL::Globals.to_wrap << [klass, old_name]
  end
  nil
end

#readd_comp_typesObject



383
384
385
# File 'lib/rdl/wrap.rb', line 383

def readd_comp_types
  RDL::Globals.dep_types.each { |klass, meth, t| RDL::Globals.info.add(klass, meth, :type, t) unless meth.nil? }
end

#type(*args) ⇒ Object

+ klass +

may be Class, Symbol, or String

+ method +

may be Symbol or String

+ type +

may be Type or String

+ wrap +

indicates whether the type should be enforced (true) or just recorded (false)

+ typecheck +

indicates a method that should be statically type checked, as follows

if :call, indicates method should be typechecked when called
if :now, indicates method should be typechecked immediately
if other-symbol, indicates method should be typechecked when rdl_do_typecheck(other-symbol) is called
+ version +

is a rubygems version requirement string (or array of such requirement strings)

if the current Ruby version does not satisfy the version, the type call will be ignored

Set a method’s type. Possible invocations: type(klass, meth, type) type(meth, type) type(type)



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/rdl/wrap.rb', line 327

def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil, effect: nil)
  return if version && !(Gem::Requirement.new(version).satisfied_by? Gem.ruby_version)
  klass, meth, type = begin
                        RDL::Wrap.process_type_args(self, *args)
                      rescue Racc::ParseError => err
                        # Remove enough backtrace to only include actual source line
                        # Warning: Adjust the -5 below if the code (or this comment) changes
                        bt = err.backtrace
                        bt.shift until bt[0] =~ /^#{__FILE__}:#{__LINE__-5}/
                        bt.shift # remove RDL::Globals.contract_switch.off call
                        bt.shift # remove type call itself
                        err.set_backtrace bt
                        raise err
                      end
  effect[0] = :- if effect && effect[0] == :~ ## For now, treating pure-ish :~ as :-, since we realized it doesn't actually affect termination checking.
  typs = type.args + [type.ret]
  if type.block
    block_type = type.block.is_a?(RDL::Type::OptionalType) ? type.block.type : type.block
    typs = typs + block_type.args + [block_type.ret]
  end
  RDL::Globals.dep_types << [klass, meth, type] if typs.any? { |t| t.is_a?(RDL::Type::ComputedType) || (t.is_a?(RDL::Type::BoundArgType) && t.type.is_a?(RDL::Type::ComputedType)) }
  if meth
# It turns out Ruby core/stdlib don't always follow this convention...
#        if (meth.to_s[-1] == "?") && (type.ret != RDL::Globals.types[:bool])
#          warn "#{RDL::Util.pp_klass_method(klass, meth)}: methods that end in ? should have return type %bool"
#        end
    RDL::Globals.info.add(klass, meth, :type, type)
    RDL::Globals.info.add(klass, meth, :effect, effect)
    unless RDL::Globals.info.set(klass, meth, :typecheck, typecheck)
      raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}"
    end
    if wrap || typecheck
      if RDL::Util.method_defined?(klass, meth) || meth == :initialize
        RDL::Globals.info.set(klass, meth, :source_location, RDL::Util.to_class(klass).instance_method(meth).source_location)
        if typecheck == :now
          RDL::Typecheck.typecheck(klass, meth)
        elsif typecheck && (typecheck != :call)
          RDL::Globals.to_typecheck[typecheck] = Set.new unless RDL::Globals.to_typecheck[typecheck]
          RDL::Globals.to_typecheck[typecheck].add([klass, meth])
        end
        RDL::Wrap.wrap(klass, meth) if wrap
      else
        RDL::Globals.to_wrap << [klass, meth] if wrap
        if (typecheck && typecheck != :call)
          RDL::Globals.to_typecheck[typecheck] = Set.new unless RDL::Globals.to_typecheck[typecheck]
          RDL::Globals.to_typecheck[typecheck].add([klass, meth])
        end
      end
    end
  else
    RDL::Globals.deferred << [klass, :type, type, {wrap: wrap,
                                             typecheck: typecheck, effect: effect}]
  end
  nil
end

#type_params(*args) ⇒ Object

+ klass +

is the class whose type parameters to set; self if omitted

params

is an array of symbols or strings that are the

parameters of this (generic) type

variance

is an array of the corresponding variances, :+ for

covariant, :- for contravariant, and :~ for invariant. If omitted, all parameters are assumed to be invariant

all

should be a symbol naming an all? method that behaves like Array#all?, and that accepts

a block that takes arguments in the same order as the type parameters

blk

is for advanced use only. If present, [all] must be

nil. Whenever an instance of this class is instantiated!, the block will be passed an array typs corresponding to the type parameters of the class, and the block should return true if and only if self is a member of self.class<typs>.

Raises:

  • (RuntimeError)


469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/rdl/wrap.rb', line 469

def type_params(klass=self, params, all, variance: nil, &blk)
  raise RuntimeError, "Empty type parameters not allowed" if params.empty?
  klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
  klass = klass.to_s
  if RDL::Globals.type_params[klass]
    raise RuntimeError, "#{klass} already has type parameters #{RDL::Globals.type_params[klass]}"
  end
  params = params.map { |v|
    raise RuntimeError, "Type parameter #{v.inspect} is not symbol or string" unless v.class == String || v.class == Symbol
    v.to_sym
  }
  raise RuntimeError, "Duplicate type parameters not allowed" unless params.uniq.size == params.size
  raise RuntimeError, "Expecting #{params.size} variance annotations, got #{variance.size}" if variance && params.size != variance.size
  raise RuntimeError, "Only :+, +-, and :~ are allowed variance annotations" unless (not variance) || variance.all? { |v| [:+, :-, :~].member? v }
  raise RuntimeError, "Can't pass both all and a block" if all && blk
  raise RuntimeError, "all must be a symbol" unless (not all) || (all.instance_of? Symbol)
  chk = all || blk
  raise RuntimeError, "At least one of {all, blk} required" unless chk
  variance = params.map { |p| :~ } unless variance # default to invariant
  RDL::Globals.type_params[klass] = [params, variance, chk]
  nil
end

#var_type(*args) ⇒ Object

+ klass +

is the class containing the variable; self if omitted; ignored for local and global variables

+ var +

is a symbol or string containing the name of the variable

+ typ +

is a string containing the type

Raises:

  • (RuntimeError)


390
391
392
393
394
395
396
397
398
# File 'lib/rdl/wrap.rb', line 390

def var_type(klass=self, var, typ)
  raise RuntimeError, "Variable cannot begin with capital" if var.to_s =~ /^[A-Z]/
  return if var.to_s =~ /^[a-z]/ # local variables handled specially, inside type checker
  klass = RDL::Util::GLOBAL_NAME if var.to_s =~ /^\$/
  unless RDL::Globals.info.set(klass, var, :type, RDL::Globals.parser.scan_str("#T #{typ}"))
    raise RuntimeError, "Type already declared for #{var}"
  end
  nil
end