NAME

Class::Abstract - Stop code from making objects of a class that is only a template

VERSION

Version 0.03

SYNOPSIS

1. Make a class abstract (the usual way)

package Animal;
use parent 'Class::Abstract';    # Animal is now abstract

package Dog;
use parent -norequire, 'Animal'; # Dog is a normal (concrete) class

package main;
my $dog = Dog->new;              # works: you get a Dog object
my $bad = Animal->new;           # dies: "Cannot instantiate abstract class Animal directly"

2. Make a class abstract with "use"

package Vehicle;
use Class::Abstract;             # same result as: use parent 'Class::Abstract'

3. A child class that stores its own data

package Dog;
use parent -norequire, 'Animal';

sub new {
    my ($class, %args) = @_;
    my $self = $class->SUPER::new;   # the check happens here
    $self->{name} = $args{name};     # then add your own data
    return $self;
}

package main;
my $rex = Dog->new(name => 'Rex');

4. An abstract class that has its own new()

package Shape;
use parent 'Class::Abstract';

sub new {
    my ($class, %args) = @_;
    Class::Abstract::check_abstract($class);   # dies if $class is Shape
    return bless { colour => $args{colour} }, $class;
}

package Circle;
use parent -norequire, 'Shape';

package main;
my $c = Circle->new(colour => 'red');   # works
Shape->new(colour => 'red');            # dies

5. Ask whether a class is abstract

Animal->is_abstract;                        # 1
Dog->is_abstract;                           # 0
$rex->is_abstract;                          # 0 (asks about Dog)
Class::Abstract->is_abstract('Animal');     # 1
Class::Abstract->is_abstract($rex);         # 0

6. Make an object of an abstract class inside a test

use Test::More;

my $animal = do {
    local $Class::Abstract::BYPASS = 1;    # turn the check off for this block only
    Animal->new;
};
is( $animal->legs, 4, 'default number of legs' );

7. Use with Sub::Abstract

package Animal;
use parent 'Class::Abstract';      # nobody can make an Animal directly
use Sub::Abstract qw(speak);       # every child class must write speak()

DESCRIPTION

What this module does

An abstract class is a class that is only a template. It holds code that its child classes share, but it is not complete on its own. You should never make an object of it. You make objects of its child classes instead.

Perl has no built-in way to say "this class is abstract". This module adds one. When code tries to make an object of an abstract class, the program dies with a clear error message. Child classes work normally.

Terms used in this document

class, package

In Perl, a class is a package. Animal and Dog are package names.

parent class, child class, @ISA

Each package has a list called @ISA that names its parent classes. If @Dog::ISA contains Animal, then Dog is a child of Animal, and Dog can use Animal's methods.

abstract, concrete

A class is abstract when Class::Abstract is in its own @ISA. All other classes are concrete (normal).

invocant

The value on the left of the arrow in a method call. In Dog->new the invocant is the string 'Dog'. In $rex->speak the invocant is the object $rex.

croak, die

Stop the program (or the current eval block) with an error message.

bypass

A setting that turns the check off. See "Turning the check off".

test harness

The program that runs test files, for example prove or make test. It sets the environment variable HARNESS_ACTIVE.

How to make a class abstract

Put Class::Abstract in the class's own @ISA. There are two ways:

package Animal;
use parent 'Class::Abstract';    # way 1 (recommended)

package Vehicle;
use Class::Abstract;             # way 2

Both ways have the same result.

How the check works

Class::Abstract gives every class below it a method called new(). When new() runs, it looks at the invocant (the class name on the left of the arrow). If that class has Class::Abstract in its own @ISA, new() dies. If not, new() makes and returns an empty object.

This is why child classes work. When Dog calls $class->SUPER::new, the invocant is still 'Dog', not 'Animal'. Dog does not have Class::Abstract in its own @ISA, so the check passes.

Only the class that asks is abstract

Being abstract is not passed down to child classes. If you want an abstract class below another abstract class, list Class::Abstract in that class too:

package Animal;   use parent 'Class::Abstract';               # abstract
package Mammal;   use parent 'Class::Abstract', 'Animal';     # also abstract
package Dog;      use parent -norequire, 'Mammal';            # concrete

Turning the check off

Sometimes a test needs an object of an abstract class. Two settings turn the check off. If either one is on, the check is off.

1. The variable $Class::Abstract::BYPASS

Set it to a true value. Always use local, so the setting ends at the end of the block:

my $animal = do { local $Class::Abstract::BYPASS = 1; Animal->new };
2. The setting harness_bypass together with a test harness

If $Class::Abstract::config{harness_bypass} is true and the environment variable HARNESS_ACTIVE is true, the check is off. harness_bypass is false by default, so the check is on in your tests unless you change it.

$BYPASS is checked first. If $BYPASS is true, setting harness_bypass to 0 does not turn the check back on.

Changed in version 0.03: before 0.03, harness_bypass was 1 by default. That meant the check was off in every test suite. To get the old behaviour back, put this near the top of your test files:

$Class::Abstract::config{harness_bypass} = 1;

METHODS

import

use Class::Abstract;

Perl calls import() for you when you write use Class::Abstract;. You do not normally call it yourself.

It makes the package that contains the use line abstract. It does this by adding 'Class::Abstract' to that package's @ISA. The result is the same as use parent 'Class::Abstract'.

ARGUMENTS

None that you pass yourself. Perl passes the string 'Class::Abstract' as the first argument. Anything in an import list (for example use Class::Abstract qw(x y)) is ignored.

RETURNS

The string 'Class::Abstract'. use throws this value away, so you only see it if you call Class::Abstract->import yourself.

SIDE EFFECTS

Adds 'Class::Abstract' to the end of the calling package's @ISA. It does nothing in these cases:

  • 'Class::Abstract' is already in that @ISA. So writing use Class::Abstract; twice is safe.

  • The calling package is Class::Abstract itself. A class cannot be its own parent.

  • Perl cannot tell which package is calling (this can happen in unusual code, such as a BEGIN block inside a string eval). Without this rule, the module would change @main::ISA by mistake.

It does not change the global variables $_, $@ or $!, and it does not touch an alarm() timer. (Before version 0.03 it cleared $@.)

EXAMPLE

package Vehicle;
use Class::Abstract;          # @Vehicle::ISA is now ('Class::Abstract')
use Class::Abstract;          # no change: it is already there

package Car;
our @ISA = ('Vehicle');       # Car is concrete

package main;
my $car = Car->new;           # works
Vehicle->new;                 # dies

To load the module without making the current package abstract, pass an empty list. Perl then does not call import():

use Class::Abstract ();       # main is NOT made abstract

API SPECIFICATION

Input

{
    class => { type => 'string', optional => 0 },   # always 'Class::Abstract'
}

Output

{
    type => 'string',
}

MESSAGES

None. import() never dies and never warns.

new

my $obj  = ConcreteClass->new;
my $obj  = ConcreteClass->new(name => 'Rex');   # arguments are ignored
my $copy = $obj->new;                           # new, empty object of the same class

Makes a new, empty object, but only for a concrete class.

  • If the class is abstract, new() dies with an error message.

  • If the class is concrete, new() returns an empty hash reference blessed into that class.

Child classes usually call this through $class->SUPER::new from their own new(). That works because the check looks at the class name on the left of the arrow (the child), not at the class where new() is written.

ARGUMENTS

$invocant (required)

The class name or object on the left of the arrow. In Dog->new it is the string 'Dog'. If it is an object, the object's class is used. It must not be undef, the empty string, or a reference that is not an object (such as [] or {}).

%args (optional)

Any other arguments. They are accepted and ignored. This lets a child class pass its arguments to SUPER::new without errors. The child class must store its own data after SUPER::new returns.

RETURNS

A new, empty hash reference, blessed into the class.

SIDE EFFECTS

None, except that bless creates the package in Perl's symbol table if it did not exist yet. (The check itself never creates a package.)

It does not change the global variables $_, $@ or $!, and it does not touch an alarm() timer.

EXAMPLE

package Animal;
use parent 'Class::Abstract';

package Dog;
our @ISA = ('Animal');

sub new {
    my ($class, %args) = @_;
    my $self = $class->SUPER::new;   # the check happens here
    $self->{name} = $args{name};
    return $self;
}

package main;
my $rex = Dog->new(name => 'Rex');   # works: Dog is concrete

eval { Animal->new };                # dies: Animal is abstract
print $@;   # Cannot instantiate abstract class Animal directly at ...

API SPECIFICATION

Input

{
    invocant => {
        type     => 'string|object',   # class name, or an object of that class
        optional => 0,
        min      => 1,                 # a name must not be empty
    },
}

Output

{
    type => 'object',                  # a hashref blessed into the class
}

MESSAGES

Message                                   What it means / What to do
-------                                   --------------------------
Cannot instantiate abstract class CLASS   CLASS is abstract and no bypass is on.
  directly                                Make an object of a child class instead.

new() invocant must be a class name or    new() was called as a plain function
  blessed object, got TYPE                with a reference that is not an object
                                          (TYPE is ARRAY, HASH, CODE, ...).
                                          Call it as Class->new or $obj->new.

new() requires a defined class name as    new() was called as a plain function
  invocant                                with undef or ''.  Pass a class name.

check_abstract

Class::Abstract::check_abstract($class);

Runs the same check as new(), but does not make an object.

Use it when your abstract class writes its own new() and that new() does not call $class->SUPER::new. In that case Class::Abstract::new() never runs, so nothing checks the class. Call check_abstract() as the first line of your new() to put the check back.

ARGUMENTS

$invocant (required)

The class to check: a class name (a string) or an object. If it is an object, the object's class is used. In your own new(), pass the $class variable. It must not be undef, the empty string, or a reference that is not an object.

RETURNS

Nothing if the class is concrete or a bypass is on: undef in scalar context, or an empty list in list context. Otherwise it dies.

SIDE EFFECTS

None. It obeys $BYPASS and harness_bypass in exactly the same way as new().

It does not change the global variables $_, $@ or $!, and it does not touch an alarm() timer.

EXAMPLE

package Shape;
use parent 'Class::Abstract';

sub new {
    my ($class, %args) = @_;
    Class::Abstract::check_abstract($class);   # dies if $class is Shape
    return bless { sides => $args{sides} }, $class;
}

package Square;
our @ISA = ('Shape');

package main;
my $sq = Square->new(sides => 4);    # works
Shape->new(sides => 4);              # dies: Shape is abstract

API SPECIFICATION

Input

{
    invocant => {
        type     => 'string|object',
        optional => 0,
        min      => 1,
    },
}

Output

{
    type => 'undef',
}

MESSAGES

Message                                   What it means / What to do
-------                                   --------------------------
Cannot instantiate abstract class CLASS   The same as for new().
  directly

check_abstract() requires a class name    The argument was a reference that is
  or blessed object, got TYPE             not an object.  Pass $class or $self.

check_abstract() requires a defined       The argument was undef or ''.  This
  class name                              usually means "my $class = shift;"
                                          is missing from your new().

is_abstract

my $yes_or_no = SomeClass->is_abstract;
my $yes_or_no = $obj->is_abstract;
my $yes_or_no = Class::Abstract->is_abstract('SomeClass');
my $yes_or_no = Class::Abstract->is_abstract($obj);

Tells you whether a class is abstract. It returns 1 (yes) or 0 (no).

A class is abstract only when Class::Abstract is in its own @ISA, or when the class is Class::Abstract. A child of an abstract class is not abstract, so is_abstract() returns 0 for it.

Every class below Class::Abstract can call this method.

ARGUMENTS

$self

Required. The class name or object on the left of the arrow. If no target is given (see below), this is the class that is checked.

$target (optional)

A class name or an object to check instead of the invocant. This is how you ask about any class: Class::Abstract->is_abstract('Dog'). If $target is undef, it is treated as "not given".

RETURNS

The integer 1 if the class is abstract, otherwise the integer 0.

A class name that does not exist (for example, a spelling mistake) gives 0, not an error.

SIDE EFFECTS

None. Asking about a class that does not exist does not create it.

It does not change the global variables $_, $@ or $!, and it does not touch an alarm() timer.

EXAMPLE

Animal->is_abstract;                         # 1
Dog->is_abstract;                            # 0

my $rex = Dog->new(name => 'Rex');
$rex->is_abstract;                           # 0 (asks about Dog)

Class::Abstract->is_abstract('Animal');      # 1
Class::Abstract->is_abstract($rex);          # 0 (asks about Dog)
Class::Abstract->is_abstract('Anmial');      # 0 (spelling mistake: no such class)

API SPECIFICATION

Input

{
    self   => { type => 'string|object', optional => 0, min => 1 },
    target => { type => 'string|object', optional => 1, min => 1 },
}

Output

{
    type => 'integer',
    min  => 0,
    max  => 1,
}

MESSAGES

Message                                   What it means / What to do
-------                                   --------------------------
is_abstract() requires a class name or    The class to check was a reference
  object invocant, got TYPE               that is not an object.

is_abstract() requires a class name or    The class to check was undef or ''.
  object invocant

PUBLIC VARIABLES

$Class::Abstract::BYPASS

Default: 0.

When this is true, the check is off for every class. Always change it with local, so that it goes back to its old value at the end of the block:

{
    local $Class::Abstract::BYPASS = 1;
    my $animal = Animal->new;    # works, even though Animal is abstract
}
Animal->new;                     # dies again

Any true value turns the check off. This includes strings that look false to a person, such as "false", "no" and "0E0".

%Class::Abstract::config

Settings for the module. It has one key.

harness_bypass

Default: 0.

When this is true and the environment variable HARNESS_ACTIVE is true, the check is off. HARNESS_ACTIVE is set by test harnesses such as prove and make test.

The default was 1 before version 0.03. Set it to 1 to get the old behaviour back.

ERROR MESSAGES

This is the full list of messages that the module can produce. The text is always plain English. It does not change with the locale. Each message ends with the file and line number of your code, for example at script.pl line 12.

Message                                                    Comes from
-------                                                    ----------
Cannot instantiate abstract class CLASS directly           new, check_abstract
new() invocant must be a class name or blessed object,     new
  got TYPE
new() requires a defined class name as invocant            new
check_abstract() requires a class name or blessed object,  check_abstract
  got TYPE
check_abstract() requires a defined class name             check_abstract
is_abstract() requires a class name or object invocant,    is_abstract
  got TYPE
is_abstract() requires a class name or object invocant     is_abstract
Class::Abstract internal error: unknown message key KEY    (a bug in this
                                                            module; please
                                                            report it)

CLASS is the class name. TYPE is the kind of reference, such as ARRAY, HASH or CODE.

COMMON PITFALLS

Your own new() skips the check

If an abstract class writes its own new() and creates the object with bless, Class::Abstract::new() never runs. Nothing stops Shape->new. Fix: call Class::Abstract::check_abstract($class) on the first line, or build the object with $class->SUPER::new.

The order of parents matters

Perl uses the first new() it finds, reading @ISA from left to right. In use parent 'Animal', 'Class::Abstract', Perl finds Animal::new first, if Animal has one. If that new() does not call check_abstract(), there is no check.

Child classes are not abstract automatically

Mammal below the abstract class Animal is concrete unless Mammal also lists Class::Abstract in its own @ISA.

"use Class::Abstract;" changes the current package

use Class::Abstract; makes the package it is written in abstract. If you write it in a script only to call is_abstract(), the main package becomes abstract. Write use Class::Abstract (); instead. With the empty list, Perl does not call import(). The same is true for require Class::Abstract;: it does not make any class abstract.

undef and empty strings

Class::Abstract::new(undef), new('') and the same calls to check_abstract() and is_abstract() die. But an undef target in $obj->is_abstract(undef) is treated as "no target", so the invocant is checked.

Spelling mistakes give 0, not an error

Class::Abstract->is_abstract('Anmial') returns 0, because no package with that name has Class::Abstract in its @ISA. Check the spelling if you get 0 when you expect 1.

A bypass without "local" never ends

$Class::Abstract::BYPASS = 1; without local turns the check off for the rest of the program, in every file.

Strings that look false are true

$BYPASS = 'false' and $BYPASS = 'no' turn the check off, because Perl treats every non-empty string except '0' as true. Use 1 and 0.

$BYPASS wins over harness_bypass

If $BYPASS is true, setting harness_bypass to 0 does not turn the check on. To be sure the check is on, set both:

local $Class::Abstract::BYPASS                 = 0;
local $Class::Abstract::config{harness_bypass} = 0;
Tests that passed before 0.03 may now die

Before version 0.03 the check was off inside a test harness. If your tests make objects of abstract classes, they will now die. See "Turning the check off".

isa() is not the same as is_abstract()

Dog->isa('Class::Abstract') is true, because Class::Abstract is a grandparent of Dog. Use Dog->is_abstract to ask whether Dog itself is abstract.

new() throws away its arguments

Dog->SUPER::new(name => 'Rex') does not store name. Store it yourself after SUPER::new returns.

Objects made during a bypass stay abstract

An object made while the bypass was on still belongs to the abstract class. Calling $obj->new on it later, with the check on, dies.

ENCODING

The module does not read files, the terminal or the network. The only text it handles is class names, and the error messages that contain them.

Class names

Class names may contain any characters that Perl allows in a package name. This includes non-ASCII letters (Café, Japanese, Chinese) and, for names made with bless or symbolic references, emoji. The module compares names exactly, character by character.

Two conditions apply:

  • Non-ASCII package names need Perl 5.16 or later. The rest of the module works on Perl 5.8.

  • The name must be a character string, not raw UTF-8 bytes. Names written in source code under use utf8; are character strings. Names read from a file or the network must be decoded first (for example with "decode" in Encode). A name that is still raw bytes is a different name to Perl, so is_abstract() returns 0 for it.

Error messages

The fixed text of every message is plain ASCII English. A message also contains the class name, so it can contain non-ASCII characters. If you print such a message to a file handle that has no encoding layer, Perl warns Wide character in print. Add a layer to avoid this:

binmode STDERR, ':encoding(UTF-8)';
Settings

$BYPASS, harness_bypass and HARNESS_ACTIVE are only tested for true or false. Their text is never used.

LIMITATIONS

Only the check in new() and check_abstract() protects a class

Code can still make an object of an abstract class with bless {}, 'Animal', with "dclone" in Storable, with thaw, or with a new() that forgets to call check_abstract(). The check is a rule that the module follows when it is asked. It is not built into Perl.

can('new') returns a method that dies

Animal->can('new') returns \&Class::Abstract::new. This looks like "yes, you can call new()". You can call it, but it dies.

The bypass is global

$BYPASS turns the check off for every class, not only for the class that a test is about.

The old package separator is not supported

Perl once allowed ' between parts of a package name (Foo'Bar meaning Foo::Bar). This module does not convert ' to ::, so such names are treated as concrete.

Threads

The module shares no data between threads, except $BYPASS and %config, which are normally only read. import() changes the caller's @ISA, usually when the program is compiled. This is safe if modules are not loaded with require from several threads at once.

Not for Moo or Moose

Moo and Moose have their own ways to make abstract classes and required methods. This module is for plain Perl classes.

DEPENDENCIES

Carp (part of Perl), Scalar::Util (part of Perl), Readonly, Return::Set.

SEE ALSO

  • Test Dashboard

  • Sub::Abstract

    Makes child classes write certain methods. Use it together with Class::Abstract for a complete abstract base class.

  • Sub::Private

    Makes a method callable only from its own class.

  • Sub::Protected

    Makes a method callable only from its own class and its child classes.

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

FORMAL SPECIFICATION

This section describes the module in the Z notation. You do not need to read it to use the module.

Definitions

Package == seq CHAR
[Object, Ref]                     -- objects and references; Object ⊆ Ref

┌─ Registry ────────────────────────────────────────────
│ bypass         : 𝔹
│ harness_bypass : 𝔹
│ HARNESS_ACTIVE : 𝔹
│ ISA            : Package ⇸ seq Package
└───────────────────────────────────────────────────────

┌─ InitRegistry ────────────────────────────────────────
│ Registry'
├───────────────────────────────────────────────────────
│ bypass' = false ∧ harness_bypass' = false
└───────────────────────────────────────────────────────

bypass_active ⇔ bypass ∨ (harness_bypass ∧ HARNESS_ACTIVE)

resolve : (Package ∪ Object) ⇸ Package
resolve(x) = class_of(x)  if x ∈ Object
           = x            if x ∈ Package ∧ #x > 0

is_direct_abstract : Package → 𝔹
is_direct_abstract(c) ⇔ c = 'Class::Abstract'
                      ∨ (c ∈ dom ISA ∧ 'Class::Abstract' ∈ ran ISA(c))

┌─ BadInvocant ─────────────────────────────────────────
│ ΞRegistry
│ inv? : Package ∪ Ref ∪ {⊥}
├───────────────────────────────────────────────────────
│ inv? ∈ Ref ∖ Object ∨ inv? = ⊥ ∨ inv? = ''
│ croak(message for the calling routine)
└───────────────────────────────────────────────────────

┌─ NewAbstract ─────────────────────────────────────────
│ ΞRegistry
│ inv? : Package ∪ Object
├───────────────────────────────────────────────────────
│ is_direct_abstract(resolve(inv?)) ∧ ¬bypass_active
│ croak('Cannot instantiate abstract class ' ⁀ resolve(inv?) ⁀ ' directly')
└───────────────────────────────────────────────────────

import

┌─ Import ──────────────────────────────────────────────
│ ΔRegistry
│ class?  : Package
│ caller? : Package ∪ {⊥}
│ result! : Package
├───────────────────────────────────────────────────────
│ registrable ⇔ caller? ≠ ⊥ ∧ #caller? > 0
│               ∧ caller? ≠ 'Class::Abstract'
│               ∧ ¬is_direct_abstract(caller?)
│ registrable ⇒ ISA'(caller?) = ISA(caller?) ⁀ ⟨'Class::Abstract'⟩
│ ¬registrable ⇒ ISA' = ISA
│ bypass' = bypass ∧ harness_bypass' = harness_bypass
│ result! = class?
└───────────────────────────────────────────────────────

new

┌─ New ─────────────────────────────────────────────────
│ ΞRegistry
│ inv?    : Package ∪ Object
│ result! : Object
├───────────────────────────────────────────────────────
│ ¬is_direct_abstract(resolve(inv?)) ∨ bypass_active
│ class_of(result!) = resolve(inv?)
│ result! is a new, empty hash
└───────────────────────────────────────────────────────

NewOp ≙ New ∨ NewAbstract ∨ BadInvocant

check_abstract

┌─ CheckAbstract ───────────────────────────────────────
│ ΞRegistry
│ inv?    : Package ∪ Object
│ result! : {⊥}
├───────────────────────────────────────────────────────
│ ¬is_direct_abstract(resolve(inv?)) ∨ bypass_active
│ result! = ⊥
└───────────────────────────────────────────────────────

CheckAbstractOp ≙ CheckAbstract ∨ NewAbstract ∨ BadInvocant

is_abstract

┌─ IsAbstract ──────────────────────────────────────────
│ ΞRegistry
│ self?   : Package ∪ Object
│ target? : Package ∪ Object ∪ {⊥}
│ result! : {0, 1}
├───────────────────────────────────────────────────────
│ c = resolve(if target? = ⊥ then self? else target?)
│ result! = 1 ⇔ is_direct_abstract(c)
└───────────────────────────────────────────────────────

IsAbstractOp ≙ IsAbstract ∨ BadInvocant

-- ΞRegistry in New, CheckAbstract and IsAbstract means that
-- asking about a package never creates it or changes any @ISA.

STATE DIAGRAM

The module has two independent kinds of state: the state of each class, and the global check mode.

Class states

Every package is in exactly one of these states:

MISSING    The package does not exist yet.
PLAIN      The package exists.  Class::Abstract is not one of its
           ancestors, so it has no new() from this module.
ABSTRACT   'Class::Abstract' is in the package's own @ISA.
CONCRETE   The package inherits from an abstract class, but
           'Class::Abstract' is not in its own @ISA.

Transitions:

                   use Class::Abstract;
                   use parent 'Class::Abstract';
                   push @P::ISA, 'Class::Abstract'
    +---------+  ------------------------------->  +----------+
    | MISSING |                                    |          |
    +---------+                                    |          |
      |     |                                      |          |
      |     | Class::Abstract::new('P')            |          |
      |     | (bless creates P)                    | ABSTRACT |
      |     v                                      |          |
      |  +-------+   same triggers as above        |          |
      |  | PLAIN | ------------------------------> |          |
      |  |       | <------------------------------ |          |
      |  +-------+   remove 'Class::Abstract'      +----------+
      |     |        from @P::ISA (P->new now          ^   |
      |     |        fails: "Can't locate object       |   | remove
      |     |        method new")                      |   | 'Class::Abstract'
      |     | @P::ISA = ('Animal')                     |   | from @P::ISA
      |     | (Animal is ABSTRACT or CONCRETE)         |   | (an abstract
      |     v                                    add   |   | parent remains)
      |  +----------+  ----------------------------->--+   |
      +->| CONCRETE |   use parent 'Class::Abstract'       |
         |          |  <-----------------------------------+
         +----------+

@P::ISA = ('Animal') also moves MISSING directly to CONCRETE.

Calls that never change a class's state:

P->is_abstract, Class::Abstract->is_abstract('P')   any state  -> same state
Class::Abstract::check_abstract('P')                any state  -> same state
use Class::Abstract;  (again, in P)                 ABSTRACT   -> ABSTRACT

Check mode

+-----------+   local $Class::Abstract::BYPASS = 1               +----------+
|           | -------------------------------------------------> |          |
| ENFORCING |   $config{harness_bypass} = 1 while HARNESS_ACTIVE | BYPASSED |
|           |   HARNESS_ACTIVE set while harness_bypass is 1     |          |
|           | <------------------------------------------------- |          |
+-----------+   end of the block with "local"                    +----------+
                $BYPASS false AND (harness_bypass false
                OR HARNESS_ACTIVE false)

Start state: ENFORCING  ($BYPASS = 0, harness_bypass = 0).

What new() and check_abstract() do in each state

Class state  Check mode  P->new                       check_abstract('P')
-----------  ----------  ---------------------------  -------------------
ABSTRACT     ENFORCING   dies: "Cannot instantiate    dies (same message)
                         abstract class P directly"
ABSTRACT     BYPASSED    returns an object of P       returns undef
CONCRETE     either      returns an object of P       returns undef
PLAIN        either      dies: "Can't locate object   returns undef
                         method new" (Perl error)
MISSING      either      dies: "Can't locate object   returns undef
                         method new" (Perl error)

No call changes the check mode except the assignments shown above.

LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms. If you use it, please let me know.