1
# This implements a parametric role (that is, one that has yet to be
2
# parameterized to get a concrete role that we can actually compose
3
# into a class or mix into an object). It also contains the logic to
4
# reify it into an actual role.
5
knowhow NQPParametricRoleHOW {
10
# Name of the parametric role.
13
# Attributes and methods.
16
has @!multi_methods_to_incorporate;
18
# Other roles that this one does.
21
# Have we been composed?
24
# The body block of the role. (If we were to support multiple role
25
# variants with the same name, this would be a multi. However, we
26
# don't do that in NQP.)
29
my $archetypes := Archetypes.new( :nominal(1), :composable(1), :parametric(1) );
38
# Creates a new instance of this meta-class.
40
my $obj := nqp::create(self);
41
$obj.BUILD(:name($name));
45
method BUILD(:$name!) {
47
@!attributes := nqp::list();
48
%!methods := nqp::hash();
49
@!multi_methods_to_incorporate := nqp::list();
50
@!roles := nqp::list();
54
# Create a new meta-class instance, and then a new type object
55
# to go with it, and return that.
56
method new_type(:$name = '<anon>') {
57
my $metarole := self.new(:name($name));
58
nqp::setwho(nqp::newtype($metarole, 'Uninstantiable'), {});
61
method set_body_block($obj, $body_block) {
62
$!body_block := $body_block;
65
method add_method($obj, $name, $code_obj) {
66
if nqp::existskey(%!methods, $name) {
67
nqp::die("This role already has a method named " ~ $name);
69
%!methods{$name} := $code_obj;
72
method add_multi_method($obj, $name, $code_obj) {
75
%todo<code> := $code_obj;
76
nqp::push(@!multi_methods_to_incorporate, %todo);
80
method add_attribute($obj, $meta_attr) {
81
my $name := $meta_attr.name;
84
nqp::die("This role already has an attribute named " ~ $name);
87
nqp::push(@!attributes, $meta_attr);
90
method add_parent($obj, $parent) {
91
nqp::die("A role cannot inherit from a class")
94
method add_role($obj, $role) {
95
nqp::push(@!roles, $role);
98
# Compose the role. Beyond this point, no changes are allowed.
99
method compose($obj) {
101
nqp::settypecache($obj, [$obj.WHAT]);
102
nqp::setmethcache($obj, {});
103
nqp::setmethcacheauth($obj, 1);
112
# Method to indicate that this type is parametric.
113
method parametric($obj) {
117
# Curries this parametric role with arguments.
118
method curry($obj, *@args) {
119
NQPCurriedRoleHOW.new_type($obj, |@args)
122
# This specializes the role for the given class and builds a concrete
124
method specialize($obj, $class_arg, *@args) {
125
# Run the body block to capture the arguments into the correct
126
# type argument context.
127
my $pad := $!body_block($class_arg, |@args);
129
# Construct a new concrete role.
130
my $irole := NQPConcreteRoleHOW.new_type(:name($!name), :instance_of($obj));
132
# Copy attributes. (Nothing to reify in NQP as we don't currently
133
# have parametric types that may end up in the signature.)
135
$irole.HOW.add_attribute($irole, $_);
138
# Capture methods in the correct lexical context.
140
my $name := nqp::iterkey_s($_);
141
my $meth := nqp::can(nqp::iterval($_), 'instantiate_generic')
142
?? nqp::iterval($_).instantiate_generic($pad)
143
!! nqp::iterval($_).clone();
144
if nqp::substr($name, 0, 12) eq '!!LATENAME!!' {
145
$name := nqp::atkey($pad, nqp::substr($name, 12));
146
$meth.'!set_name'($name);
148
$irole.HOW.add_method($irole, $name, $meth);
150
for @!multi_methods_to_incorporate {
151
$irole.HOW.add_multi_method($irole, $_<name>, $_<code>.clone());
154
# Copy roles, instantiating them as we go.
156
my $specialized := $_.HOW.specialize($_, $class_arg);
157
$irole.HOW.add_role($irole, $specialized);
160
# Compose and return produced role.
161
$irole.HOW.compose($irole);
169
method methods($obj, :$local) {
172
nqp::push(@meths, nqp::iterval($_));
177
method method_table($obj) {
185
method attributes($obj, :$local) {
188
nqp::push(@attrs, $_);