The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use 5.016;

use mop;

class Foo is repr('HASH') {
    has $!attr = 'ATTR';

    method attr { $!attr }
    method foo  { 'FOO' }
    method bar  { 'BAR' }
}

{
    package Bar;
    use parent 'Foo';
    sub bar { 'BAZ' }
}

{
    my $bar = Bar->new;
    is($bar->attr, 'ATTR');
    is($bar->foo, 'FOO');
    is($bar->bar, 'BAZ');
}

{
    my $bar = Bar->new(attr => 'RTTA');
    is($bar->attr, 'RTTA');
    is($bar->foo, 'FOO');
    is($bar->bar, 'BAZ');
}

{
    package Baz;
    use parent 'Foo';
    sub bar { my $self = shift; $self->SUPER::bar . 'BAZ' }
}

{
    my $baz = Baz->new;
    is($baz->attr, 'ATTR');
    is($baz->foo, 'FOO');
    is($baz->bar, 'BARBAZ');
}

{
    my $baz = Baz->new(attr => 'RTTA');
    is($baz->attr, 'RTTA');
    is($baz->foo, 'FOO');
    is($baz->bar, 'BARBAZ');
}

{
    package Quux;
    use parent 'Foo';
    sub new {
        my $class = shift;
        my (%opts) = @_;

        my $self = $class->SUPER::new(%opts);
        $self->{extra} = $opts{extra} // 'EXTRA';

        return $self;
    }
    sub extra { $_[0]->{extra} }
}

{
    my $quux = Quux->new;
    is($quux->attr, 'ATTR');
    is($quux->extra, 'EXTRA');
}

{
    my $quux = Quux->new(attr => 'RTTA');
    is($quux->attr, 'RTTA');
    is($quux->extra, 'EXTRA');
}

{
    my $quux = Quux->new(extra => 'ARTXE');
    is($quux->attr, 'ATTR');
    is($quux->extra, 'ARTXE');
}

{
    my $quux = Quux->new(attr => 'RTTA', extra => 'ARTXE');
    is($quux->attr, 'RTTA');
    is($quux->extra, 'ARTXE');
}

class MyScalar is repr('SCALAR') {
    has $!attr is ro = 'MOP';
}

package MyScalar::Sub {
    use parent 'MyScalar';
    sub foo { my $self = shift; $$self = 'foo' }
}

{
    my $scalar = MyScalar::Sub->new;
    is($$scalar, undef);
    $scalar->foo;
    is($$scalar, 'foo');
    is($scalar->attr, 'MOP');
}

class MyArray is repr('ARRAY') {
    has $!attr is ro = 'MOP';
}

package MyArray::Sub {
    use parent 'MyArray';
    sub foo { my $self = shift; push @$self, @_ }
}

{
    my $array = MyArray::Sub->new;
    is_deeply([ @$array ], []);
    $array->foo(1, "foo");
    is_deeply([ @$array ], [1, "foo"]);
    $array->foo(2, "bar");
    is_deeply([ @$array ], [1, "foo", 2, "bar"]);
    is($array->attr, 'MOP');
}

class MyHash is repr('HASH') {
    has $!attr is ro = 'MOP';
}

package MyHash::Sub {
    use parent 'MyHash';
    sub foo { my $self = shift; $self->{$_[0]} = $_[1] }
}

{
    my $hash = MyHash::Sub->new;
    is_deeply({ %$hash }, {});
    $hash->foo('a', 1);
    is_deeply({ %$hash }, { a => 1 });
    $hash->foo('b', 2);
    is_deeply({ %$hash }, { a => 1, b => 2 });
    $hash->foo('a', 3);
    is_deeply({ %$hash }, { a => 3, b => 2 });
    is($hash->attr, 'MOP');
}

class MyGlob is repr('GLOB') {
    has $!attr is ro = 'MOP';
}

package MyGlob::Sub {
    use parent 'MyGlob';
    sub foo { my $self = shift; ${*$self}{$_[0]} = $_[1] }
}

{
    my $glob = MyGlob::Sub->new;
    is_deeply({ %{*$glob} }, {});
    $glob->foo('a', 1);
    is_deeply({ %{*$glob} }, { a => 1 });
    $glob->foo('b', 2);
    is_deeply({ %{*$glob} }, { a => 1, b => 2 });
    $glob->foo('a', 3);
    is_deeply({ %{*$glob} }, { a => 3, b => 2 });
    is($glob->attr, 'MOP');
}

class MyCustom is repr(sub { sub { state $x += $_[0] } }) {
    has $!attr is ro = 'MOP';
}

package MyCustom::Sub {
    use parent 'MyCustom';
    sub foo { my $self = shift; $self->($_[0]) }
}

{
    my $custom = MyCustom::Sub->new;
    is($custom->(0), 0);
    $custom->foo(1);
    is($custom->(0), 1);
    $custom->foo(2);
    is($custom->(0), 3);
    $custom->foo(3);
    is($custom->(0), 6);
    is($custom->attr, 'MOP');
}

eval '
class Error is repr("FOO") { }
';
like($@, qr/^unknown instance generator type FOO/);

eval '
class Error2 is repr([]) { }
';
like($@, qr/^unknown instance generator ARRAY\(/);

done_testing;