package Devel::Canary;

use 5.018;
use strict;
use warnings;
use autodie;

use Carp          qw(confess);
use Scalar::Util  qw(refaddr reftype);
use PadWalker     qw(peek_my);

$Carp::CarpInternal{'Devel::Canary'}++;
$Carp::CarpInternal{'Devel::Canary::Hash'}++;

sub new_hashref {
    my $ref = {};
    tie %$ref, 'Devel::Canary::Hash', $ref;
    return $ref;
}

sub throw_error {
    my($self, $format) = @_;

    my $var_name = '$devel_canary';
    my $pad = peek_my(2);
    foreach my $key (keys %$pad) {
        my $var_ref = $pad->{$key};
        next unless reftype($var_ref) eq 'REF';
        if(my $addr = refaddr($$var_ref)) {
            if($addr == $$self) {
                $var_name = $key;
                last;
            }
        }
    }
    confess sprintf("Fatal attempt to $format", $var_name);
}


package Devel::Canary::Hash;

use parent 'Devel::Canary';

sub TIEHASH {
    my($class, $ref) = @_;
    my $addr = Scalar::Util::refaddr($ref);
    return bless \$addr, $class;
}

sub FETCH    { shift->throw_error('access %s->{'      . shift . '}'); }
sub STORE    { shift->throw_error('assign to %s->{'   . shift . '}'); }
sub EXISTS   { shift->throw_error('call exists %s->{' . shift . '}'); }
sub DELETE   { shift->throw_error('delete %s->{'      . shift . '}'); }
sub CLEAR    { shift->throw_error('clear hashref %s'); }
sub FIRSTKEY { shift->throw_error('iterate hashref %s'); }

1;

