Class: TypeProf::Core::MethodDeclBox
- Defined in:
- lib/typeprof/core/graph/box.rb
Instance Attribute Summary collapse
-
#cpath ⇒ Object
readonly
Returns the value of attribute cpath.
-
#method_types ⇒ Object
readonly
Returns the value of attribute method_types.
-
#mid ⇒ Object
readonly
Returns the value of attribute mid.
-
#node ⇒ Object
Returns the value of attribute node.
-
#overloading ⇒ Object
readonly
Returns the value of attribute overloading.
-
#ret ⇒ Object
readonly
Returns the value of attribute ret.
-
#singleton ⇒ Object
readonly
Returns the value of attribute singleton.
Attributes inherited from Box
Instance Method Summary collapse
- #destroy(genv) ⇒ Object
-
#initialize(node, genv, cpath, singleton, mid, method_types, overloading) ⇒ MethodDeclBox
constructor
A new instance of MethodDeclBox.
-
#keyword_arg_typecheck?(genv, changes, keywords_vtx, key, expected_ty, param_map) ⇒ Boolean
Typecheck a single keyword argument value against the expected type by directly inspecting the pre-existing value vertices in the keywords vertex's types (Record, Hash, Instance).
- #match_arguments?(genv, changes, param_map, a_args, method_type) ⇒ Boolean
- #resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, force) ⇒ Object
- #resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk) ⇒ Object
- #resolve_symbol_proc(changes, genv, sym, blk_a_args, rbs_blk, param_map) ⇒ Object
-
#rest_keyword_args_typecheck?(genv, changes, keywords_vtx, method_type, param_map) ⇒ Boolean
Typecheck rest keyword argument values (those not consumed by named keywords) against the method type's rest_keywords type.
- #show ⇒ Object
-
#splat_elements_uninformative?(genv, a_args) ⇒ Boolean
Check if any splatted argument has an Array element vertex that is empty.
- #vertex_uninformative?(genv, vtx, depth = 0) ⇒ Boolean
Methods inherited from Box
#add_symbol_proc_call_box, #destroy_symbol_proc_call_boxes, #on_type_added, #on_type_removed, #reuse, #run, #run0, #to_s
Constructor Details
#initialize(node, genv, cpath, singleton, mid, method_types, overloading) ⇒ MethodDeclBox
Returns a new instance of MethodDeclBox.
263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/typeprof/core/graph/box.rb', line 263 def initialize(node, genv, cpath, singleton, mid, method_types, overloading) super(node) @cpath = cpath @singleton = singleton @mid = mid @method_types = method_types @overloading = overloading @ret = Source.new me = genv.resolve_method(@cpath, @singleton, @mid) me.add_decl(self) me.add_run_all_method_call_boxes(genv) me.add_run_all_mdefs(genv) end |
Instance Attribute Details
#cpath ⇒ Object (readonly)
Returns the value of attribute cpath.
280 281 282 |
# File 'lib/typeprof/core/graph/box.rb', line 280 def cpath @cpath end |
#method_types ⇒ Object (readonly)
Returns the value of attribute method_types.
280 281 282 |
# File 'lib/typeprof/core/graph/box.rb', line 280 def method_types @method_types end |
#mid ⇒ Object (readonly)
Returns the value of attribute mid.
280 281 282 |
# File 'lib/typeprof/core/graph/box.rb', line 280 def mid @mid end |
#node ⇒ Object
Returns the value of attribute node.
278 279 280 |
# File 'lib/typeprof/core/graph/box.rb', line 278 def node @node end |
#overloading ⇒ Object (readonly)
Returns the value of attribute overloading.
280 281 282 |
# File 'lib/typeprof/core/graph/box.rb', line 280 def overloading @overloading end |
#ret ⇒ Object (readonly)
Returns the value of attribute ret.
280 281 282 |
# File 'lib/typeprof/core/graph/box.rb', line 280 def ret @ret end |
#singleton ⇒ Object (readonly)
Returns the value of attribute singleton.
280 281 282 |
# File 'lib/typeprof/core/graph/box.rb', line 280 def singleton @singleton end |
Instance Method Details
#destroy(genv) ⇒ Object
282 283 284 285 286 287 |
# File 'lib/typeprof/core/graph/box.rb', line 282 def destroy(genv) me = genv.resolve_method(@cpath, @singleton, @mid) me.remove_decl(self) me.add_run_all_method_call_boxes(genv) destroy_symbol_proc_call_boxes(genv) end |
#keyword_arg_typecheck?(genv, changes, keywords_vtx, key, expected_ty, param_map) ⇒ Boolean
Typecheck a single keyword argument value against the expected type by directly inspecting the pre-existing value vertices in the keywords vertex's types (Record, Hash, Instance).
542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/typeprof/core/graph/box.rb', line 542 def keyword_arg_typecheck?(genv, changes, keywords_vtx, key, expected_ty, param_map) keywords_vtx.each_type do |kw_ty| val_vtx = case kw_ty when Type::Hash then kw_ty.get_value(key) when Type::Record then kw_ty.get_value(key) when Type::Instance then kw_ty.mod == genv.mod_hash ? kw_ty.args[1] : nil else nil end return false if val_vtx && !expected_ty.typecheck(genv, changes, val_vtx, param_map) end true end |
#match_arguments?(genv, changes, param_map, a_args, method_type) ⇒ Boolean
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 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 |
# File 'lib/typeprof/core/graph/box.rb', line 289 def match_arguments?(genv, changes, param_map, a_args, method_type) # TODO: handle a tuple as a splat argument? if a_args.splat_flags.any? return false unless method_type.rest_positionals method_type.req_positionals.size.times do |i| return false if a_args.splat_flags[i] end method_type.post_positionals.size.times do |i| return false if a_args.splat_flags[-i - 1] end else actual = a_args.positionals.size required_formal = method_type.req_positionals.size + method_type.post_positionals.size if actual < required_formal # too few actual arguments return false end if !method_type.rest_positionals && actual > required_formal + method_type.opt_positionals.size # too many actual arguments return false end end method_type.req_positionals.each_with_index do |ty, i| return false unless ty.typecheck(genv, changes, a_args.positionals[i], param_map) end method_type.post_positionals.each_with_index do |ty, i| i -= method_type.post_positionals.size return false unless ty.typecheck(genv, changes, a_args.positionals[i], param_map) end start_rest = method_type.req_positionals.size end_rest = a_args.positionals.size - method_type.post_positionals.size i = 0 while i < method_type.opt_positionals.size && start_rest < end_rest break if a_args.splat_flags[start_rest] return false unless method_type.opt_positionals[i].typecheck(genv, changes, a_args.positionals[start_rest], param_map) i += 1 start_rest += 1 end if start_rest < end_rest vtxs = a_args.get_rest_args(genv, changes, start_rest, end_rest) while i < method_type.opt_positionals.size ty = method_type.opt_positionals[i] return false if vtxs.any? {|vtx| !ty.typecheck(genv, changes, vtx, param_map) } i += 1 end if method_type.rest_positionals return false if vtxs.any? {|vtx| !method_type.rest_positionals.typecheck(genv, changes, vtx, param_map) } end end # Check keyword arguments by inspecting the keywords vertex types # directly. We avoid get_keyword_arg here because it creates a fresh # Vertex each call, which would destabilize the change-set edges and # cause oscillation when match_arguments? runs on every box re-eval. if a_args.keywords method_type.req_keyword_keys.zip(method_type.req_keyword_values) do |key, ty| return false unless keyword_arg_typecheck?(genv, changes, a_args.keywords, key, ty, param_map) end method_type.opt_keyword_keys.zip(method_type.opt_keyword_values) do |key, ty| return false unless keyword_arg_typecheck?(genv, changes, a_args.keywords, key, ty, param_map) end if method_type.rest_keywords return false unless rest_keyword_args_typecheck?(genv, changes, a_args.keywords, method_type, param_map) end end return true end |
#resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, force) ⇒ Object
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/typeprof/core/graph/box.rb', line 362 def resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, force) param_map0 = param_map.dup if method_type.type_params method_type.type_params.zip(yield(method_type)) do |(var, _default_ty), vtx| param_map0[var] = vtx # TODO: default_ty? end end unless match_arguments?(genv, changes, param_map0, a_args, method_type) if force meth = node.mid_code_range ? :mid_code_range : :code_range changes.add_diagnostic(meth, "wrong type of arguments") # XXX: more friendly and fine-grained error message end return false end rbs_blk = method_type.block if method_type.block_required && !a_args.block if force meth = node.mid_code_range ? :mid_code_range : :code_range changes.add_diagnostic(meth, "block is expected") # XXX: more friendly error message end return false end if !rbs_blk && a_args.block if force meth = node.mid_code_range ? :mid_code_range : :code_range changes.add_diagnostic(meth, "block is not expected") # XXX: more friendly error message end return false end if rbs_blk && a_args.block # rbs_blk_func.optional_keywords, ... blk_a_args = rbs_blk.req_positionals.map do |blk_a_arg| blk_a_arg.covariant_vertex(genv, changes, param_map0) end a_args.block.each_type do |ty| case ty when Type::Proc ty.block.accept_args(genv, changes, blk_a_args) if ty.block.is_a?(Block) ty.block.next_boxes.each do |next_box| unless rbs_blk.return_type.typecheck(genv, changes, next_box.a_ret, param_map0) next_box.wrong_return_type(rbs_blk.return_type.show, changes) end end end when Type::Symbol resolve_symbol_proc(changes, genv, ty.sym, blk_a_args, rbs_blk, param_map0) end end end force = true return true ensure if force ret_vtx = method_type.return_type.covariant_vertex(genv, changes, param_map0) changes.add_edge(genv, ret_vtx, ret) end end |
#resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk) ⇒ Object
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 |
# File 'lib/typeprof/core/graph/box.rb', line 434 def resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk) if @method_types.size == 1 method_type = @method_types.first resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, true, &blk) return end # If any positional argument has no type information, we cannot # determine which overload to select. Return silently (untyped) # rather than attempting to match. This prevents oscillation in # cyclic cases and avoids false "failed to resolve overloads" # diagnostics for untyped arguments. # # We check at two levels: # 1. Top-level empty vertices are always uninformative. # 2. Empty type parameter vertices (e.g., Array[T] where T is # empty) are only uninformative when overloads differ solely # in their type parameters (e.g., Array[Integer] vs # Array[String]). When overloads differ at the top level # (e.g., Integer vs Float), the type parameter contents are # irrelevant for overload selection and should not trigger # bail-out. has_uninformative_args = if @method_types.overloads_differ_in_args? # Check whether overloads also differ at the top level (e.g., # Integer vs Float) or only in their type parameters (e.g., # Array[Integer] vs Array[String]). if @method_types.overloads_differ_at_top_level? # Overloads are distinguished by top-level types. # Only top-level empty vertices matter; empty type parameters # are irrelevant for overload selection. # However, splatted arguments have their elements extracted # during matching, so also check splat element vertices. a_args.positionals.any? {|vtx| vtx.types.empty? } || splat_elements_uninformative?(genv, a_args) || (a_args.keywords && a_args.keywords.types.empty?) else # Overloads differ only in type parameters (e.g., # Array[Integer] vs Array[String]). Empty type parameter # vertices can cause oscillation, so check recursively. a_args.positionals.any? {|vtx| vertex_uninformative?(genv, vtx) } || (a_args.keywords && vertex_uninformative?(genv, a_args.keywords)) end else a_args.positionals.any? {|vtx| vtx.types.empty? } || (a_args.keywords && a_args.keywords.types.empty?) end if has_uninformative_args a_args.positionals.each do |vtx| changes.add_edge(genv, vtx, changes.target) end # Note: keywords already have a permanent edge to the box # (established in MethodCallBox#initialize), so no extra edge needed. return end # A splatted call can match only a rest-positional overload; otherwise prefer # the fixed-arity ones, as a rest-positional one is usually a catch-all overload_groups = a_args.splat_flags.any? ? [@method_types] : @method_types.partition_by_rest_positionals match_any_overload = false overload_groups.each do |method_types| method_types.each do |method_type| if resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, false, &blk) match_any_overload = true end end break if match_any_overload end unless match_any_overload meth = node.mid_code_range ? :mid_code_range : :code_range changes.add_diagnostic(meth, "failed to resolve overloads") end end |
#resolve_symbol_proc(changes, genv, sym, blk_a_args, rbs_blk, param_map) ⇒ Object
427 428 429 430 431 432 |
# File 'lib/typeprof/core/graph/box.rb', line 427 def resolve_symbol_proc(changes, genv, sym, blk_a_args, rbs_blk, param_map) box = add_symbol_proc_call_box(changes, genv, sym, blk_a_args) return unless box rbs_blk.return_type.typecheck(genv, changes, box.ret, param_map) end |
#rest_keyword_args_typecheck?(genv, changes, keywords_vtx, method_type, param_map) ⇒ Boolean
Typecheck rest keyword argument values (those not consumed by named keywords) against the method type's rest_keywords type.
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 |
# File 'lib/typeprof/core/graph/box.rb', line 557 def rest_keyword_args_typecheck?(genv, changes, keywords_vtx, method_type, param_map) named_keys = method_type.req_keyword_keys + method_type.opt_keyword_keys rest_ty = method_type.rest_keywords keywords_vtx.each_type do |kw_ty| case kw_ty when Type::Record kw_ty.fields.each do |key, val_vtx| next if named_keys.include?(key) return false unless rest_ty.typecheck(genv, changes, val_vtx, param_map) end when Type::Hash val_vtx = kw_ty.base_type(genv).args[1] return false if val_vtx && !rest_ty.typecheck(genv, changes, val_vtx, param_map) when Type::Instance if kw_ty.mod == genv.mod_hash && kw_ty.args[1] return false unless rest_ty.typecheck(genv, changes, kw_ty.args[1], param_map) end end end true end |
#show ⇒ Object
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 |
# File 'lib/typeprof/core/graph/box.rb', line 579 def show @method_types.map do |method_type| args = [] method_type.req_positionals.each do |arg| args << arg.show end method_type.opt_positionals.each do |arg| args << "?#{arg.show}" end if method_type.rest_positionals args << "*#{method_type.rest_positionals.show}" end method_type.post_positionals.each do |arg| args << arg.show end method_type.req_keyword_keys.zip(method_type.req_keyword_values) do |key, arg| args << "#{ key }: #{arg.show}" end method_type.opt_keyword_keys.zip(method_type.opt_keyword_values) do |key, arg| args << "?#{ key }: #{arg.show}" end if method_type.rest_keywords args << "**#{method_type.rest_keywords.show}" end s = args.empty? ? "-> " : "(#{ args.join(", ") }) -> " s += method_type.return_type.show end.join(" | ") end |
#splat_elements_uninformative?(genv, a_args) ⇒ Boolean
Check if any splatted argument has an Array element vertex that is empty. Splat expansion extracts elements during overload matching, so empty element types can cause oscillation even when the top-level Array type is present.
513 514 515 516 517 518 519 520 521 522 523 524 |
# File 'lib/typeprof/core/graph/box.rb', line 513 def splat_elements_uninformative?(genv, a_args) a_args.positionals.each_with_index do |vtx, i| next unless a_args.splat_flags[i] vtx.each_type do |ty| base = ty.base_type(genv) if base.is_a?(Type::Instance) && base.mod == genv.mod_ary && base.args[0] return true if base.args[0].types.empty? end end end false end |
#vertex_uninformative?(genv, vtx, depth = 0) ⇒ Boolean
526 527 528 529 530 531 532 533 534 535 536 537 |
# File 'lib/typeprof/core/graph/box.rb', line 526 def vertex_uninformative?(genv, vtx, depth = 0) return true if vtx.types.empty? return false if depth > 3 vtx.each_type do |ty| base = ty.base_type(genv) next unless base.is_a?(Type::Instance) && !base.args.empty? base.args.each do |arg_vtx| return true if arg_vtx && vertex_uninformative?(genv, arg_vtx, depth + 1) end end false end |