Skip to content

Fatal error during autoloading of complex inheritance chain with LSP checks since PHP 8.1.0 #20112

@janedbal

Description

@janedbal

Description

The following code:

<?php

$depth = 0;

spl_autoload_register(function ($class) use (&$depth) {
    $depth++;
    $indent = str_repeat('| ', $depth);
    echo $indent."$depth: $class\n";

    if ($class == 'IFace') {
        interface IFace {}
    } elseif ($class == 'IFaceImplementor') {
        class IFaceImplementor implements IFace {}
    } elseif ($class == 'InterfaceOfMainParent') {
        interface InterfaceOfMainParent
        {
            public function methodForSecondaryLspCheck(): IFace;
        }
    } elseif ($class == 'MainParent') {
        abstract class MainParent implements InterfaceOfMainParent
        {
            public function methodForSecondaryLspCheck(): IFaceImplementor {}
        }
    } elseif ($class == 'Intermediate') {
        abstract class Intermediate extends MainParent {}
    } elseif ($class == 'Child1') {
        class Child1 extends Intermediate {}
    } elseif ($class == 'Child2') {
        class Child2 extends Intermediate {}
    } elseif ($class == 'EntrypointParent') {
        abstract class EntrypointParent
        {
            abstract public function methodForLspCheck1(): MainParent;
            abstract public function methodForLspCheck2(): MainParent;
        }
    } elseif ($class == 'Entrypoint') {
        class Entrypoint extends EntrypointParent
        {
            public function methodForLspCheck1(): Child1 {}
            public function methodForLspCheck2(): Child2 {}
        }
    }

    $depth--;
});

class_exists(Entrypoint::class);

Resulted in this output:

| 1: Entrypoint
| | 2: EntrypointParent
| | 2: Child1
| | | 3: Intermediate
| | | | 4: MainParent
| | | | | 5: InterfaceOfMainParent
| | | | | 5: Child2

Fatal error: During inheritance of MainParent, while autoloading Child2: Uncaught Error: Class "Intermediate" not found in php-wasm run script:29
Stack trace:
#0 php-wasm run script(20): {closure}('Child2')
#1 php-wasm run script(25): {closure}('MainParent')
#2 php-wasm run script(27): {closure}('Intermediate')
#3 php-wasm run script(37): {closure}('Child1')
#4 [internal function]: {closure}('Entrypoint')
#5 php-wasm run script(47): class_exists('Entrypoint')
#6 {main} in php-wasm run script on line 20

But I expected this output instead:

| 1: Entrypoint
| | 2: EntrypointParent
| | 2: Child1
| | | 3: Intermediate
| | | | 4: MainParent
| | | | | 5: InterfaceOfMainParent
| | | | | 5: IFaceImplementor
| | | | | | 6: IFace
| | 2: Child2

PHP Version

PHP 8.5.0RC1 (cli) (built: Oct  8 2025 22:41:41) (NTS)
Copyright (c) The PHP Group
Built by https://github.com/docker-library/php
Zend Engine v4.5.0RC1, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.0RC1, Copyright (c), by Zend Technologies

Operating System

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions