Technical investigation on 2026-09-20, without application-source changes, annotation removal, diagnostic filtering, or upstream implementation changes. This review does not represent Maintainer approval.
Runtime: combined WFL df6ad9a2f9f401d943edfdec2e3804941a9c513f, reporting 26.9.12,
SHA-256 b96c06f6013a9a64d4d042c9b379a30af1c8d274d7855b5ebe9d7fe14a750d1c.
The eight minimal WFL probes and captured output remain under ignored
target/type-analysis-probes/. Each executable probe was run as:
target/runtime/combined-df6ad9a2/wfl.exe --test target/type-analysis-probes/<name>.test.wfl
All eight execution tests passed, one assertion case per probe. These are
runtime characterization results, not a claim of clean static analysis.
Their .test.wfl.log files retain every diagnostic.
| Probe | Static diagnostic | Interpretation |
|---|---|---|
list-parameter |
List differs from List of Any; a list argument also differs from List. |
Inconsistent parser representation of the same collection annotation. |
container-parameter |
Cannot access label on non-container type Item. |
Named container parameter is represented as a custom type, but the property resolver does not recognize that registered container. |
include-definition |
Included container type/metadata not found. | Static include traversal is absent; the definition exists when execution reaches it. |
parent-definition |
Container metadata not found in an included consumer. | Parent type symbols alone do not populate the analyzer's container registry. |
parent-after-call |
Container metadata not found after a parent action call. | Same metadata limitation; no runtime failure. |
parent-after-method |
Expected Container<ParentItem> but found Any. |
Conservative call effects widen a parent container binding seeded as mutable, even though this particular method leaves it intact. |
untyped-factory |
Typed Text property initialized from Unknown. |
Intentional persistent-property safety diagnostic: the untyped function signature does not establish its input contract. |
typed-factory |
None. | Adding the existing as text parameter annotation establishes the contract and preserves the same successful call. |
The final two files differ only by with parameters label_value versus
with parameters label_value as text. This distinguishes an authored static
contract gap from a runtime false positive. Successful tests alone cannot prove
that every untyped call will always satisfy a typed property.
Source references below are in WebFirstLanguage/wfl at the revision above.
-
src/parser/stmt/containers.rs:64parses a bare propertyListasType::List(Type::Any). In contrast, method parameter parsing atsrc/parser/stmt/actions.rs:541usescolon_type_from_tokenat line 45, whose primitive cases omitList, leavingType::Custom("List"). Scriptorium uses the documented colon-style method form, not an invented annotation syntax. A minimal reproduction is:create container Basket: property entries: List defaults [] action replace_entries needs replacement: List: change entries to replacement end end -
src/typechecker/mod.rs:10693resolves dot-property access forContainerInstanceand other concrete runtime types. A method parameter annotatedincoming: Iteminstead carriesCustom("Item"). That custom name is valid for nominal assignment compatibility (src/typechecker/mod.rs:11107), but does not reach the registered container's property lookup. The minimal failing diagnostic is produced bychange saved_label to incoming.labelin a method withincoming: Item, even whenItemand itsTextproperty are in the same file. -
src/typechecker/mod.rs:7631explicitly documents that the checker does not parse includes. The runtime checks each included file before executing its nested includes (src/interpreter/mod.rs:9190and:9219).snapshot_parent_scope(src/interpreter/mod.rs:5292) transports variable types and action signatures, but not the container metadata registry. Consequently a later file can seeContainer("Item")without its field and inheritance metadata.escape_all_visible_mutable_state(src/typechecker/mod.rs:1786) further widens mutable imported bindings toAnyacross an opaque method/constructor call; the local-container declaration is registered immutable while the runtime snapshot uses the runtime binding's mutability. Theparent-after-methodprobe reproduces exactly the application'snot a container type ... found Anycategory. -
are_declared_property_types_compatible(src/typechecker/mod.rs:10975) deliberately rejectsAny/Unknownsources for a concrete persistent property. Ordinary action compatibility is more gradual; persistent properties retain their declared type on later reads, so accepting an unchecked replacement would make those reads unsound. Existing Rust regression coverage attests/typechecker_container_contract_test.rs:177specifically protects this rule. Do not loosen it to silence Scriptorium factories. Where a factory's parameter contract is known, adding the existing specific type annotation is the appropriate application improvement. Dynamic list construction and included action return inference may still need a separate evidence-based checker improvement; assigningAnyeverywhere is not one.
The container guide demonstrates colon-style properties, needs parameters,
and named container parameters (docs/04-advanced-features/containers-oop.md,
including its Task/TaskList example). The action guide documents the additive
as text form (docs/03-language-basics/actions-functions.md:249).
The CLI deliberately emits type diagnostics as nonfatal warnings before normal
execution (src/main.rs:965); the included-file path does the same. The
error[ERROR] presentation does not mean these tests were skipped or that the
runner discarded a nonzero result. It also does not make the diagnostic
limitations disappear.
The first two issues are suitable for a focused upstream change, separately reviewed and tested before use:
- Normalize the already documented bare
Listmethod-parameter annotation to the same list type as a property. Reuse the property type grammar if extending parameter annotations to nested lists, rather than maintaining divergent parsers. Preserve existing case and custom-type rules; explicitly examine legacy containers namedListbefore changing their interpretation. - Resolve
Custom(name)through the registered container in value-member contexts. Reuse inheritance/property lookup, preserve built-in handle and temporal type rules, and continue rejecting unknown names and missing fields. Check method calls through the same nominal value annotations as well. - Use a WFL process driver to run minimal WFL fixtures and inspect captured
diagnostics. Positive fixtures must still execute and lose only the false
diagnostic; negative fixtures must retain mismatched element/property types,
missing fields, unrelated container arguments, and unsafe
Any/Unknownpersistent-property assignments. Cover inherited named parameters and unchanged built-in/custom name behavior. Register these WFL tests in the existing gated runner; no new Rust/Python test scenario or driver is needed.
Include-aware analysis is a larger change: preserving complete parent container
contracts is only part of it, because a main file is checked before a nested
include executes. A sound static dependency pass must respect include scope,
shadowing, inheritance, cycles, source budgets, and runtime binding effects
without executing application code. Flattening the application's include tree,
reordering definitions to fool the checker, marking unknown definitions as
containers, or broadly exempting Any are not sound small patches.
These findings are not new functional prerequisites for the passing CMS flows. They are real diagnostic limitations and authored contract gaps to track explicitly; the application must not claim a clean type-analysis run. No new upstream PR was started as part of this investigation.
The foundation's overarching invariant (docs/wfl-foundation.md:101) requires
beginner and production forms to be the same or joined by an additive path.
Making List and named-container annotations mean the same thing on properties
and parameters repairs that path. Adding a precise parameter annotation to a
known factory contract also follows it: the existing call form and property
model stay intact. Removing annotations, teaching different spellings merely
to evade diagnostics, flattening reusable modules, or asking authors to ignore
all type warnings would create habits that production users must unlearn.
Keeping the current limitations visible is an honest interim statement, not a
foundation exception or acceptance of those workarounds.