Commit | Line | Data |
bbc9f1c6 |
1 | #!/usr/bin/perl |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
d443cad0 |
6 | use Test::More tests => 40; |
bbc9f1c6 |
7 | use Test::Exception; |
d443cad0 |
8 | use Test::Output; |
bbc9f1c6 |
9 | |
10 | sub req_or_has ($$) { |
11 | my ( $role, $method ) = @_; |
12 | local $Test::Builder::Level = $Test::Builder::Level + 1; |
13 | if ( $role ) { |
68b6146c |
14 | ok( |
15 | $role->has_method($method) || $role->requires_method($method), |
16 | $role->name . " has or requires method $method" |
17 | ); |
bbc9f1c6 |
18 | } else { |
19 | fail("role has or requires method $method"); |
20 | } |
21 | } |
22 | |
1344fd47 |
23 | { |
24 | package Bar; |
25 | use Moose::Role; |
bbc9f1c6 |
26 | |
27 | # this role eventually adds three methods, qw(foo bar xxy), but only one is |
28 | # known when it's still a role |
29 | |
1344fd47 |
30 | has foo => ( is => "rw" ); |
bbc9f1c6 |
31 | |
32 | has gorch => ( reader => "bar" ); |
33 | |
34 | sub xxy { "BAAAD" } |
35 | |
36 | package Gorch; |
37 | use Moose::Role; |
38 | |
39 | # similarly this role gives attr and gorch_method |
40 | |
41 | has attr => ( is => "rw" ); |
42 | |
43 | sub gorch_method { "gorch method" } |
44 | |
45 | around dandy => sub { shift->(@_) . "bar" }; |
46 | |
47 | package Quxx; |
48 | use Moose; |
49 | |
50 | sub dandy { "foo" } |
51 | |
52 | # this object will be used in an attr of Foo to test that Foo can do the |
53 | # Gorch interface |
54 | |
55 | with qw(Gorch); |
56 | |
57 | package Dancer; |
58 | use Moose::Role; |
59 | |
60 | requires "twist"; |
61 | |
62 | package Dancer::Ballerina; |
63 | use Moose; |
64 | |
65 | with qw(Dancer); |
66 | |
67 | sub twist { } |
68 | |
69 | sub pirouette { } |
70 | |
71 | package Dancer::Robot; |
72 | use Moose::Role; |
73 | |
74 | # this doesn't fail but it produces a requires in the role |
75 | # the order doesn't matter |
76 | has twist => ( is => "rw" ); |
77 | ::lives_ok { with qw(Dancer) }; |
78 | |
79 | package Dancer::Something; |
80 | use Moose; |
81 | |
82 | # this fail even though the method already exists |
83 | |
84 | has twist => ( is => "rw" ); |
85 | |
86 | { |
bbc9f1c6 |
87 | ::lives_ok { with qw(Dancer) }; |
88 | } |
89 | |
90 | package Dancer::80s; |
91 | use Moose; |
92 | |
93 | # this should pass because ::Robot has the attribute to fill in the requires |
94 | # but due to the deferrence logic that doesn't actually work |
95 | { |
96 | local our $TODO = "attribute accessor in role doesn't satisfy role requires"; |
97 | ::lives_ok { with qw(Dancer::Robot) }; |
98 | } |
99 | |
1344fd47 |
100 | package Foo; |
101 | use Moose; |
bbc9f1c6 |
102 | |
d443cad0 |
103 | ::stderr_like { |
104 | with qw(Bar); |
105 | } qr/The Foo class has implicitly overridden the method \(xxy\) from role Bar\./; |
bbc9f1c6 |
106 | |
107 | has oink => ( |
108 | is => "rw", |
109 | handles => "Gorch", # should handles take the same arguments as 'with'? Meta::Role::Application::Delegation? |
110 | default => sub { Quxx->new }, |
111 | ); |
112 | |
113 | has dancer => ( |
114 | is => "rw", |
115 | does => "Dancer", |
116 | handles => "Dancer", |
117 | default => sub { Dancer::Ballerina->new }, |
118 | ); |
119 | |
1344fd47 |
120 | sub foo { 42 } |
bbc9f1c6 |
121 | |
122 | sub bar { 33 } |
123 | |
124 | sub xxy { 7 } |
e953aaf5 |
125 | |
126 | package Tree; |
127 | use Moose::Role; |
128 | |
129 | has bark => ( is => "rw" ); |
130 | |
131 | package Dog; |
132 | use Moose::Role; |
133 | |
134 | sub bark { warn "woof!" }; |
135 | |
136 | package EntPuppy; |
137 | use Moose; |
138 | |
139 | { |
140 | local our $TODO = "attrs and methods from a role should clash"; |
d443cad0 |
141 | local $SIG{__WARN__} = sub { 'Ignore!' }; |
e953aaf5 |
142 | ::dies_ok { with qw(Tree Dog) } |
143 | } |
bbc9f1c6 |
144 | } |
145 | |
146 | # these fail because of the deferral logic winning over actual methods |
147 | # this might be tricky to fix due to the 'sub foo {}; has foo => ( )' hack |
148 | # we've been doing for a long while, though I doubt people relied on it for |
149 | # anything other than fulfilling 'requires' |
150 | { |
151 | local $TODO = "attributes from role overwrite class methods"; |
152 | is( Foo->new->foo, 42, "attr did not zap overriding method" ); |
153 | is( Foo->new->bar, 33, "attr did not zap overriding method" ); |
154 | } |
155 | is( Foo->new->xxy, 7, "method did not zap overriding method" ); # duh |
156 | |
157 | # these pass, simple delegate |
158 | # mostly they are here to contrast the next blck |
159 | can_ok( Foo->new->oink, "dandy" ); |
160 | can_ok( Foo->new->oink, "attr" ); |
161 | can_ok( Foo->new->oink, "gorch_method" ); |
162 | |
163 | ok( Foo->new->oink->does("Gorch"), "Quxx does Gorch" ); |
164 | |
165 | |
166 | # these are broken because 'attr' is not technically part of the interface |
167 | can_ok( Foo->new, "gorch_method" ); |
168 | { |
169 | local $TODO = "accessor methods from a role are omitted in handles role"; |
170 | can_ok( Foo->new, "attr" ); |
171 | } |
172 | |
173 | { |
174 | local $TODO = "handles role doesn't add the role to the ->does of the delegate's parent class"; |
175 | ok( Foo->new->does("Gorch"), "Foo does Gorch" ); |
176 | } |
177 | |
178 | |
179 | # these work |
180 | can_ok( Foo->new->dancer, "pirouette" ); |
181 | can_ok( Foo->new->dancer, "twist" ); |
182 | |
183 | can_ok( Foo->new, "twist" ); |
184 | ok( !Foo->new->can("pirouette"), "can't pirouette, not part of the iface" ); |
185 | |
186 | { |
187 | local $TODO = "handles role doesn't add the role to the ->does of the delegate's parent class"; |
188 | ok( Foo->new->does("Dancer") ); |
189 | } |
190 | |
191 | |
192 | |
193 | |
194 | my $gorch = Gorch->meta; |
195 | |
196 | isa_ok( $gorch, "Moose::Meta::Role" ); |
197 | |
198 | ok( $gorch->has_attribute("attr"), "has attribute 'attr'" ); |
199 | |
200 | { |
201 | local $TODO = "role attribute isn't a meta attribute yet"; |
202 | isa_ok( $gorch->get_attribute("attr"), "Moose::Meta::Attribute" ); |
203 | } |
204 | |
205 | req_or_has($gorch, "gorch_method"); |
206 | ok( $gorch->has_method("gorch_method"), "has_method gorch_method" ); |
207 | ok( !$gorch->requires_method("gorch_method"), "requires gorch method" ); |
9cde8a2f |
208 | isa_ok( $gorch->get_method("gorch_method"), "Moose::Meta::Method" ); |
bbc9f1c6 |
209 | |
210 | { |
211 | local $TODO = "method modifier doesn't yet create a method requirement or meta object"; |
212 | req_or_has($gorch, "dandy" ); |
213 | |
214 | # this specific test is maybe not backwards compat, but in theory it *does* |
215 | # require that method to exist |
216 | ok( $gorch->requires_method("dandy"), "requires the dandy method for the modifier" ); |
217 | } |
218 | |
219 | { |
220 | local $TODO = "attribute related methods are not yet known by the role"; |
221 | # we want this to be a part of the interface, somehow |
222 | req_or_has($gorch, "attr"); |
223 | ok( $gorch->has_method("attr"), "has_method attr" ); |
224 | isa_ok( $gorch->get_method("attr"), "Moose::Meta::Method" ); |
225 | isa_ok( $gorch->get_method("attr"), "Moose::Meta::Method::Accessor" ); |
226 | } |
227 | |
228 | my $robot = Dancer::Robot->meta; |
229 | |
230 | isa_ok( $robot, "Moose::Meta::Role" ); |
231 | |
232 | ok( $robot->has_attribute("twist"), "has attr 'twist'" ); |
233 | |
234 | { |
235 | local $TODO = "role attribute isn't a meta attribute yet"; |
236 | isa_ok( $robot->get_attribute("twist"), "Moose::Meta::Attribute" ); |
237 | } |
238 | |
239 | { |
bbc9f1c6 |
240 | req_or_has($robot, "twist"); |
a632beb5 |
241 | |
242 | local $TODO = "attribute related methods are not yet known by the role"; |
bbc9f1c6 |
243 | ok( $robot->has_method("twist"), "has twist method" ); |
244 | isa_ok( $robot->get_method("twist"), "Moose::Meta::Method" ); |
245 | isa_ok( $robot->get_method("twist"), "Moose::Meta::Method::Accessor" ); |
246 | } |
247 | |
248 | __END__ |
249 | |
250 | I think Attribute needs to be refactored in some way to better support roles. |
251 | |
252 | There are several possible ways to do this, all of them seem plausible to me. |
253 | |
254 | The first approach would be to change the attribute class to allow it to be |
255 | queried about the methods it would install. |
256 | |
257 | Then we instantiate the attribute in the role, and instead of deferring the |
258 | arguments, we just make an C<unpack>ish method. |
259 | |
260 | Then we can interrogate the attr when adding it to the role, and generate stub |
261 | methods for all the methods it would produce. |
262 | |
263 | A second approach is kinda like the Immutable hack: wrap the attr in an |
264 | anonmyous class that disables part of its interface. |
265 | |
266 | A third method would be to create an Attribute::Partial object that would |
267 | provide a more role-ish behavior, and to do this independently of the actual |
268 | Attribute class. |
269 | |
270 | Something similar can be done for method modifiers, but I think that's even simpler. |
271 | |
272 | |
273 | |
274 | The benefits of doing this are: |
275 | |
276 | * Much better introspection of roles |
277 | |
278 | * More correctness in many cases (in my opinion anyway) |
279 | |
280 | * More roles are more usable as interface declarations, without having to split |
281 | them into two pieces (one for the interface with a bunch of requires(), and |
282 | another for the actual impl with the problematic attrs (and stub methods to |
283 | fix the accessors) and method modifiers (dunno if this can even work at all) |
284 | |
285 | |