1#!./perl 2 3BEGIN { 4 chdir 't' if -d 't'; 5 require './test.pl'; 6 set_up_inc('../lib'); 7 require Config; 8} 9 10use v5.36; 11use feature 'class'; 12no warnings 'experimental::class'; 13 14# We can't test fields in isolation without having at least one method to 15# use them from. We'll try to keep most of the heavy testing of method 16# abilities to t/class/method.t 17 18# field in method 19{ 20 class Testcase1 { 21 field $f; 22 method incr { return ++$f; } 23 } 24 25 my $obj = Testcase1->new; 26 $obj->incr; 27 is($obj->incr, 2, 'Field $f incremented twice'); 28 29 my $obj2 = Testcase1->new; 30 is($obj2->incr, 1, 'Fields are distinct between instances'); 31} 32 33# fields are distinct 34{ 35 class Testcase2 { 36 field $x; 37 field $y; 38 39 method setpos { $x = $_[0]; $y = $_[1] } 40 method x { return $x; } 41 method y { return $y; } 42 } 43 44 my $obj = Testcase2->new; 45 $obj->setpos(10, 20); 46 is($obj->x, 10, '$pos->x'); 47 is($obj->y, 20, '$pos->y'); 48} 49 50# fields of all variable types 51{ 52 class Testcase3 { 53 field $s; 54 field @a; 55 field %h; 56 57 method setup { 58 $s = "scalar"; 59 @a = ( "array" ); 60 %h = ( key => "hash" ); 61 return $self; # test chaining 62 } 63 method test { 64 ::is($s, "scalar", 'scalar storage'); 65 ::is($a[0], "array", 'array storage'); 66 ::is($h{key}, "hash", 'hash storage'); 67 } 68 } 69 70 Testcase3->new->setup->test; 71} 72 73# fields can be captured by anon subs 74{ 75 class Testcase4 { 76 field $count; 77 78 method make_incrsub { 79 return sub { $count++ }; 80 } 81 82 method count { return $count } 83 } 84 85 my $obj = Testcase4->new; 86 my $incr = $obj->make_incrsub; 87 88 $incr->(); 89 $incr->(); 90 $incr->(); 91 92 is($obj->count, 3, '$obj->count after invoking closure x 3'); 93} 94 95# fields can be captured by anon methods 96{ 97 class Testcase5 { 98 field $count; 99 100 method make_incrmeth { 101 return method { $count++ }; 102 } 103 104 method count { return $count } 105 } 106 107 my $obj = Testcase5->new; 108 my $incr = $obj->make_incrmeth; 109 110 $obj->$incr; 111 $obj->$incr; 112 $obj->$incr; 113 114 is($obj->count, 3, '$obj->count after invoking method-closure x 3'); 115} 116 117# fields of multiple unit classes are distinct 118{ 119 class Testcase6::A; 120 field $x = "A"; 121 method m { return "unit-$x" } 122 123 class Testcase6::B; 124 field $x = "B"; 125 method m { return "unit-$x" } 126 127 package main; 128 ok(eq_array([Testcase6::A->new->m, Testcase6::B->new->m], ["unit-A", "unit-B"]), 129 'Fields of multiple unit classes remain distinct'); 130} 131 132# fields can be initialised with constant expressions 133{ 134 class Testcase7 { 135 field $scalar = 123; 136 method scalar { return $scalar; } 137 138 field @array = (4, 5, 6); 139 method array { return @array; } 140 141 field %hash = (7 => 89); 142 method hash { return %hash; } 143 } 144 145 my $obj = Testcase7->new; 146 147 is($obj->scalar, 123, 'Scalar field can be constant initialised'); 148 149 ok(eq_array([$obj->array], [4, 5, 6]), 'Array field can be constant initialised'); 150 151 ok(eq_hash({$obj->hash}, {7 => 89}), 'Hash field can be constant initialised'); 152} 153 154# field initialiser expressions are evaluated within the constructor of each 155# instance 156{ 157 my $next_x = 1; 158 my @items; 159 my %mappings; 160 161 class Testcase8 { 162 field $x = $next_x++; 163 method x { return $x; } 164 165 field @y = ("more", @items); 166 method y { return @y; } 167 168 field %z = (first => "value", %mappings); 169 method z { return %z; } 170 } 171 172 is($next_x, 1, '$next_x before any objects'); 173 174 @items = ("values"); 175 $mappings{second} = "here"; 176 177 my $obj1 = Testcase8->new; 178 my $obj2 = Testcase8->new; 179 180 is($obj1->x, 1, 'Object 1 has x == 1'); 181 is($obj2->x, 2, 'Object 2 has x == 2'); 182 183 is($next_x, 3, '$next_x after constructing two'); 184 185 ok(eq_array([$obj1->y], ["more", "values"]), 186 'Object 1 has correct array field'); 187 ok(eq_hash({$obj1->z}, {first => "value", second => "here"}), 188 'Object 1 has correct hash field'); 189} 190 191# fields are visible during initialiser expressions of later fields 192{ 193 class Testcase9 { 194 field $one = 1; 195 field $two = $one + 1; 196 field $three = $two + 1; 197 198 field @four = $one; 199 field @five = (@four, $two, $three); 200 field @six = grep { $_ > 1 } @five; 201 202 method three { return $three; } 203 204 method six { return @six; } 205 } 206 207 my $obj = Testcase9->new; 208 is($obj->three, 3, 'Scalar fields initialised from earlier fields'); 209 ok(eq_array([$obj->six], [2, 3]), 'Array fields initialised from earlier fields'); 210} 211 212# fields can take :param attributes to consume constructor parameters 213{ 214 my $next_gamma = 4; 215 216 class Testcase10 { 217 field $alpha :param = undef; 218 field $beta :param = 123; 219 field $gamma :param(delta) = $next_gamma++; 220 221 method values { return ($alpha, $beta, $gamma); } 222 } 223 224 my $obj = Testcase10->new( 225 alpha => "A", 226 beta => "B", 227 delta => "G", 228 ); 229 ok(eq_array([$obj->values], [qw(A B G)]), 230 'Field initialised by :params'); 231 is($next_gamma, 4, 'Defaulting expression not evaluated for passed value'); 232 233 $obj = Testcase10->new(); 234 ok(eq_array([$obj->values], [undef, 123, 4]), 235 'Field initialised by defaulting expressions'); 236 is($next_gamma, 5, 'Defaulting expression evaluated for missing value'); 237} 238 239# fields can be made non-optional 240{ 241 class Testcase11 { 242 field $x :param; 243 field $y :param; 244 } 245 246 Testcase11->new(x => 1, y => 1); 247 248 ok(!eval { Testcase11->new(x => 2) }, 249 'Constructor fails without y'); 250 like($@, qr/^Required parameter 'y' is missing for "Testcase11" constructor at /, 251 'Failure from missing y argument'); 252} 253 254# field assignment expressions on :param can use //= and ||= 255{ 256 class Testcase12 { 257 field $if_exists :param(e) = "DEF"; 258 field $if_defined :param(d) //= "DEF"; 259 field $if_true :param(t) ||= "DEF"; 260 261 method values { return ($if_exists, $if_defined, $if_true); } 262 } 263 264 ok(eq_array( 265 [Testcase12->new(e => "yes", d => "yes", t => "yes")->values], 266 ["yes", "yes", "yes"]), 267 'Values for "yes"'); 268 269 ok(eq_array( 270 [Testcase12->new(e => 0, d => 0, t => 0)->values], 271 [0, 0, "DEF"]), 272 'Values for 0'); 273 274 ok(eq_array( 275 [Testcase12->new(e => undef, d => undef, t => undef)->values], 276 [undef, "DEF", "DEF"]), 277 'Values for undef'); 278 279 ok(eq_array( 280 [Testcase12->new()->values], 281 ["DEF", "DEF", "DEF"]), 282 'Values for missing'); 283} 284 285# field initialiser expressions permit `goto` in do {} blocks 286{ 287 class Testcase13 { 288 field $forwards = do { goto HERE; HERE: 1 }; 289 field $backwards = do { my $x; HERE: ; goto HERE if !$x++; 2 }; 290 291 method values { return ($forwards, $backwards) } 292 } 293 294 ok(eq_array( 295 [Testcase13->new->values], 296 [1, 2], 297 'Values for goto inside do {} blocks in field initialisers')); 298} 299 300# field initialiser expressions permit a __CLASS__ 301{ 302 class Testcase14 { 303 field $classname = __CLASS__; 304 305 method classname { return $classname } 306 } 307 308 is(Testcase14->new->classname, "Testcase14", '__CLASS__ in field initialisers'); 309} 310 311done_testing; 312