Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Enhance interop of generics, roles, and classes, Raku or nqp #773

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
80 changes: 80 additions & 0 deletions src/how/MonicMachine.nqp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
knowhow MonicMachine is repr('VMArray') {
method new() {
nqp::create(self)
}

method accept($member) {
nqp::push(self, $member);
self
}

method veneer(@members) {
nqp::splice(self, @members, nqp::elems(self), 0)
}

method embody(*@members) {
nqp::splice(self, @members, 0, nqp::elems(self))
}

method emboss(*@members) {
nqp::push(self, nqp::splice(nqp::create(self), @members, 0, 0));
self
}

method summon($evoke) {
if nqp::elems(self) -> $cursor {
repeat { $evoke(self, nqp::shift(self)) } while --$cursor;
}
self
}

method banish($evoke, @keep) {
if nqp::elems(self) -> $cursor {
repeat { $evoke(self, nqp::shift(self)) } while --$cursor;
nqp::splice(@keep, self, nqp::elems(@keep), 0);
nqp::setelems(self, 0);
}
@keep
}

method beckon(@keep) {
my @safe;
my $cursor := 0;
while nqp::elems(self) -> $n {
repeat {
my @members := self[$cursor];
next unless nqp::elems(@members);

my $member := @members[0];
my $i;
repeat {
my @blocks := self[$i];
next if @blocks =:= @members;
next unless my $b := nqp::elems(@blocks);
my $j;
last if @blocks[$j] =:= $member while ++$j < $b;
last if $j < $b;
} while ++$i < $n;
last if $i == $n;
} while ++$cursor < $n;
last if $cursor == $n;

nqp::push(@safe, my $member := self[$cursor][0]);
$cursor := nqp::elems(self);
repeat {
my @members := nqp::pop(self);
next unless nqp::elems(@members);
nqp::shift(@members) if @members[0] =:= $member;
nqp::unshift(self, @members) if nqp::elems(@members);
} while --$cursor;
}
if $cursor && @safe {
nqp::die("Could not build C3 linearization: ambiguous hierarchy");
}
nqp::splice(@keep, @safe, nqp::elems(@keep), 0)
}

method list() {
nqp::splice(nqp::list(), self, 0, 0)
}
}
178 changes: 70 additions & 108 deletions src/how/NQPClassHOW.nqp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ knowhow NQPClassHOW {
has @!mro;

# Full list of roles that we do.
has @!done;
has @!role_typecheck_list;

# If needed, a cached flattened method table accounting for all methods in
# this class and its parents. This is only needed in the sitaution that a
Expand Down Expand Up @@ -91,7 +91,7 @@ knowhow NQPClassHOW {
@!parents := nqp::list();
@!roles := nqp::list();
@!mro := nqp::list();
@!done := nqp::list();
@!role_typecheck_list := nqp::list();
@!BUILDALLPLAN := nqp::list();
@!BUILDPLAN := nqp::list();
$!is_mixin := 0;
Expand Down Expand Up @@ -229,11 +229,12 @@ knowhow NQPClassHOW {
# the composer.
if @!roles {
my @specialized_roles;
for @!roles {
my $ins := $_.HOW.specialize($_, $obj);
for @!roles -> $role {
my $ins := nqp::how_nd($role).specialize($role, $obj);
my @ins_rtl := nqp::how_nd($ins).role_typecheck_list($ins);
nqp::push(@!role_typecheck_list, $ins);
nqp::splice(@!role_typecheck_list, @ins_rtl, nqp::elems(@!role_typecheck_list), 0);
nqp::push(@specialized_roles, $ins);
nqp::push(@!done, $_);
nqp::push(@!done, $ins);
}
RoleToClassApplier.apply($obj, @specialized_roles);
}
Expand Down Expand Up @@ -383,94 +384,9 @@ knowhow NQPClassHOW {
# Computes C3 MRO.
sub compute_c3_mro($class) {
my @immediate_parents := $class.HOW.parents($class, :local);

# Provided we have immediate parents...
my @result;
if nqp::elems(@immediate_parents) {
if nqp::elems(@immediate_parents) == 1 {
@result := compute_c3_mro(@immediate_parents[0]);
} else {
# Build merge list of linearizations of all our parents, add
# immediate parents and merge.
my @merge_list;
for @immediate_parents {
nqp::push(@merge_list, compute_c3_mro($_));
}
nqp::push(@merge_list, @immediate_parents);
@result := c3_merge(@merge_list);
}
}

# Put this class on the start of the list, and we're done.
nqp::unshift(@result, $class);
return @result;
}

# C3 merge routine.
sub c3_merge(@merge_list) {
my @result;
my $accepted;
my $something_accepted := 0;
my $cand_count := 0;

# Try to find something appropriate to add to the MRO.
for @merge_list {
my @cand_list := $_;
if @cand_list {
my $rejected := 0;
my $cand_class := @cand_list[0];
$cand_count := $cand_count + 1;
for @merge_list {
# Skip current list.
unless $_ =:= @cand_list {
# Is current candidate in the tail? If so, reject.
my $cur_pos := 1;
while $cur_pos <= nqp::elems($_) {
if $_[$cur_pos] =:= $cand_class {
$rejected := 1;
}
$cur_pos := $cur_pos + 1;
}
}
}

# If we didn't reject it, this candidate will do.
unless $rejected {
$accepted := $cand_class;
$something_accepted := 1;
last;
}
}
}

# If we never found any candidates, return an empty list.
if $cand_count == 0 {
return @result;
}

# If we didn't find anything to accept, error.
unless $something_accepted {
nqp::die("Could not build C3 linearization: ambiguous hierarchy");
}

# Otherwise, remove what was accepted from the merge lists.
my $i := 0;
while $i < nqp::elems(@merge_list) {
my @new_list;
for @merge_list[$i] {
unless $_ =:= $accepted {
nqp::push(@new_list, $_);
}
}
@merge_list[$i] := @new_list;
$i := $i + 1;
}

# Need to merge what remains of the list, then put what was accepted on
# the start of the list, and we're done.
@result := c3_merge(@merge_list);
nqp::unshift(@result, $accepted);
return @result;
my @hier := MonicMachine.new;
@hier.emboss(|$_.HOW.mro($_)) for @immediate_parents;
@hier.beckon(nqp::list($class))
}

method publish_type_cache($obj) {
Expand All @@ -480,17 +396,16 @@ knowhow NQPClassHOW {
nqp::push(@tc, $_);
if nqp::can($_.HOW, 'role_typecheck_list') {
for $_.HOW.role_typecheck_list($_) -> $role {
my @role_rtl := nqp::how_nd($role).role_typecheck_list($role);
nqp::push(@tc, $role);
if nqp::can($role.HOW, 'role_typecheck_list') {
for $role.HOW.role_typecheck_list($role) {
nqp::push(@tc, $_);
}
}
nqp::splice(@tc, @role_rtl, nqp::elems(@tc), 0);
}
}
}

nqp::settypecache($obj, @tc)
nqp::settypecache($obj, @tc);
nqp::settypecheckmode($obj,
nqp::const::TYPE_CHECK_CACHE_DEFINITIVE);
}

sub reverse(@in) {
Expand Down Expand Up @@ -619,20 +534,67 @@ knowhow NQPClassHOW {
## Introspecty
##

method parents($obj, :$local = 0) {
$local ?? @!parents !! @!mro
my &PARENTS-TREE := nqp::getstaticcode(
anon sub PARENTS-TREE(@self, $obj) {
(my @parents := $obj.HOW.parents($obj, :tree))
?? @self.accept(nqp::list($obj, @parents))
!! @self.accept(nqp::list($obj))
});

my &PARENTS-ALL := nqp::getstaticcode(
anon sub PARENTS-ALL(@self, $obj) {
@self.emboss(|$obj.HOW.mro($obj))
});

method parents($obj, :$local = 0, :$tree = 0, :$excl, :$all) {
$local
?? @!parents
!! $tree
?? nqp::elems(my @p := MonicMachine.new.veneer(@!parents).banish(&PARENTS-TREE, nqp::list())) == 1
?? @p[0]
!! @p
!! $!composed
?? nqp::slice(@!mro, 1, nqp::elems(@!mro) - 1)
!! MonicMachine.new.veneer(@!parents).summon(&PARENTS-ALL).beckon(nqp::list())
}

method mro($obj) {
method mro($obj, :$concretizations, :$roles) {
@!mro
}

method roles($obj, :$local!) {
@!roles
my &ROLES-REMOTE := nqp::getstaticcode(anon sub ROLES-REMOTE(@self, $obj) {
@self.veneer($obj.HOW.roles($obj, :local, :!transitive, :!mro))
});

my &ROLES-TRANSITIVE := nqp::getstaticcode(anon sub ROLES-TRANSITIVE(@self, $obj) {
@self.accept($obj).veneer($obj.HOW.roles($obj, :local, :transitive, :!mro))
});

my &ROLES-MRO := nqp::getstaticcode(anon sub ROLES-MRO(@self, $obj) {
@self.accept(nqp::splice(nqp::list($obj), $obj.HOW.roles($obj, :local, :transitive, :!mro), 1, 0))
});

method roles($obj, :$local = 0, :$transitive = 1, :$mro = 0) {
my @roles;
if $local {
@roles := @!roles;
}
else {
@roles := nqp::clone(@!roles);
MonicMachine.new.veneer(@!parents).banish(&ROLES-REMOTE, @roles);
@roles := @roles.list();
}
if $transitive {
@roles := MonicMachine.new.veneer(@roles);
@roles := $mro
?? @roles.summon(&ROLES-MRO).beckon(nqp::list())
!! @roles.banish(&ROLES-TRANSITIVE, nqp::list());
}
@roles
}

method role_typecheck_list($obj) {
@!done;
@!role_typecheck_list
}

method methods($obj, :$local = 0, :$all) {
Expand Down Expand Up @@ -702,10 +664,10 @@ knowhow NQPClassHOW {
}

method does($obj, $check) {
my $i := nqp::elems(@!done);
my $i := nqp::elems(@!role_typecheck_list);
while $i > 0 {
$i := $i - 1;
if @!done[$i] =:= $check {
if @!role_typecheck_list[$i] =:= $check {
return 1;
}
}
Expand Down
Loading