sub _generate_set {
my ($self, $name, $value, $spec) = @_;
my $simple = $self->_generate_simple_set($name, $value);
- if (my $trigger = $spec->{trigger}) {
- my $value = '$value';
+ my ($trigger, $isa_check) = @{$spec}{qw(trigger isa)};
+ return $simple unless $trigger or $isa_check;
+ my $code = 'do {';
+ if ($isa_check) {
+ $code .= ' '.$self->_generate_isa_check($name, '$_[1]', $isa_check).';';
+ }
+ if ($trigger) {
my $fire = $self->_generate_trigger($name, '$_[0]', '$value', $trigger);
- return 'do { '
- .'my $value = '.$simple.'; '.$fire.'; '
- .'$value }'
- ;
+ $code .=
+ ' my $value = '.$simple.'; '.$fire.'; '
+ .'$value';
+ } else {
+ $code .= ' '.$simple;
}
- return $simple;
+ $code .= ' }';
+ return $code;
}
sub generate_trigger {
sub _generate_trigger {
my ($self, $name, $obj, $value, $trigger) = @_;
- if (my $quoted = quoted_from_sub($trigger)) {
+ $self->_generate_call_code($name, 'trigger', "${obj}, ${value}", $trigger);
+}
+
+sub generate_isa_check {
+ my $self = shift;
+ local $self->{captures} = {};
+ my $code = $self->_generate_isa_check(@_);
+ return ($code, $self->{captures});
+}
+
+sub _generate_isa_check {
+ my ($self, $name, $value, $check) = @_;
+ $self->_generate_call_code($name, 'isa_check', $value, $check);
+}
+
+sub _generate_call_code {
+ my ($self, $name, $type, $values, $sub) = @_;
+ if (my $quoted = quoted_from_sub($sub)) {
my $code = $quoted->[1];
- my $at_ = 'local @_ = ('.join(', ', $obj, $value).');';
+ my $at_ = 'local @_ = ('.$values.');';
if (my $captures = $quoted->[2]) {
- my $cap_name = qq{\$trigger_captures_for_${name}};
+ my $cap_name = qq{\$${type}_captures_for_${name}};
$self->{captures}->{$cap_name} = \$captures;
return "do {\n".' '.$at_."\n"
.Sub::Quote::capture_unroll($cap_name, $captures, 6)
." ${code}\n }";
}
- return 'do { local @_ = ('.join(', ', $obj, $value).'); '.$code.' }';
+ return 'do { local @_ = ('.$values.'); '.$code.' }';
}
- my $cap_name = qq{\$trigger_for_${name}};
- $self->{captures}->{$cap_name} = \$trigger;
- return "${cap_name}->(${obj}, ${value})";
+ my $cap_name = qq{\$${type}_for_${name}};
+ $self->{captures}->{$cap_name} = \$sub;
+ return "${cap_name}->(${values})";
}
sub _generate_simple_set {
my $body = ' my $class = shift;'."\n";
$body .= $self->_generate_args;
$body .= $self->_check_required($spec);
+ $body .= $self->_check_isa($spec);
$body .= ' my $new = bless({}, $class);'."\n";
$body .= $self->_assign_new($spec);
$body .= $self->_fire_triggers($spec);
my @required_init =
map $spec->{$_}{init_arg},
grep $spec->{$_}{required},
- keys %$spec;
+ sort keys %$spec;
return '' unless @required_init;
' if (my @missing = grep !exists $args->{$_}, qw('
.join(' ',@required_init).')) {'."\n"
." }\n";
}
+sub _check_isa {
+ my ($self, $spec) = @_;
+ my $acc = $self->accessor_generator;
+ my $captures = $self->{captures};
+ my $check = '';
+ foreach my $name (sort keys %$spec) {
+ my ($init, $isa) = @{$spec->{$name}}{qw(init_arg isa)};
+ next unless $init and $isa;
+ my $init_str = perlstring($init);
+ my ($code, $add_captures) = $acc->generate_isa_check(
+ $name, "\$args->{${init_str}}", $isa
+ );
+ @{$captures}{keys %$add_captures} = values %$add_captures;
+ $check .= " ${code} if exists \$args->{${init_str}};\n";
+ }
+ return $check;
+}
+
sub _fire_triggers {
my ($self, $spec) = @_;
- my @fire = map {
- [ $_, $spec->{$_}{init_arg}, $spec->{$_}{trigger} ]
- } grep { $spec->{$_}{init_arg} && $spec->{$_}{trigger} } keys %$spec;
my $acc = $self->accessor_generator;
my $captures = $self->{captures};
my $fire = '';
- foreach my $name (keys %$spec) {
+ foreach my $name (sort keys %$spec) {
my ($init, $trigger) = @{$spec->{$name}}{qw(init_arg trigger)};
next unless $init && $trigger;
my ($code, $add_captures) = $acc->generate_trigger(
sub _unquote_all_outstanding {
return unless %QUOTE_OUTSTANDING;
my ($assembled_code, @assembled_captures, @localize_these) = '';
- foreach my $outstanding (keys %QUOTE_OUTSTANDING) {
+ # we sort the keys in order to make debugging more predictable
+ foreach my $outstanding (sort keys %QUOTE_OUTSTANDING) {
my ($name, $code, $captures) = @{$QUOTE_OUTSTANDING{$outstanding}};
push @localize_these, $name if $name;
sub quoted_from_sub {
my ($sub) = @_;
- $QUOTED{$sub};
+ $QUOTED{$sub||''};
}
sub unquote_sub {
--- /dev/null
+use strictures 1;
+use Test::More;
+use Test::Fatal;
+
+sub run_for {
+ my $class = shift;
+
+ my $obj = $class->new(less_than_three => 1);
+
+ is($obj->less_than_three, 1, 'initial value set');
+
+ like(
+ exception { $obj->less_than_three(4) },
+ qr/4 is not less than three/, 'exception thrown on bad set'
+ );
+
+ is($obj->less_than_three, 1, 'initial value remains after bad set');
+
+ my $ret;
+
+ is(
+ exception { $ret = $obj->less_than_three(2) },
+ undef, 'no exception on correct set'
+ );
+
+ is($ret, 2, 'correct setter return');
+ is($obj->less_than_three, 2, 'correct getter return');
+
+ is(exception { $class->new }, undef, 'no exception with no value');
+ like(
+ exception { $class->new(less_than_three => 12) },
+ qr/12 is not less than three/, 'exception thrown on bad constructor arg'
+ );
+}
+
+{
+ package Foo;
+
+ use Class::Tiny;
+
+ has less_than_three => (
+ is => 'rw',
+ isa => sub { die "$_[0] is not less than three" unless $_[0] < 3 }
+ );
+}
+
+run_for 'Foo';
+
+{
+ package Bar;
+
+ use Sub::Quote;
+ use Class::Tiny;
+
+ has less_than_three => (
+ is => 'rw',
+ isa => quote_sub q{ die "$_[0] is not less than three" unless $_[0] < 3 }
+ );
+}
+
+run_for 'Bar';
+
+{
+ package Baz;
+
+ use Sub::Quote;
+ use Class::Tiny;
+
+ has less_than_three => (
+ is => 'rw',
+ isa => quote_sub(
+ q{ die "$_[0] is not less than ${word}" unless $_[0] < $limit },
+ { '$limit' => \3, '$word' => \'three' }
+ )
+ );
+}
+
+run_for 'Baz';
+
+done_testing;