Repository navigation
Latte 3.1 support - #1
Merged
Merged
Conversation
WerySkok
marked this pull request as ready for review
October 8, 2026 22:34
WerySkok
marked this pull request as draft
October 8, 2026 22:36
…>line normalization
…preg_match - add LatteVersion::isLatte31() and use it instead of method_exists() which PHPStan constant-folds to true under Latte 3.1 - ignore the legacy TemplateGenerator::generate()/buildClass()/generateCode() calls for the Latte version that does not define them, following the existing cross-version ignoreErrors in phpstan.neon - narrow preg_match result in LayoutPathResolver with isset() so PHPStan stops reporting offsets on the inferred array shape
…n PHPDoc Latte 3.1 registers built-in filters and functions as first-class callables, so their closure types (resolved by ClosureTypeFactory) now embed bare array/iterable/Traversable/Generator types. Those were emitted verbatim into the @var doc of the generated $__filters__/$__functions__ variables, making every compiled template report missingType errors at level 6+. Add TypeHelper::toPhpDocNode() which expands such bare iterables to explicit mixed type arguments before printing. Fixes 28 failing tests.
Latte 3.1 compiles escaping to Latte\Runtime\HtmlHelpers::escapeText() whose $s parameter is mixed, so the FilterString checks from the old Latte\Runtime\Filters::escapeHtmlText() stub no longer fired. Stub HtmlHelpers::escapeText with the same FilterString type and teach EscapeErrorTransformer to translate its errors to the "Cannot convert X to HTML string." message.
… through array callables Latte 3.1 registers built-in filters and functions as first-class callables (Closure objects) instead of [class, method] arrays. The visitors treated them as plain closures, so calls were generated on closure variables and the Latte stubs (e.g. stubs/Latte/Essential/Filters.stub) no longer applied. Detect closures that wrap a method via ReflectionFunction scope class and convert them back to [class, method], so static methods are called directly and stubs apply again. Latte 3.1 also adds native types to the built-in filters; PHPStan stubs can only narrow native types, so the FilterString stub union no longer applies to |lower/|upper/|capitalize and the native |slice return type (Generator) leaks into escape checks. Expectations for Latte 3.1 are adjusted accordingly, Latte 2 and 3.0 expectations are unchanged.
- use the correct Nette\Utils\FileSystem class name (Filesystem references) - cover list<Latte\Extension> in the getExtensions() is_array ignore - drop now-redundant is_string()/is_array()/count() checks - align FakePresenterFactory::setFakeMapping() mapping shape with PresenterFactory::setMapping(); parent already validates the mapping, so the duplicated 2-element array branch was unreachable - drop ignores that no longer match and mark the get_class one reportUnmatched
PHPStan 2.3 reports new dead-code, repeated-assignment, implicit-array and coalesce errors on generated template code, and nette/forms 3.3 changed FormsLatte\Runtime::get() to return Control|Container, which made generated control lookups lose their BaseControl type. - add a Runtime::get() stub with a conditional return type so control lookups keep resolving to BaseControl (Container lookups are unaffected) - update Variables/Forms/Links/NoMapping fixture expectations for the new PHPStan 2.3 errors; the separated-phpstan-command variant does not report them, so they are gated with usesSeparatedPhpStanCommand() - gate the Latte 3.1-only slice() Generator expectations with isLatte31() Latte 2 and 3.0 expectations are preserved.
PHP 7.4 CI installs Latte 2.11.x and analyses with phpVersion 70400, where the PHP 8 string helpers used across the collectors and node visitors are unknown symbols. Require symfony/polyfill-php80 so the functions are discovered on PHP 7.4 too. Fixes 26 'Function str_contains/str_starts_with not found.' errors in the PHPStan (normal) and PHPStan (lowest) matrix jobs.
On Latte 3.0/2.11 TemplateGenerator::generateCode() is unknown, so its return value is mixed; assert string before str_replace(). On PHPStan 2.1 ClassReflection::getMethod() received mixed from filter/function array callables; add an is_string() guard (ignored as always-true on PHPStan 2.3 where the phpdoc already narrows). Fixes Latte3Compiler.php:100 and ChangeFilters/ChangeFunctionsNodeVisitor getMethod() errors in the PHPStan (normal) PHP 8.0/8.1 and PHPStan (lowest) jobs.
The PHPunit matrix resolves different dependencies per PHP version: PHPStan 2.1.33 (lowest), Latte 2.11.x (PHP 7.4, lowest <= 8.3), Latte 3.0.x (PHP 8.0/8.1, lowest 8.4/8.5), nette/forms 3.1.x (PHP <= 8.1), 3.2.x (PHP 8.2) and 3.3.x (PHP 8.3+). - DependencyVersion detects PHPStan >= 2.3 via UnusedVariableRule (inside RuleTestCase the PHPStan phar ships its own Composer\InstalledVersions without package versions) and nette/forms >= 3.3 via the removal of FormsLatte\Runtime::item() - testVariables/testComponents/testSnippets/testLinks: gate PHPStan 2.3-only dead-code errors and add the Latte 2-only $iterations errors - testForms: add a Latte 3.1 + nette/forms 3.2 branch (item() still used there) and the nette/forms 3.1 extras for Latte 2/3.0 Verified locally on all four dependency shapes (Latte 2.11.7/nf 3.1.15/ PHPStan 2.3.1, Latte 3.0.26/nf 3.1.15/PHPStan 2.3.1, Latte 3.1.6/nf 3.2.9/ PHPStan 2.3.1, Latte 3.1.6/nf 3.3.0/PHPStan 2.3.1) plus Latte 2.11.7/ PHPStan 2.1.33: 82/82 tests pass.
TypeHelper::toPhpDocNode() replaced the base identifier of already generic types (e.g. "array" in "array<string>"), which threw a TypeError inside NodeTraverser and aborted analysis of every compiled template. Expand bare iterables only and leave typed generics untouched. Resolve first-class callables of instance methods via the bound object class so the most derived implementation is analysed, as with array callables. Add TypeHelper unit tests and an inherited first-class callable filter fixture that fails without the fix.
Latte 3.0.27 normalizes multi-line template names to null, so passing the generated class comment as TemplateGenerator::generate()'s template name dropped the class docblock and left $this->global typed as mixed in every compiled template. Pass null and inject the comment before the class declaration for both the 3.0 and 3.1 generator APIs, matching the previous 3.0 output. Also keep the first-class callable fixture parseable by PHP 7.4 (PHPDoc @return static instead of the native return type).
- stub Latte 3.1 runtime escaping helpers (Helpers, XmlHelpers, HtmlHelpers)
so escape type errors are reported again, and teach the error transformer
about the new helper classes
- respect {layout none} and report missing explicit layouts instead of
silently replacing them with @layout.latte
- include the latte config in the collector cache key so templateTypes and
other options invalidate cached collector results
- escape and verify the injected generated class comment
- keep non-public first-class callables as closures, as generated template
classes cannot call them
- add regression tests for escapes, layout resolution and private filters
- document templateTypes, maxTemplateVariants and breakIncludeCycles
- add a dynamic static return type extension for slice() so strings stay strings and arrays keep their value type instead of leaking Generator into escape checks on Latte 3.1 - stop narrowing escapeJs to FilterString: Latte JSON-encodes arrays and objects, so those are valid arguments - pass the engine feature map to TemplateGenerator::buildClass() so ScopedLoopVariables and other 3.1 features affect generated code; ignore the unset() false positive produced by Latte's scoped-loop code - add a ScopedLoopVariables engine-bootstrap regression test and update slice/escape expectations
phpstan.neon now skips Feature::cases() and Engine::hasFeature() when self-analysis runs against Latte 2.11/3.0, as it already does for the version-specific TemplateGenerator methods.
- report a missing layout as a regular rule error instead of crashing on a non-existent template path; guard all layout template additions with is_file() and make ErrorBuilder fall back to an existing file - parse layout tags with quote awareness: only single non-interpolated string literals (or bare file names) are paths, expressions fall back to @layout.latte; support absolute paths and treat quoted none/auto as files - include engine feature state in the Latte3Compiler cache key - keep running semantic scope visitors when resolveTypes=false, with a fallback for filter/function class detection and snippet driver rewrite - normalize '/* pos */' comments via token_get_all so user string literals are not rewritten - give the Latte 3.1 phantom Filters::escapeJs stub an explicit mixed type - add tests for missing layouts, layout expressions, maxTemplateVariants, breakIncludeCycles, resolveTypes=false, templateTypes and compiler cache key; fix the first-class callable @requires annotation
- ignore the mixed feature-name concatenation when self-analysing against Latte 2.11/3.0 - import PREG_OFFSET_CAPTURE for the coding standard - require ext-tokenizer now that the compiler uses token_get_all()
- reject non-file setFile() template paths with latte.notFound instead of
letting them crash compilation, and guard LayoutPathResolver::resolve()
against non-file inputs
- treat extensionless unquoted layout names ({layout main}) as literal paths
again, while keeping interpolated/quoted expressions dynamic
- emit warnings when maxTemplateVariants or breakIncludeCycles skip a
variant/include so analysis gaps are visible, preserving explicit
is_warning metadata in ErrorBuilder
- document the resolveTypes tradeoff for forms and the new skipped-variant
warnings
- add missing setFile and extensionless layout regression tests
WerySkok
force-pushed
the
latte-3.1-support
branch
from
October 28, 2026 08:55
faa6606 to
2221f7a
Compare
WerySkok
marked this pull request as ready for review
October 28, 2026 09:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fork of the extension with Latte 3.1 support while keeping Latte 2.11 / 3.0 support.
Changes
TemplateGenerator::buildClass()/generateCode()split,pos->linemapping, class comment injection (also for Latte 3.0.27)HtmlHelpers/Helpers/XmlHelpers; more preciseslice()andescapeJs()typing{layout none}, missing-layout diagnosticslatteconfig is part of the collector cache keylatte.templateTypes,latte.maxTemplateVariants,latte.breakIncludeCycles,latte.resolveTypesCI
All 49 checks pass: codesniffer, PHPStan level max (normal + lowest dependencies), PHPUnit (normal + lowest dependencies), syntax checker and extensions finder.
Fork notes
composer.jsonpinsversion: 0.20.1for path repositories