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

Prioritize class attributes/method over those from roles #3397

Merged
merged 7 commits into from Jan 2, 2020
@@ -102,6 +102,7 @@ class Perl6::Metamodel::ClassHOW
# composer.
my @roles_to_compose := self.roles_to_compose($obj);
my @stubs;
my $rtca;
if @roles_to_compose {
my @ins_roles;
while @roles_to_compose {
@@ -121,7 +122,9 @@ class Perl6::Metamodel::ClassHOW
self.add_concretization($obj, $r, $ins);
}
self.compute_mro($obj); # to the best of our knowledge, because the role applier wants it.
@stubs := RoleToClassApplier.apply($obj, @ins_roles);
$rtca := Perl6::Metamodel::Configuration.role_to_class_applier_type.new;
$rtca.prepare($obj, @ins_roles);

self.wipe_conc_cache;

# Add them to the typecheck list, and pull in their
@@ -134,6 +137,13 @@ class Perl6::Metamodel::ClassHOW
}
}

# Compose class attributes first. We prioritize them and their accessors over anything coming from roles.
self.compose_attributes($obj, :$compiler_services);

if $rtca {
@stubs := $rtca.apply();
}

# Some things we only do if we weren't already composed once, like
# building the MRO.
my $was_composed := $!composed;
@@ -148,12 +158,12 @@ class Perl6::Metamodel::ClassHOW
# Incorporate any new multi candidates (needs MRO built).
self.incorporate_multi_candidates($obj);

# Compose remaining attributes from roles.
self.compose_attributes($obj, :$compiler_services);

# Set up finalization as needed.
self.setup_finalization($obj);

# Compose attributes.
self.compose_attributes($obj, :$compiler_services);

# Test the remaining stubs
for @stubs -> %data {
if !has_method(%data<target>, %data<name>) {
@@ -63,7 +63,7 @@ class Perl6::Metamodel::ConcreteRoleHOW
method compose($the-obj) {
my $obj := nqp::decont($the-obj);

RoleToRoleApplier.apply($obj, self.roles_to_compose($obj));
Perl6::Metamodel::Configuration.role_to_role_applier_type.apply($obj, self.roles_to_compose($obj));
for self.roles_to_compose($obj) {
@!role_typecheck_list[+@!role_typecheck_list] := $_;
for $_.HOW.role_typecheck_list($_) {
@@ -25,4 +25,16 @@ class Perl6::Metamodel::Configuration {
?? $multi_sig_comparator($a, $b)
!! 0
}

my $role_to_class_applier_type := nqp::null();
method set_role_to_class_applier_type($rtca_type) {
$role_to_class_applier_type := $rtca_type;
}
method role_to_class_applier_type() { $role_to_class_applier_type }

my $role_to_role_applier_type := nqp::null();
method set_role_to_role_applier_type($rtra_type) {
$role_to_role_applier_type := $rtra_type;
}
method role_to_role_applier_type() { $role_to_role_applier_type }
}
@@ -108,6 +108,7 @@ class Perl6::Metamodel::EnumHOW
# all roles are generic on ::?CLASS) and pass them to the
# composer.
my @roles_to_compose := self.roles_to_compose($obj);
my $rtca;
if @roles_to_compose {
my @ins_roles;
while @roles_to_compose {
@@ -118,7 +119,8 @@ class Perl6::Metamodel::EnumHOW
if nqp::istype($ins.HOW, Perl6::Metamodel::LanguageRevision);
@ins_roles.push($ins);
}
RoleToClassApplier.apply($obj, @ins_roles);
$rtca := Perl6::Metamodel::Configuration.role_to_class_applier_type.new;
$rtca.prepare($obj, @ins_roles);

# Add them to the typecheck list, and pull in their
# own type check lists also.
@@ -130,10 +132,19 @@ class Perl6::Metamodel::EnumHOW
}
}

# Compose own attributes first.
for self.attributes($obj, :local) {
$_.compose($obj);
}

if $rtca {
$rtca.apply();
}

# Incorporate any new multi candidates (needs MRO built).
self.incorporate_multi_candidates($obj);

# Compose attributes.
# Compose remaining attributes.
for self.attributes($obj, :local) {
$_.compose($obj);
}
@@ -2,6 +2,7 @@ role Perl6::Metamodel::MultiMethodContainer {
# Set of multi-methods to incorporate. Not just the method handles;
# each is a hash containing keys name and body.
has @!multi_methods_to_incorporate;
has %!multi_candidate_names;

# The proto we'll clone.
my $autogen_proto;
@@ -26,6 +27,7 @@ role Perl6::Metamodel::MultiMethodContainer {
my $how := MultiToIncorporate.HOW.WHAT;
my $todo := MultiToIncorporate.new( :name($name), :code(nqp::decont($code_obj)) );
@!multi_methods_to_incorporate[+@!multi_methods_to_incorporate] := $todo;
%!multi_candidate_names{$name} := 1;
$code_obj;
}

@@ -105,5 +107,10 @@ role Perl6::Metamodel::MultiMethodContainer {
}
}
@!multi_methods_to_incorporate := [];
%!multi_candidate_names := nqp::hash();
}

method has_multi_candidate($obj, $name) {
%!multi_candidate_names{$name}
}
}
@@ -186,7 +186,7 @@ class Perl6::Metamodel::ParametricRoleHOW
# the concrete role.
for self.attributes($obj, :local(1)) {
$conc.HOW.add_attribute($conc,
$_.is_generic ?? $_.instantiate_generic($type_env) !! $_);
$_.is_generic ?? $_.instantiate_generic($type_env) !! nqp::clone($_));
}

# Go through methods and instantiate them; we always do this
@@ -1,4 +1,9 @@
my class RoleToClassApplier {
has $!target;
has $!to_compose;
has $!to_compose_meta;
has @!roles;

sub has_method($target, $name, $local) {
if $local {
my %mt := nqp::hllize($target.HOW.method_table($target));
@@ -26,27 +31,27 @@ my class RoleToClassApplier {
return nqp::existskey(%pmt, $name)
}

method apply($target, @roles) {
method prepare($target, @roles) {
$!target := $target;
@!roles := @roles;
# If we have many things to compose, then get them into a single helper
# role first.
my $to_compose;
my $to_compose_meta;
if +@roles == 1 {
$to_compose := @roles[0];
$to_compose_meta := $to_compose.HOW;
$!to_compose := @roles[0];
$!to_compose_meta := $!to_compose.HOW;
}
else {
$to_compose := $concrete.new_type();
$to_compose_meta := $to_compose.HOW;
$to_compose_meta.set_language_revision($to_compose, $target.HOW.language-revision($target));
$!to_compose := $concrete.new_type();
$!to_compose_meta := $!to_compose.HOW;
$!to_compose_meta.set_language_revision($!to_compose, $target.HOW.language-revision($target));
for @roles {
$to_compose_meta.add_role($to_compose, $_);
$!to_compose_meta.add_role($!to_compose, $_);
}
$to_compose := $to_compose_meta.compose($to_compose);
$!to_compose := $!to_compose_meta.compose($!to_compose);
}

# Collisions?
my @collisions := $to_compose_meta.collisions($to_compose);
my @collisions := $!to_compose_meta.collisions($!to_compose);
for @collisions {
if $_.private {
unless has_private_method($target, $_.name) {
@@ -84,11 +89,13 @@ my class RoleToClassApplier {
}
}
}
}

method apply() {
my @stubs;

# Starting with v6.e submethods must not be composed in from roles.
my $with_submethods := $target.HOW.lang-rev-before($target, 'e');
my $with_submethods := $!target.HOW.lang-rev-before($!target, 'e');

# Compose in any methods.
sub compose_method_table(@methods, @method_names) {
@@ -98,49 +105,49 @@ my class RoleToClassApplier {
my $yada := 0;
try { $yada := $method.yada }
if $yada {
unless has_method($target, $name, 0)
|| $target.HOW.has_public_attribute($target, $name) {
unless has_method($!target, $name, 0)
|| $!target.HOW.has_public_attribute($!target, $name) {
my @needed;
for @roles {
for @!roles {
for nqp::hllize($_.HOW.method_table($_)) -> $m {
if $m.key eq $name {
nqp::push(@needed, $_.HOW.name($_));
}
}
}
nqp::push(@stubs, nqp::hash('name', $name, 'needed', @needed, 'target', $target));
nqp::push(@stubs, nqp::hash('name', $name, 'needed', @needed, 'target', $!target));
}
}
elsif !has_method($target, $name, 1)
elsif !has_method($!target, $name, 1)
&& ($with_submethods
|| !nqp::istype($method, Perl6::Metamodel::Configuration.submethod_type))
{
$target.HOW.add_method($target, $name, $method);
$!target.HOW.add_method($!target, $name, $method);
}
}
}
my @methods := $to_compose_meta.method_order($to_compose);
my @method_names := $to_compose_meta.method_names($to_compose);
my @methods := $!to_compose_meta.method_order($!to_compose);
my @method_names := $!to_compose_meta.method_names($!to_compose);
compose_method_table(
nqp::hllize(@methods),
nqp::hllize(@method_names),
);
if nqp::can($to_compose_meta, 'private_method_table') {
my @private_methods := nqp::hllize($to_compose_meta.private_methods($to_compose));
my @private_method_names := nqp::hllize($to_compose_meta.private_method_names($to_compose));
if nqp::can($!to_compose_meta, 'private_method_table') {
my @private_methods := nqp::hllize($!to_compose_meta.private_methods($!to_compose));
my @private_method_names := nqp::hllize($!to_compose_meta.private_method_names($!to_compose));
my $i := 0;
for @private_method_names -> str $name {
unless has_private_method($target, $name) {
$target.HOW.add_private_method($target, $name, @private_methods[$i]);
unless has_private_method($!target, $name) {
$!target.HOW.add_private_method($!target, $name, @private_methods[$i]);
}
$i++;
}
}

# Compose in any multi-methods, looking for any requirements and
# ensuring they are met.
if nqp::can($to_compose_meta, 'multi_methods_to_incorporate') {
my @multis := $to_compose_meta.multi_methods_to_incorporate($to_compose);
if nqp::can($!to_compose_meta, 'multi_methods_to_incorporate') {
my @multis := $!to_compose_meta.multi_methods_to_incorporate($!to_compose);
my @required;
for @multis -> $add {
my $yada := 0;
@@ -150,7 +157,7 @@ my class RoleToClassApplier {
}
else {
my $already := 0;
for $target.HOW.multi_methods_to_incorporate($target) -> $existing {
for $!target.HOW.multi_methods_to_incorporate($!target) -> $existing {
if $existing.name eq $add.name {
if Perl6::Metamodel::Configuration.compare_multi_sigs($existing.code, $add.code) {
$already := 1;
@@ -159,12 +166,12 @@ my class RoleToClassApplier {
}
}
unless $already {
$target.HOW.add_multi_method($target, $add.name, $add.code);
$!target.HOW.add_multi_method($!target, $add.name, $add.code);
}
}
for @required -> $req {
my $satisfaction := 0;
for $target.HOW.multi_methods_to_incorporate($target) -> $existing {
for $!target.HOW.multi_methods_to_incorporate($!target) -> $existing {
if $existing.name eq $req.name {
if Perl6::Metamodel::Configuration.compare_multi_sigs($existing.code, $req.code) {
$satisfaction := 1;
@@ -176,40 +183,42 @@ my class RoleToClassApplier {
my $name := $req.name;
my $sig := $req.code.signature.perl;
nqp::die("Multi method '$name' with signature $sig must be implemented by " ~
$target.HOW.name($target) ~
$!target.HOW.name($!target) ~
" because it is required by a role");
}
}
}
}

# Compose in any role attributes.
my @attributes := $to_compose_meta.attributes($to_compose, :local(1));
my @attributes := $!to_compose_meta.attributes($!to_compose, :local(1));
for @attributes {
if $target.HOW.has_attribute($target, $_.name) {
if $!target.HOW.has_attribute($!target, $_.name) {
nqp::die("Attribute '" ~ $_.name ~ "' already exists in the class '" ~
$target.HOW.name($target) ~ "', but a role also wishes to compose it");
$!target.HOW.name($!target) ~ "', but a role also wishes to compose it");
}
$target.HOW.add_attribute($target, $_);
$!target.HOW.add_attribute($!target, $_);
}

# Compose in any parents.
if nqp::can($to_compose_meta, 'parents') {
my @parents := $to_compose_meta.parents($to_compose, :local(1));
if nqp::can($!to_compose_meta, 'parents') {
my @parents := $!to_compose_meta.parents($!to_compose, :local(1));
for @parents {
$target.HOW.add_parent($target, $_, :hides($to_compose_meta.hides_parent($to_compose, $_)));
$!target.HOW.add_parent($!target, $_, :hides($!to_compose_meta.hides_parent($!to_compose, $_)));
}
}

# Copy any array_type.
if nqp::can($target.HOW, 'is_array_type') && !$target.HOW.is_array_type($target) {
if nqp::can($to_compose_meta, 'is_array_type') {
if $to_compose_meta.is_array_type($to_compose) {
$target.HOW.set_array_type($target, $to_compose_meta.array_type($to_compose));
if nqp::can($!target.HOW, 'is_array_type') && !$!target.HOW.is_array_type($!target) {
if nqp::can($!to_compose_meta, 'is_array_type') {
if $!to_compose_meta.is_array_type($!to_compose) {
$!target.HOW.set_array_type($!target, $!to_compose_meta.array_type($!to_compose));
}
}
}

@stubs;
}

Perl6::Metamodel::Configuration.set_role_to_class_applier_type(RoleToClassApplier);
}
@@ -262,4 +262,6 @@ my class RoleToRoleApplier {

1;
}

Perl6::Metamodel::Configuration.set_role_to_role_applier_type(RoleToRoleApplier);
}
@@ -1426,6 +1426,7 @@ BEGIN {
Attribute.HOW.add_attribute(Attribute, BOOTSTRAPATTR.new(:name<$!why>, :type(Mu), :package(Attribute)));
Attribute.HOW.add_attribute(Attribute, BOOTSTRAPATTR.new(:name<$!container_initializer>, :type(Mu), :package(Attribute)));
Attribute.HOW.add_attribute(Attribute, BOOTSTRAPATTR.new(:name<$!original>, :type(Attribute), :package(Attribute)));
Attribute.HOW.add_attribute(Attribute, BOOTSTRAPATTR.new(:name<$!composed>, :type(int), :package(Attribute)));

# Need new and accessor methods for Attribute in here for now.
Attribute.HOW.add_method(Attribute, 'new',
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.