← All results

@psalm-seal-methods / @no-seal-methods

Group: Advanced PHPDoc types · Category: ecosystem · File: phpdoc_advanced_psalm_seal_methods.php

The positive @psalm-seal-methods cannot be observed on its own: Psalm seals magic methods by default (sealAllMethods="true"), and PHPStan / Mago / phpy likewise distrust a __call method that has no matching @method tag. So a call to an undeclared magic method is rejected *with or without* the seal tag, and that rejection proves nothing about whether the tag was read. The lever that actually discriminates is the inverse tag @psalm-no-seal-methods: it *lifts* the default seal, so an undeclared magic call becomes allowed. Only a tool that models the seal machinery goes silent there; a tool that ignores the tag keeps its default diagnostic. That silence is the real signal, so it is scored with a quiet probe on the origin tools. References: - Psalm supported_annotations.md: @psalm-seal-methods, @psalm-no-seal-methods - Psalm configuration.md: sealAllMethods (default true)

Analyzer results

AnalyzerVersionResultDiagnostics
phan 6.0.7Phan 6.0.7 php-ast version 1.1.3 PHP version used to run Phan: 8.5.9 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
phpstan 2.2.8PHPStan - PHP Static Analysis Tool 2.2.8 Recognized (no probes) reported Lv.2+
phpdoc_advanced_psalm_seal_methods.php:72: Call to an undefined method Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Sealed::wave(). [identifier=method.notFound] [reported-from-level=2]
phpdoc_advanced_psalm_seal_methods.php:81: Call to an undefined method Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Unsealed::wave(). [identifier=method.notFound] [reported-from-level=2]
With strict-rules
phpdoc_advanced_psalm_seal_methods.php:72: Call to an undefined method Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Sealed::wave(). [identifier=method.notFound]
phpdoc_advanced_psalm_seal_methods.php:81: Call to an undefined method Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Unsealed::wave(). [identifier=method.notFound]
  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
psalm 6.16.1Psalm 6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac
next: 7.0.0-beta19
Enforced
phpdoc_advanced_psalm_seal_methods.php:72: Magic method Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Sealed::wave does not exist [UndefinedMagicMethod]
  • Recognition: spelling resolved
  • Enforcement: 1/1 of the expected violations reported
mago 1.46.0mago 1.46.0 Recognized (no probes)
phpdoc_advanced_psalm_seal_methods.php:72: Ambiguous method call to `wave` on class `Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Sealed`. [non-documented-method]
phpdoc_advanced_psalm_seal_methods.php:81: Ambiguous method call to `wave` on class `Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Unsealed`. [non-documented-method]
  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
mir 0.70.1mir 0.70.1 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
phpantom 0.9.0phpantom_lsp 0.9.0 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
intelephense 1.18.5intelephense 1.18.5 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
phpy 0.2.0phpy 0.2.0 Recognized (no probes)
phpdoc_advanced_psalm_seal_methods.php:72: Method Sealed::wave() is not defined. The call is passed to the magic Sealed::__call() method.
phpdoc_advanced_psalm_seal_methods.php:81: Method Unsealed::wave() is not defined. The call is passed to the magic Unsealed::__call() method.
  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
qodana 262.8665.325Qodana 262.8665.325 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
noverify 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 Recognized (no probes)
phpdoc_advanced_psalm_seal_methods.php:40: param $arguments miss matched with phpdoc type <<list<mixed>>> [funcParamTypeMissMatch]
phpdoc_advanced_psalm_seal_methods.php:60: param $arguments miss matched with phpdoc type <<list<mixed>>> [funcParamTypeMissMatch]
  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
steins 0.1.5steins 0.1.5 (2026-08-12 revision 39b4cc1) Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
phpstan-strict 2.2.8PHPStan - PHP Static Analysis Tool 2.2.8 Recognized (no probes)
phpdoc_advanced_psalm_seal_methods.php:72: Call to an undefined method Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Sealed::wave(). [identifier=method.notFound]
phpdoc_advanced_psalm_seal_methods.php:81: Call to an undefined method Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Unsealed::wave(). [identifier=method.notFound]
  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
psalm-next 7.0.0-beta19Psalm 7.0.0-beta19@7e751c06a756fa64dc4c759c09fe4a173afcb433 Enforced
phpdoc_advanced_psalm_seal_methods.php:72: Magic method Conformance\Tests\PhpdocAdvancedPsalmSealMethods\Sealed::wave does not exist [UndefinedMagicMethod]
  • Recognition: spelling resolved
  • Enforcement: 1/1 of the expected violations reported

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\PhpdocAdvancedPsalmSealMethods;

/**
 * Cross-tool handling of `@psalm-seal-methods` / `@no-seal-methods`.
 *
 * The positive `@psalm-seal-methods` cannot be observed on its own: Psalm seals
 * magic methods by default (`sealAllMethods="true"`), and PHPStan / Mago / phpy
 * likewise distrust a `__call` method that has no matching `@method` tag. So a
 * call to an undeclared magic method is rejected *with or without* the seal tag,
 * and that rejection proves nothing about whether the tag was read.
 *
 * The lever that actually discriminates is the inverse tag
 * `@psalm-no-seal-methods`: it *lifts* the default seal, so an undeclared magic
 * call becomes allowed. Only a tool that models the seal machinery goes silent
 * there; a tool that ignores the tag keeps its default diagnostic. That silence
 * is the real signal, so it is scored with a quiet probe on the origin tools.
 *
 * References:
 * - Psalm supported_annotations.md: `@psalm-seal-methods`, `@psalm-no-seal-methods`
 * - Psalm configuration.md: `sealAllMethods` (default true)
 */

/**
 * Sealed (the default). An undeclared magic call is rejected here, but every
 * default-sealing tool does that regardless of the tag, so the diagnostic is
 * baseline noise rather than evidence the seal tag was honoured.
 *
 * @method string greet()
 * @psalm-seal-methods
 */
final class Sealed // T: @psalm-seal-methods
{
    /**
     * @param list<mixed> $arguments
     */
    public function __call(string $name, array $arguments): mixed // E?[noise]: some tools flag the docblock/native array mismatch
    {
        return $name === 'greet' ? 'hello' : throw new \BadMethodCallException($name);
    }
}

/**
 * Explicitly unsealed. `@psalm-no-seal-methods` lifts the default seal, so a
 * tool that honours the seal family must now *allow* an undeclared magic call.
 * Silence is the discriminator; a tool that ignores the tag keeps its default
 * "undefined magic method" diagnostic.
 *
 * @method string greet()
 * @psalm-no-seal-methods
 */
final class Unsealed // T: @psalm-no-seal-methods
{
    /**
     * @param list<mixed> $arguments
     */
    public function __call(string $name, array $arguments): mixed // E?[noise]: some tools flag the docblock/native array mismatch
    {
        return $name === 'greet' ? 'hello' : throw new \BadMethodCallException($name);
    }
}

$sealed = new Sealed();
$sealed->greet(); // V

// Rejected by every default-sealing tool, tag or no tag — baseline, not the
// seal tag under test. A bare statement avoids a confounding mixed-return
// diagnostic from the surrounding expression.
$sealed->wave(); // E?[noise]: undeclared magic method rejected by default sealing

$unsealed = new Unsealed();
$unsealed->greet(); // V

// Honouring the seal family means `@psalm-no-seal-methods` lifts the seal and
// this undeclared call is allowed (silence). A tool that ignores the tag still
// reports it; that report is noise, not tag enforcement. Quiet is origin-only
// (Psalm/pzoom), the only tools confirmed to model the no-seal lever.
$unsealed->wave(); // Q?<psalm> // Q?<pzoom> // E?[noise]