use super::*;
use tree_sitter::{Parser, Point};
fn parse(source: &str) -> FileAnalysis {
let mut parser = Parser::new();
parser
.set_language(&ts_parser_perl::LANGUAGE.into())
.unwrap();
let tree = parser.parse(source, None).unwrap();
crate::builder::build(&tree, source.as_bytes())
}
fn point_at(source: &str, needle: &str) -> Point {
let byte = source
.find(needle)
.unwrap_or_else(|| panic!("needle {:?} not in source:\n{}", needle, source));
let row = source[..byte].matches('\n').count();
let col = byte - source[..byte].rfind('\n').map(|i| i + 1).unwrap_or(0);
Point::new(row, col)
}
#[test]
fn coderef_call_arity_zero_resolves_mojo_getter() {
let src = "\
package Foo;
use Mojo::Base -base;
has 'name' => 'default';
package main;
my $foo = Foo->new;
my $cb = \\&Foo::name;
my $x = $cb->($foo);
my $sentinel;
";
let fa = parse(src);
let pt = point_at(src, "my $sentinel");
let ty = fa.inferred_type_via_bag("$x", pt);
assert_eq!(
ty,
Some(InferredType::String),
"coderef call of Mojo getter (arity 0 after dropping receiver) must \
resolve through ReturnExpr's UnionOnArgs::Empty arm to String"
);
}
#[test]
fn coderef_call_arity_one_resolves_mojo_writer() {
let src = "\
package Foo;
use Mojo::Base -base;
has 'name' => 'default';
package main;
my $foo = Foo->new;
my $cb = \\&Foo::name;
my $x = $cb->($foo, \"v\");
my $sentinel;
";
let fa = parse(src);
let pt = point_at(src, "my $sentinel");
let ty = fa.inferred_type_via_bag("$x", pt);
assert_eq!(
ty,
Some(InferredType::ClassName("Foo".into())),
"coderef call of Mojo writer (arity 1 after dropping receiver) must \
resolve through ReturnExpr's UnionOnArgs::Any => Receiver arm"
);
}
#[test]
fn coderef_to_resultset_find_returns_row_class() {
let src = "\
package Schema::Result::Users;
use base 'DBIx::Class::Core';
__PACKAGE__->add_columns(
name => { data_type => 'varchar' },
);
package main;
my $schema;
my $rs = $schema->resultset('Schema::Result::Users');
my $cb = \\&DBIx::Class::ResultSet::find;
my $row = $cb->($rs, 1);
$row->{name};
";
let fa = parse(src);
let pt_row = point_at(src, "$row->{name}");
let ty = fa.inferred_type_via_bag("$row", pt_row);
let parametric = match ty {
Some(InferredType::Parametric(p)) => p,
other => panic!(
"coderef call of `find` with a Parametric receiver must produce \
a Parametric (RowOf<ResultSet>); got {:?}",
other
),
};
assert_eq!(
parametric.class_name(),
Some("Schema::Result::Users"),
"RowOf<ResultSet>'s class_name() evaluates to the row class for \
downstream method dispatch + hash-key access"
);
assert_eq!(
parametric.hash_key_class(),
Some("Schema::Result::Users"),
"RowOf<ResultSet>'s hash_key_class() evaluates to the row class \
(delegates to inner ResultSet's hash_key_class)"
);
}
#[test]
fn anon_closure_arity_dispatch_via_coderef_call() {
let src = "\
package Foo;
sub new { bless {}, shift }
package main;
my $cb = sub {
return \"getter\" unless @_;
return Foo->new;
};
my $g = $cb->();
my $w = $cb->(\"v\");
my $sentinel;
";
let fa = parse(src);
let pt = point_at(src, "my $sentinel");
let g_ty = fa.inferred_type_via_bag("$g", pt);
assert_eq!(
g_ty,
Some(InferredType::String),
"anon-sub called with arity 0 must hit the `unless @_` arm \
(returning a String literal); chain typer threads arity_hint=0 \
through the coderef-call edge"
);
let w_ty = fa.inferred_type_via_bag("$w", pt);
assert_eq!(
w_ty,
Some(InferredType::ClassName("Foo".into())),
"anon-sub called with arity 1 falls through to the default arm \
(returning Foo->new); chain typer threads arity_hint=1"
);
}
#[test]
fn dynamic_method_call_routes_through_coderef_edge() {
let src = "\
package Foo;
sub new { bless {}, shift }
package main;
my $obj = Foo->new;
my $cb = sub { [1, 2] };
my $r = $obj->$cb();
my $sentinel;
";
let fa = parse(src);
let pt = point_at(src, "my $sentinel");
let ty = fa.inferred_type_via_bag("$r", pt);
assert_eq!(
ty,
Some(InferredType::ArrayRef),
"dynamic-method call must chase $cb's CodeRef return_edge — \
the closure returns an arrayref regardless of receiver, so \
the chase resolves through the Symbol's stored return"
);
}