also allow suppressing the meta method during CMOP::Class->create
[gitmo/Class-MOP.git] / t / 310_inline_structor.t
1 use strict;
2 use warnings;
3
4 use Test::More;
5
6 use Test::Requires {
7     'Test::Output' => '0.01', # skip all if not installed
8 };
9
10 use Class::MOP;
11
12 {
13     package HasConstructor;
14
15     sub new { bless {}, $_[0] }
16
17     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
18
19     $meta->superclasses('NotMoose');
20
21     ::stderr_like(
22         sub { $meta->make_immutable },
23         qr/\QNot inlining a constructor for HasConstructor since it defines its own constructor.\E\s+\QIf you are certain you don't need to inline your constructor, specify inline_constructor => 0 in your call to HasConstructor->meta->make_immutable\E/,
24         'got a warning that Foo will not have an inlined constructor because it defines its own new method'
25     );
26
27     ::is(
28         $meta->find_method_by_name('new')->body,
29         HasConstructor->can('new'),
30         'HasConstructor->new was untouched'
31     );
32 }
33
34 {
35     package My::Constructor;
36
37     use base 'Class::MOP::Method::Constructor';
38
39     sub _expected_method_class { 'Base::Class' }
40 }
41
42 {
43     package No::Constructor;
44 }
45
46 {
47     package My::Constructor2;
48
49     use base 'Class::MOP::Method::Constructor';
50
51     sub _expected_method_class { 'No::Constructor' }
52 }
53
54 {
55     package Base::Class;
56
57     sub new { bless {}, $_[0] }
58     sub DESTROY { }
59 }
60
61 {
62     package NotMoose;
63
64     sub new {
65         my $class = shift;
66
67         return bless { not_moose => 1 }, $class;
68     }
69 }
70
71 {
72     package Foo;
73     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
74
75     $meta->superclasses('NotMoose');
76
77     ::stderr_like(
78         sub { $meta->make_immutable( constructor_class => 'My::Constructor' ) },
79         qr/\QNot inlining 'new' for Foo since it is not inheriting the default Base::Class::new\E\s+\QIf you are certain you don't need to inline your constructor, specify inline_constructor => 0 in your call to Foo->meta->make_immutable/,
80         'got a warning that Foo will not have an inlined constructor'
81     );
82
83     ::is(
84         $meta->find_method_by_name('new')->body,
85         NotMoose->can('new'),
86         'Foo->new is inherited from NotMoose'
87     );
88 }
89
90 {
91     package Bar;
92     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
93
94     $meta->superclasses('NotMoose');
95
96     ::stderr_is(
97         sub { $meta->make_immutable( replace_constructor => 1 ) },
98         q{},
99         'no warning when replace_constructor is true'
100     );
101
102     ::is(
103         $meta->find_method_by_name('new')->package_name,
104         'Bar',
105         'Bar->new is inlined, and not inherited from NotMoose'
106     );
107 }
108
109 {
110     package Baz;
111     Class::MOP::Class->initialize(__PACKAGE__)->make_immutable;
112 }
113
114 {
115     package Quux;
116     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
117
118     $meta->superclasses('Baz');
119
120     ::stderr_is(
121         sub { $meta->make_immutable },
122         q{},
123         'no warning when inheriting from a class that has already made itself immutable'
124     );
125 }
126
127 {
128     package Whatever;
129     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
130
131     ::stderr_like(
132         sub { $meta->make_immutable( constructor_class => 'My::Constructor2' ) },
133         qr/\QNot inlining 'new' for Whatever since No::Constructor::new is not defined/,
134         'got a warning that Whatever will not have an inlined constructor because its expected inherited method does not exist'
135     );
136 }
137
138 {
139     package My::Constructor3;
140
141     use base 'Class::MOP::Method::Constructor';
142 }
143
144 {
145     package CustomCons;
146
147     Class::MOP::Class->initialize(__PACKAGE__)->make_immutable( constructor_class => 'My::Constructor3' );
148 }
149
150 {
151     package Subclass;
152     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
153
154     $meta->superclasses('CustomCons');
155
156     ::stderr_is(
157         sub { $meta->make_immutable },
158         q{},
159         'no warning when inheriting from a class that has already made itself immutable'
160     );
161 }
162
163 {
164     package ModdedNew;
165     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
166
167     sub new { bless {}, shift }
168
169     $meta->add_before_method_modifier( 'new' => sub { } );
170 }
171
172 {
173     package ModdedSub;
174     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
175
176     $meta->superclasses('ModdedNew');
177
178     ::stderr_like(
179         sub { $meta->make_immutable },
180         qr/\QNot inlining 'new' for ModdedSub since it has method modifiers which would be lost if it were inlined/,
181         'got a warning that ModdedSub will not have an inlined constructor since it inherited a wrapped new'
182     );
183 }
184
185 {
186     package My::Destructor;
187
188     use base 'Class::MOP::Method::Inlined';
189
190     sub new {
191         my $class   = shift;
192         my %options = @_;
193
194         my $self = bless \%options, $class;
195         $self->_inline_destructor;
196
197         return $self;
198     }
199
200     sub _inline_destructor {
201         my $self = shift;
202
203         my ( $code, $e ) = $self->_eval_closure( {}, 'sub { }' );
204         die $e if $e;
205
206         $self->{body} = $code;
207     }
208
209     sub is_needed { 1 }
210     sub associated_metaclass { $_[0]->{metaclass} }
211     sub body { $_[0]->{body} }
212     sub _expected_method_class { 'Base::Class' }
213 }
214
215 {
216     package HasDestructor;
217     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
218
219     sub DESTROY { }
220
221     ::stderr_like(
222         sub {
223             $meta->make_immutable(
224                 inline_destructor => 1,
225                 destructor_class  => 'My::Destructor',
226             );
227         },
228         qr/Not inlining a destructor for HasDestructor since it defines its own destructor./,
229         'got a warning when trying to inline a destructor for a class that already defines DESTROY'
230     );
231
232     ::is(
233         $meta->find_method_by_name('DESTROY')->body,
234         HasDestructor->can('DESTROY'),
235         'HasDestructor->DESTROY was untouched'
236     );
237 }
238
239 {
240     package HasDestructor2;
241     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
242
243     sub DESTROY { }
244
245     $meta->make_immutable(
246         inline_destructor  => 1,
247         destructor_class   => 'My::Destructor',
248         replace_destructor => 1
249     );
250
251     ::stderr_is(
252         sub {
253             $meta->make_immutable(
254                 inline_destructor  => 1,
255                 destructor_class   => 'My::Destructor',
256                 replace_destructor => 1
257             );
258         },
259         q{},
260         'no warning when replace_destructor is true'
261     );
262
263     ::isnt(
264         $meta->find_method_by_name('new')->body,
265         HasConstructor2->can('new'),
266         'HasConstructor2->new was replaced'
267     );
268 }
269
270 {
271     package ParentHasDestructor;
272
273     sub DESTROY { }
274 }
275
276 {
277     package DestructorChild;
278
279     use base 'ParentHasDestructor';
280
281     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
282
283     ::stderr_like(
284         sub {
285             $meta->make_immutable(
286                 inline_destructor => 1,
287                 destructor_class  => 'My::Destructor',
288             );
289         },
290         qr/Not inlining 'DESTROY' for DestructorChild since it is not inheriting the default Base::Class::DESTROY/,
291         'got a warning when trying to inline a destructor in a class that inherits an unexpected DESTROY'
292     );
293 }
294
295 done_testing;