use 5.008006;
use strict;
use warnings;

package Lexical::TypeTiny;

our $AUTHORITY = 'cpan:TOBYINK';
our $VERSION   = '0.002';
	
use Types::Standard qw(Any);
use Lexical::Types qw();
use Type::Registry qw();
use Import::Into qw();
use Variable::Magic qw(wizard cast);
use Carp qw();

sub import {
	my $me = shift;
	my $caller = caller;
	$me->setup_for($caller, @_);
}

sub setup_for {
	my $me = shift;
	my ($caller, @args) = @_;
	my %args = map { $_ => 1 } @args;
	
	my $reg = Type::Registry->for_class($caller);
	/\W/ or eval "package $_" for keys %$reg;
	
	if ($args{-nocheck}) {
		Lexical::Types->import::into($caller, as => sub { Any });
	}
	else {
		Lexical::Types->import::into($caller, as => sub { $reg->lookup($_[0]) });
	}
}

sub Type::Tiny::TYPEDSCALAR {
	my $type  =  $_[0];
	
	return if $type == Any;
	
	my $var   = \$_[1];
	my $check = $type->compiled_check;
	my $wiz   = wizard(
		set => sub {
			package # hide from PAUSE
				Type::Tiny;
			$check->(${$_[0]}) or Carp::croak($type->get_message(${$_[0]}));
		},
	);
	cast $_[1], $wiz;
}

1;

__END__

=pod

=encoding utf-8

=head1 NAME

Lexical::TypeTiny - my Int $n

=head1 SYNOPSIS

  use Types::Standard qw(Int);
  use Lexical::TypeTiny;
  
  my Int $n = 42;
  $n += 0.5;   # dies, not an Int

=head1 DESCRIPTION

Lexical::TypeTiny is similar in spirit to L<Type::Tie>, but:

=over

=item *

It's a lot faster because it uses L<Variable::Magic> instead of C<tie>.

=item *

It's limited to only scalar variables, no arrays or hashes.
(Of course, those scalars may be arrayrefs or hashrefs.)

=item *

Does not (currently) support coercion.

=item *

It's limited to simple type constraints like C<ArrayRef>, and not
parameterized type constraints like C<< ArrayRef[Int] >>. (This is
a limitation of the syntax Perl will parse, not a limitation of the
complexity of type constraints supported. You can define a 
C<ArrayRef_of_Int> type constraint in your own type library, and
it will work.)

=item *

Although an exception is thrown if you try to assign an invalid
value to the variable, the assignment still happens. In the L</SYNOPSIS>,
if you caught the exception and then examined C<< $n >>, it would be
42.5.

(This particular aspect of Lexical::TypeTiny's behaviour is not fixed
in stone and may change in a future version.)

=back

Because of the way Perl parses lexical variable types, if you wish to
declare, say C<< my Int $x >>, there needs to exist a class called
C<Int>. That class doesn't have to actually I<do> anything; it doesn't
need constructors, methods, etc.

Lexical::TypeTiny will create such classes for you at import time,
however to do so, it needs to know what type constraints you are planning
on using. This means B<< you need to import your type libraries before
importing Lexical::TypeTiny >>.

Good:

  use Types::Standard qw(Int);
  use Lexical::TypeTiny;

Bad:

  use Lexical::TypeTiny;
  use Types::Standard qw(Int);

=head2 Disabling Type Checks

  use Lexical::TypeTiny -nocheck;

=head1 BUGS

There currently seem to be issues with threaded Perls. Hopefully these can
be solved pretty soon.

Please report any bugs to
L<http://rt.cpan.org/Dist/Display.html?Queue=Lexical-TypeTiny>.

=head1 SEE ALSO

L<Type::Tie>, L<Types::Standard>.

=head1 AUTHOR

Toby Inkster E<lt>tobyink@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2018 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.