Reputation: 3045
I have a class that does a role that does another role. A method from the class can access an attribute in the top level role:
role A {
has $.x
}
role B does A {
}
class C does B {
method this() { say $!x }
}
C.new(:x(1)).this;
That works fine and says 1 like I think it should.
But I have a number of classes that all do role B, that I want to share the method this(), so I move it up into role B:
role A {
has $.x
}
role B does A {
method this() { $!x }
}
class C does B {}
C.new(:x(1)).this;
That won't even compile: SORRY Attribute $!x not declared in role B
.
Can a role not see attributes in a role it includes?
Upvotes: 7
Views: 164
Reputation: 29454
Doing this requires the introduction of a private method in the first role and access through that:
role A {
has $.x;
method !x() { $!x }
}
role B does A {
method this() { self!x }
}
class C does B {}
say C.new(:x(1)).this; # 1
The reason the original code doesn't work - and isn't trivial to make it work - is that roles are generic and potentially overloaded with different sets of type parameters, so the mention of a role is itself generic. Therefore, in general, the does A
does not identify a specific target role; there could be multiple of them, and they could have different attributes. It's not until the point that we compose the role into a class that we finally identify the concrete set of roles being composed.
However, attribute accesses are a kind of variable access, and so checked at the point we are compiling the role. In the final class, this isn't so much of a problem, since we are at that point choosing all the concrete roles we'll use; this is why it can easily work there. For the role
case, we couldn't really check them until the point of composition into a final class, which could be in a separate compilation unit altogether. This is likely a solvable problem, and a future Perl 6 language version could specify a way for this to work. For now, it conservatively does not.
Upvotes: 7