From 899d5b505f6ff2bb05961177360c1b624a228108 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Wed, 11 May 2022 14:48:41 -0300 Subject: [PATCH 01/11] Add some failing role_typecheck_list tests, tweak successful old ones - No need for nqp::eqaddr directly in the old tests. - We need to test for specializations and classes' role_typecheck_list in addition to parametric roles. Surprise! We have more work to do here. --- t/nqp/056-role.t | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/t/nqp/056-role.t b/t/nqp/056-role.t index 916959679..3e9478d21 100644 --- a/t/nqp/056-role.t +++ b/t/nqp/056-role.t @@ -1,4 +1,4 @@ -plan(18); +plan(21); role R1 { has $!a; @@ -57,9 +57,20 @@ class X does PackageUsingRole { } is(X.name(), 'PackageUsingRole', 'using $?PACKAGE from a role'); -role Bar does Foo { } -role Baz does Bar { } +role Bar does Foo { +} +role Baz does Bar { +} +class Qux does Bar { +} +class Quux does Baz { +} +class Todo is Quux { +} -my @roles := Baz.HOW.role_typecheck_list(Baz); -ok(nqp::eqaddr(@roles[0], Bar), 'role typecheck list includes roles done'); -ok(nqp::eqaddr(@roles[1], Foo), 'role typecheck list includes roles done by roles done'); +ok(nqp::istype_nd(Baz, Foo), 'role RTL includes roles done'); +ok(nqp::istype_nd(Qux, Bar), 'class RTL includes roles done after specialization'); +ok(nqp::istype_nd(Quux, Baz), 'class RTL includes roles done by parents'); +ok(nqp::istype_nd(Todo, Baz), 'class RTL includes roles done after reparenting...'); +Todo.HOW.reparent(Todo, Qux); +ok(!nqp::istype_nd(Todo, Baz), '...and not those prior'); From 27cc1ad2d44d030848ca445cb7b16a5715aeb1aa Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Tue, 24 May 2022 01:13:35 -0300 Subject: [PATCH 02/11] Fix role typechecking errors - Updating a role typecheck list should delegate to another role typecheck list at one point or another. - The role typecheck list should become part of the type cache for any given MRO-ish type. - The role typecheck list should carry a containerless `VMArray` of roles, as is the case with the MRO which, combined with the type object itself, forms the type cache of a supportive kind of type. - Because a concrete role's instance is optional as `RoleToClassApplier` depends on it, such a nullish value should not become part of the roles list. - Due to the simplicity of the various NQP HOWs' typechecks, each can carry a definitive type cache. - Curried roles need typechecking in order for Rakudo to build with each of these changes. --- src/how/NQPClassHOW.nqp | 20 ++++++++++---------- src/how/NQPConcreteRoleHOW.nqp | 31 ++++++++++++++++++++++--------- src/how/NQPCurriedRoleHOW.nqp | 14 +++++++++++++- src/how/NQPParametricRoleHOW.nqp | 23 ++++++++++++++++------- src/how/RoleToClassApplier.nqp | 2 +- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index 5d062d58e..bcdcc745d 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -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(@!done, $role); + nqp::splice(@!done, @ins_rtl, nqp::elems(@!done), 0); nqp::push(@specialized_roles, $ins); - nqp::push(@!done, $_); - nqp::push(@!done, $ins); } RoleToClassApplier.apply($obj, @specialized_roles); } @@ -480,17 +481,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) { diff --git a/src/how/NQPConcreteRoleHOW.nqp b/src/how/NQPConcreteRoleHOW.nqp index adb90b8cd..6cba2b677 100644 --- a/src/how/NQPConcreteRoleHOW.nqp +++ b/src/how/NQPConcreteRoleHOW.nqp @@ -40,7 +40,7 @@ knowhow NQPConcreteRoleHOW { # Creates a new instance of this meta-class. method new(:$name!, :$instance_of!) { my $obj := nqp::create(self); - $obj.BUILD(:name($name), :instance_of($instance_of)); + $obj.BUILD(:$name, :$instance_of); $obj } @@ -55,13 +55,14 @@ knowhow NQPConcreteRoleHOW { @!collisions := nqp::list(); @!roles := nqp::list(); @!role_typecheck_list := nqp::list(); + nqp::isnull($instance_of) || nqp::push(@!role_typecheck_list, $instance_of); $!composed := 0; } # Create a new meta-object instance, and then a new type object # to go with it, and return that. - method new_type(:$name = '', :$instance_of!) { - my $metarole := self.new(:name($name), :instance_of($instance_of)); + method new_type(:$name = '', :$instance_of = nqp::null()) { + my $metarole := self.new(:$name, :$instance_of); nqp::settypehll($metarole, 'nqp'); nqp::setdebugtypename(nqp::newtype($metarole, 'Uninstantiable'), $name); } @@ -109,23 +110,35 @@ knowhow NQPConcreteRoleHOW { # Incorporate roles. They're already instantiated. We need to # add to done list their instantiation source. if @!roles { - for @!roles { - nqp::push(@!role_typecheck_list, $_); - nqp::push(@!role_typecheck_list, $_.HOW.instance_of($_)); + for @!roles -> $role { + my @role_rtl := nqp::how_nd($role).role_typecheck_list($role); + nqp::push(@!role_typecheck_list, $role); + nqp::splice(@!role_typecheck_list, @role_rtl, nqp::elems(@!role_typecheck_list), 0); } RoleToRoleApplier.apply($obj, @!roles); } - # Mark composed. - $!composed := 1; - nqp::settypecache($obj, [$obj.WHAT]); + # Publish type cache. + my @tc := nqp::clone(@!role_typecheck_list); + nqp::unshift(@tc, $obj.WHAT); + nqp::settypecache($obj, @tc); + nqp::settypecheckmode($obj, + nqp::const::TYPE_CHECK_CACHE_DEFINITIVE); + + # Publish method cache. #?if !moar nqp::setmethcache($obj, {}); nqp::setmethcacheauth($obj, 1); #?endif + + # Mark composed. + $!composed := 1; $obj } + method is_composed($obj) { + $!composed + } ## ## Introspecty diff --git a/src/how/NQPCurriedRoleHOW.nqp b/src/how/NQPCurriedRoleHOW.nqp index 622cb55f2..842e79b6c 100644 --- a/src/how/NQPCurriedRoleHOW.nqp +++ b/src/how/NQPCurriedRoleHOW.nqp @@ -1,6 +1,7 @@ knowhow NQPCurriedRoleHOW { has $!curried_role; has @!pos_args; + has @!role_typecheck_list; my $archetypes := Archetypes.new( :nominal(1), :composable(1), :parametric(1) ); method archetypes() { @@ -16,6 +17,9 @@ knowhow NQPCurriedRoleHOW { method BUILD(:$curried_role!, :@pos_args!) { $!curried_role := $curried_role; @!pos_args := @pos_args; + @!role_typecheck_list := nqp::clone( + nqp::how_nd($curried_role).role_typecheck_list($curried_role)); + nqp::unshift(@!role_typecheck_list, $curried_role); } method new_type($curried_role!, *@pos_args) { @@ -23,7 +27,11 @@ knowhow NQPCurriedRoleHOW { my $type := nqp::newtype($meta, 'Uninstantiable'); nqp::settypehll($type, 'nqp'); nqp::setdebugtypename($type, 'Curried ' ~ $curried_role.HOW.name($curried_role)); - $type + + my @rtl := $meta.role_typecheck_list($type); + nqp::settypecache($type, @rtl); + nqp::settypecheckmode($type, + nqp::const::TYPE_CHECK_CACHE_DEFINITIVE); } method specialize($obj, $class_arg) { @@ -42,4 +50,8 @@ knowhow NQPCurriedRoleHOW { method curried_role($obj) { $!curried_role } + + method role_typecheck_list($obj) { + @!role_typecheck_list + } } diff --git a/src/how/NQPParametricRoleHOW.nqp b/src/how/NQPParametricRoleHOW.nqp index 08b5e1a0d..fafef8381 100644 --- a/src/how/NQPParametricRoleHOW.nqp +++ b/src/how/NQPParametricRoleHOW.nqp @@ -107,19 +107,28 @@ knowhow NQPParametricRoleHOW { # Compose the role. Beyond this point, no changes are allowed. method compose($obj) { - for @!roles { - nqp::push(@!role_typecheck_list, $_); - for $_.HOW.role_typecheck_list($_) { - nqp::push(@!role_typecheck_list, $_); - } + # Update the role typecheck list. + for @!roles -> $role { + my @role_rtl := nqp::how_nd($role).role_typecheck_list($role); + nqp::push(@!role_typecheck_list, $role); + nqp::splice(@!role_typecheck_list, @role_rtl, nqp::elems(@!role_typecheck_list), 0); } - $!composed := 1; - nqp::settypecache($obj, [$obj.WHAT]); + # Publish type cache. + my @tc := nqp::clone(@!role_typecheck_list); + nqp::unshift(@tc, $obj.WHAT); + nqp::settypecache($obj, @tc); + nqp::settypecheckmode($obj, + nqp::const::TYPE_CHECK_CACHE_DEFINITIVE); + + # Publish method cache. #?if !moar nqp::setmethcache($obj, {}); nqp::setmethcacheauth($obj, 1); #?endif + + # Mark composed. + $!composed := 1; $obj } diff --git a/src/how/RoleToClassApplier.nqp b/src/how/RoleToClassApplier.nqp index 1440de7cb..bf216750f 100644 --- a/src/how/RoleToClassApplier.nqp +++ b/src/how/RoleToClassApplier.nqp @@ -23,7 +23,7 @@ knowhow RoleToClassApplier { $to_compose_meta := $to_compose.HOW; } else { - $to_compose := NQPConcreteRoleHOW.new_type(:instance_of(NQPMu)); + $to_compose := NQPConcreteRoleHOW.new_type(); $to_compose_meta := $to_compose.HOW; for @roles { $to_compose_meta.add_role($to_compose, $_); From 533acf934e0bc449f4cd52ae4eaea52617a47a09 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Thu, 26 May 2022 11:46:48 -0300 Subject: [PATCH 03/11] Unify the roles metamethods This is more or less a direct translation of Rakudo's `roles` metamethods as applied. Besides new parameters, the key difference is `NQPClassHOW.roles`' `:$local` parameter, which (thankfully) was required before. It should be capable of matching Rakudo's `0` default because of that. --- src/how/NQPClassHOW.nqp | 38 ++++++++++++++++++++++++++++++-- src/how/NQPConcreteRoleHOW.nqp | 15 +++++++++++-- src/how/NQPCurriedRoleHOW.nqp | 4 ++++ src/how/NQPParametricRoleHOW.nqp | 15 +++++++++++-- 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index bcdcc745d..232705396 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -627,8 +627,42 @@ knowhow NQPClassHOW { @!mro } - method roles($obj, :$local!) { - @!roles + method roles($obj, :$local = 0, :$transitive = 1, :$mro = 0) { + $local + ?? $transitive + ?? $mro + ?? self.visit_roles_in_mro($obj, @!roles, nqp::list()) + !! self.visit_roles($obj, @!roles, nqp::list()) + !! @!roles + !! self.visit_roles_by_mro($obj, @!roles, nqp::list(), :$transitive, :$mro) + } + + method visit_roles($obj, $roles, @result) { + for nqp::hllize($roles) -> $role { + my @role_roles := nqp::hllize($role.HOW.roles($role, :local, :transitive, :!mro)); + nqp::push(@result, $role); + nqp::splice(@result, @role_roles, nqp::elems(@result), 0); + } + @result + } + + method visit_roles_in_mro($obj, $roles, @result) { + for nqp::hllize($roles) -> $role { + my @role_roles := nqp::hllize($role.HOW.roles($role, :local, :transitive, :!mro)); + my @todo := nqp::clone(@role_roles); + nqp::unshift(@todo, $role); + nqp::push(@result, @todo); + } + c3_merge(@result) + } + + method visit_roles_by_mro($obj, $roles, @result, :$transitive!, :$mro!) { + my $i := nqp::elems(@!mro); + my $n := nqp::elems(@result); + nqp::splice(@result, + nqp::hllize(@!mro[$i].HOW.roles(@!mro[$i], :local, :$transitive, :$mro)), + $n, 0) while $i--; + @result } method role_typecheck_list($obj) { diff --git a/src/how/NQPConcreteRoleHOW.nqp b/src/how/NQPConcreteRoleHOW.nqp index 6cba2b677..9141c9a26 100644 --- a/src/how/NQPConcreteRoleHOW.nqp +++ b/src/how/NQPConcreteRoleHOW.nqp @@ -177,8 +177,19 @@ knowhow NQPConcreteRoleHOW { @!attributes } - method roles($obj, :$transitive = 0) { - @!roles + method roles($obj, :$local, :$transitive = 1, :$mro) { + $transitive + ?? self.visit_roles($obj, @!roles, nqp::list()) + !! @!roles + } + + method visit_roles($obj, $roles, @result) { + for nqp::hllize($roles) -> $role { + my @role_roles := nqp::hllize($role.HOW.roles($role, :local, :transitive, :!mro)); + nqp::push(@result, $role); + nqp::splice(@result, @role_roles, nqp::elems(@result), 0); + } + @result } method role_typecheck_list($obj) { diff --git a/src/how/NQPCurriedRoleHOW.nqp b/src/how/NQPCurriedRoleHOW.nqp index 842e79b6c..44561060d 100644 --- a/src/how/NQPCurriedRoleHOW.nqp +++ b/src/how/NQPCurriedRoleHOW.nqp @@ -51,6 +51,10 @@ knowhow NQPCurriedRoleHOW { $!curried_role } + method roles($obj, *%named) { + $!curried_role.HOW.roles($!curried_role, |%named) + } + method role_typecheck_list($obj) { @!role_typecheck_list } diff --git a/src/how/NQPParametricRoleHOW.nqp b/src/how/NQPParametricRoleHOW.nqp index fafef8381..53dd6aaef 100644 --- a/src/how/NQPParametricRoleHOW.nqp +++ b/src/how/NQPParametricRoleHOW.nqp @@ -223,8 +223,19 @@ knowhow NQPParametricRoleHOW { @!attributes } - method roles($obj, :$transitive = 0) { - @!roles + method roles($obj, :$local, :$transitive = 1, :$mro) { + $transitive + ?? self.visit_roles($obj, @!roles, nqp::list()) + !! @!roles + } + + method visit_roles($obj, $roles, @result) { + for nqp::hllize($roles) -> $role { + my @role_roles := nqp::hllize($role.HOW.roles($role, :local, :transitive, :!mro)); + nqp::push(@result, $role); + nqp::splice(@result, @role_roles, nqp::elems(@result), 0); + } + @result } method role_typecheck_list($obj) { From 3003290d97a22faf1414014954e2bd3bf28bffff Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Sat, 4 Jun 2022 20:57:27 -0300 Subject: [PATCH 04/11] Revert "Unify the roles metamethods" This is a recursive algorithm, echoing problems with coercions back in the day. There's a better approach to this. This reverts commit 533acf934e0bc449f4cd52ae4eaea52617a47a09. --- src/how/NQPClassHOW.nqp | 38 ++------------------------------ src/how/NQPConcreteRoleHOW.nqp | 15 ++----------- src/how/NQPCurriedRoleHOW.nqp | 4 ---- src/how/NQPParametricRoleHOW.nqp | 15 ++----------- 4 files changed, 6 insertions(+), 66 deletions(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index 232705396..bcdcc745d 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -627,42 +627,8 @@ knowhow NQPClassHOW { @!mro } - method roles($obj, :$local = 0, :$transitive = 1, :$mro = 0) { - $local - ?? $transitive - ?? $mro - ?? self.visit_roles_in_mro($obj, @!roles, nqp::list()) - !! self.visit_roles($obj, @!roles, nqp::list()) - !! @!roles - !! self.visit_roles_by_mro($obj, @!roles, nqp::list(), :$transitive, :$mro) - } - - method visit_roles($obj, $roles, @result) { - for nqp::hllize($roles) -> $role { - my @role_roles := nqp::hllize($role.HOW.roles($role, :local, :transitive, :!mro)); - nqp::push(@result, $role); - nqp::splice(@result, @role_roles, nqp::elems(@result), 0); - } - @result - } - - method visit_roles_in_mro($obj, $roles, @result) { - for nqp::hllize($roles) -> $role { - my @role_roles := nqp::hllize($role.HOW.roles($role, :local, :transitive, :!mro)); - my @todo := nqp::clone(@role_roles); - nqp::unshift(@todo, $role); - nqp::push(@result, @todo); - } - c3_merge(@result) - } - - method visit_roles_by_mro($obj, $roles, @result, :$transitive!, :$mro!) { - my $i := nqp::elems(@!mro); - my $n := nqp::elems(@result); - nqp::splice(@result, - nqp::hllize(@!mro[$i].HOW.roles(@!mro[$i], :local, :$transitive, :$mro)), - $n, 0) while $i--; - @result + method roles($obj, :$local!) { + @!roles } method role_typecheck_list($obj) { diff --git a/src/how/NQPConcreteRoleHOW.nqp b/src/how/NQPConcreteRoleHOW.nqp index 9141c9a26..6cba2b677 100644 --- a/src/how/NQPConcreteRoleHOW.nqp +++ b/src/how/NQPConcreteRoleHOW.nqp @@ -177,19 +177,8 @@ knowhow NQPConcreteRoleHOW { @!attributes } - method roles($obj, :$local, :$transitive = 1, :$mro) { - $transitive - ?? self.visit_roles($obj, @!roles, nqp::list()) - !! @!roles - } - - method visit_roles($obj, $roles, @result) { - for nqp::hllize($roles) -> $role { - my @role_roles := nqp::hllize($role.HOW.roles($role, :local, :transitive, :!mro)); - nqp::push(@result, $role); - nqp::splice(@result, @role_roles, nqp::elems(@result), 0); - } - @result + method roles($obj, :$transitive = 0) { + @!roles } method role_typecheck_list($obj) { diff --git a/src/how/NQPCurriedRoleHOW.nqp b/src/how/NQPCurriedRoleHOW.nqp index 44561060d..842e79b6c 100644 --- a/src/how/NQPCurriedRoleHOW.nqp +++ b/src/how/NQPCurriedRoleHOW.nqp @@ -51,10 +51,6 @@ knowhow NQPCurriedRoleHOW { $!curried_role } - method roles($obj, *%named) { - $!curried_role.HOW.roles($!curried_role, |%named) - } - method role_typecheck_list($obj) { @!role_typecheck_list } diff --git a/src/how/NQPParametricRoleHOW.nqp b/src/how/NQPParametricRoleHOW.nqp index 53dd6aaef..fafef8381 100644 --- a/src/how/NQPParametricRoleHOW.nqp +++ b/src/how/NQPParametricRoleHOW.nqp @@ -223,19 +223,8 @@ knowhow NQPParametricRoleHOW { @!attributes } - method roles($obj, :$local, :$transitive = 1, :$mro) { - $transitive - ?? self.visit_roles($obj, @!roles, nqp::list()) - !! @!roles - } - - method visit_roles($obj, $roles, @result) { - for nqp::hllize($roles) -> $role { - my @role_roles := nqp::hllize($role.HOW.roles($role, :local, :transitive, :!mro)); - nqp::push(@result, $role); - nqp::splice(@result, @role_roles, nqp::elems(@result), 0); - } - @result + method roles($obj, :$transitive = 0) { + @!roles } method role_typecheck_list($obj) { From f77e64ed2da8fafc9c6b7df742db7e96e4768597 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Mon, 6 Jun 2022 19:20:43 -0300 Subject: [PATCH 05/11] Make nqp classes vaguely aware of ^mro(:concretizations|:roles) `Metamodel::C3MRO` now expects a `:concretizations` parameter, but we lack the `Metamodel::ConcreteRoleHOW` it wants from whatever carries it; `:roles` is based off this as well. --- src/how/NQPClassHOW.nqp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index bcdcc745d..f16203687 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -623,7 +623,7 @@ knowhow NQPClassHOW { $local ?? @!parents !! @!mro } - method mro($obj) { + method mro($obj, :$concretizations, :$roles) { @!mro } From 319b6af236ab08baef2ef90de1c59b7d0dc434d7 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Mon, 6 Jun 2022 22:39:55 -0300 Subject: [PATCH 06/11] Introduce the metamodel MonicMachine A backport of Rakudo's. We lack its efface method here since we lack `^parents(:excl)`. --- src/how/MonicMachine.nqp | 80 ++++++++++++++++++++++++++++++ tools/templates/Makefile-common.in | 1 + 2 files changed, 81 insertions(+) create mode 100644 src/how/MonicMachine.nqp diff --git a/src/how/MonicMachine.nqp b/src/how/MonicMachine.nqp new file mode 100644 index 000000000..7fe355189 --- /dev/null +++ b/src/how/MonicMachine.nqp @@ -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) + } +} diff --git a/tools/templates/Makefile-common.in b/tools/templates/Makefile-common.in index c214ff90a..5b51b6367 100644 --- a/tools/templates/Makefile-common.in +++ b/tools/templates/Makefile-common.in @@ -90,6 +90,7 @@ QAST_COMBINED = QAST.nqp NQP_MO_SOURCES = \ @nfp(src/how/Archetypes.nqp)@ \ + @nfp(src/how/MonicMachine.nqp)@ \ @nfp(src/how/RoleToRoleApplier.nqp)@ \ @nfp(src/how/NQPConcreteRoleHOW.nqp)@ \ @nfp(src/how/RoleToClassApplier.nqp)@ \ From e5270b5cba8ffb2913218e5ca1dfc5219d52d787 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Tue, 7 Jun 2022 00:14:21 -0300 Subject: [PATCH 07/11] Compute C3MRO via monic beckoning, not c3_merge Backport of Rakudo's changes. Err against recursing by depending on the MROs of parents instead. --- src/how/NQPClassHOW.nqp | 91 ++--------------------------------------- 1 file changed, 3 insertions(+), 88 deletions(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index f16203687..188f26b85 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -384,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) { From 6d8a65b428d1c02ad394999d7e469530a6a02b72 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Wed, 8 Jun 2022 03:19:15 -0300 Subject: [PATCH 08/11] Fix NQPClassHOW's role typecheck list fix Include the specialization, not a duplicate of the role that was specialized. --- src/how/NQPClassHOW.nqp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index 188f26b85..1c6b987c2 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -232,7 +232,7 @@ knowhow NQPClassHOW { 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(@!done, $role); + nqp::push(@!done, $ins); nqp::splice(@!done, @ins_rtl, nqp::elems(@!done), 0); nqp::push(@specialized_roles, $ins); } From 842b5fcb460f7a23c0be003ce6bd209b6f301e0b Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Wed, 8 Jun 2022 03:20:26 -0300 Subject: [PATCH 09/11] Call NQPClassHOW's role typecheck list what it is For consistency with every other HOW. --- src/how/NQPClassHOW.nqp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index 1c6b987c2..07b3ebab1 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -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 @@ -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; @@ -232,8 +232,8 @@ knowhow NQPClassHOW { 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(@!done, $ins); - nqp::splice(@!done, @ins_rtl, nqp::elems(@!done), 0); + nqp::push(@!role_typecheck_list, $ins); + nqp::splice(@!role_typecheck_list, @ins_rtl, nqp::elems(@!role_typecheck_list), 0); nqp::push(@specialized_roles, $ins); } RoleToClassApplier.apply($obj, @specialized_roles); @@ -547,7 +547,7 @@ knowhow NQPClassHOW { } method role_typecheck_list($obj) { - @!done; + @!role_typecheck_list } method methods($obj, :$local = 0, :$all) { @@ -617,10 +617,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; } } From 3a30678aeb5432e0ca6dead1ee732a455be6a989 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Wed, 8 Jun 2022 20:50:05 -0300 Subject: [PATCH 10/11] Unify the roles metamethods Backport of Rakudo's changes. `NQPConcreteRoleHOW` follows MRO ordering for consistency with `Metamodel::ConcreteRoleHOW`. --- src/how/NQPClassHOW.nqp | 31 +++++++++++++++++++++++++++++-- src/how/NQPConcreteRoleHOW.nqp | 19 +++++++++++++++++-- src/how/NQPCurriedRoleHOW.nqp | 4 ++++ src/how/NQPParametricRoleHOW.nqp | 19 +++++++++++++++++-- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index 07b3ebab1..48a5a476e 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -542,8 +542,35 @@ knowhow NQPClassHOW { @!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) { diff --git a/src/how/NQPConcreteRoleHOW.nqp b/src/how/NQPConcreteRoleHOW.nqp index 6cba2b677..2bf984eb7 100644 --- a/src/how/NQPConcreteRoleHOW.nqp +++ b/src/how/NQPConcreteRoleHOW.nqp @@ -177,8 +177,23 @@ knowhow NQPConcreteRoleHOW { @!attributes } - method roles($obj, :$transitive = 0) { - @!roles + my &ROLES-TRANSITIVE := nqp::getstaticcode(anon sub ROLES-TRANSITIVE(@self, $obj) { + @self.accept($obj).veneer($obj.HOW.roles($obj, :transitive, :!mro)) + }); + + my &ROLES-MRO := nqp::getstaticcode(anon sub ROLES-MRO(@self, $obj) { + @self.accept(nqp::splice(nqp::list($obj), $obj.HOW.roles($obj, :transitive, :!mro), 1, 0)) + }); + + method roles($obj, :$local, :$transitive = 1, :$mro = 1) { + my @roles := @!roles; + 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) { diff --git a/src/how/NQPCurriedRoleHOW.nqp b/src/how/NQPCurriedRoleHOW.nqp index 842e79b6c..88bae8ae4 100644 --- a/src/how/NQPCurriedRoleHOW.nqp +++ b/src/how/NQPCurriedRoleHOW.nqp @@ -54,4 +54,8 @@ knowhow NQPCurriedRoleHOW { method role_typecheck_list($obj) { @!role_typecheck_list } + + method roles($obj, *%named) { + $!curried_role.HOW.roles($!curried_role, |%named) + } } diff --git a/src/how/NQPParametricRoleHOW.nqp b/src/how/NQPParametricRoleHOW.nqp index fafef8381..c116ce921 100644 --- a/src/how/NQPParametricRoleHOW.nqp +++ b/src/how/NQPParametricRoleHOW.nqp @@ -223,8 +223,23 @@ knowhow NQPParametricRoleHOW { @!attributes } - method roles($obj, :$transitive = 0) { - @!roles + my &ROLES-TRANSITIVE := nqp::getstaticcode(anon sub ROLES-TRANSITIVE(@self, $obj) { + @self.accept($obj).veneer($obj.HOW.roles($obj, :transitive, :!mro)) + }); + + my &ROLES-MRO := nqp::getstaticcode(anon sub ROLES-MRO(@self, $obj) { + @self.accept(nqp::splice(nqp::list($obj), $obj.HOW.roles($obj, :transitive, :!mro), 1, 0)) + }); + + method roles($obj, :$local, :$transitive = 1, :$mro = 0) { + my @roles := @!roles; + 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) { From f31c49986175960110f7b984fd013585d0bdf2b7 Mon Sep 17 00:00:00 2001 From: Ben Davies Date: Mon, 13 Jun 2022 21:03:03 -0300 Subject: [PATCH 11/11] Unify the parents metamethods Backport of Rakudo's changes. Nothing's hidden in nqp, so nothing to `:excl`. --- src/how/NQPClassHOW.nqp | 24 ++++++++++++++++++++++-- src/how/NQPConcreteRoleHOW.nqp | 8 ++++++++ src/how/NQPCurriedRoleHOW.nqp | 8 ++++++++ src/how/NQPParametricRoleHOW.nqp | 8 ++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/how/NQPClassHOW.nqp b/src/how/NQPClassHOW.nqp index 48a5a476e..8e8b6be61 100644 --- a/src/how/NQPClassHOW.nqp +++ b/src/how/NQPClassHOW.nqp @@ -534,8 +534,28 @@ 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, :$concretizations, :$roles) { diff --git a/src/how/NQPConcreteRoleHOW.nqp b/src/how/NQPConcreteRoleHOW.nqp index 2bf984eb7..48a58b84b 100644 --- a/src/how/NQPConcreteRoleHOW.nqp +++ b/src/how/NQPConcreteRoleHOW.nqp @@ -203,4 +203,12 @@ knowhow NQPConcreteRoleHOW { method instance_of($obj) { $!instance_of } + + method parents($obj, *%named) { + [] + } + + method mro($obj, *%named) { + [$obj] + } } diff --git a/src/how/NQPCurriedRoleHOW.nqp b/src/how/NQPCurriedRoleHOW.nqp index 88bae8ae4..4a1c66017 100644 --- a/src/how/NQPCurriedRoleHOW.nqp +++ b/src/how/NQPCurriedRoleHOW.nqp @@ -58,4 +58,12 @@ knowhow NQPCurriedRoleHOW { method roles($obj, *%named) { $!curried_role.HOW.roles($!curried_role, |%named) } + + method parents($obj, *%named) { + $!curried_role.HOW.parents($!curried_role, |%named) + } + + method mro($obj, *%named) { + [$obj] + } } diff --git a/src/how/NQPParametricRoleHOW.nqp b/src/how/NQPParametricRoleHOW.nqp index c116ce921..c75680115 100644 --- a/src/how/NQPParametricRoleHOW.nqp +++ b/src/how/NQPParametricRoleHOW.nqp @@ -245,4 +245,12 @@ knowhow NQPParametricRoleHOW { method role_typecheck_list($obj) { @!role_typecheck_list } + + method parents($obj, *%named) { + [] + } + + method mro($obj, *%named) { + [$obj] + } }