Fix constraint solver non-determinism and test isolation#28
Merged
Conversation
…elds
Add missing grammar rule for (params) -> () in function type expressions.
The parser handled () -> () but not (self: T) -> () or (a: number) -> (),
causing parse errors in record field type annotations with void-returning
methods.
One new rule in parser.go.y:
'(' funcparamlist ')' TArrow '(' ')' → FunctionTypeExpr with empty Returns
Impact on real-world fixture scorecard (117 → 72 tracked errors):
- factory-constructor: 6 → 0 (fixed)
- iterator-pipeline: 11 → 0 (fixed)
- module-with-generics: 5 → 0 (fixed)
- service-locator: 14 → 0 (fixed)
- generic-registry: 12 → 4
- metatable-oop: 5 → 4
Two independent bugs causing test non-determinism: 1. types/flow/domain/type_domain.go: applyIsNil was unconditionally setting narrowed type to nil without checking if the base type could actually be nil. When a contradictory isNil constraint applied to a non-optional type, it corrupted the domain state instead of marking it unsatisfiable. Now filters by kind.Nil and marks Unsat when narrowing produces Never. 2. state_test.go: TestUninitializedVarAccess requires an exact 128-slot registry to trigger the resize at the right point. Previous drain of statePool was insufficient — per-P caches in sync.Pool could return states after the drain. Added runtime.GC() between drain passes to flush per-P caches. Verified: 0 failures in 50 consecutive runs of go test -count=1 .
Add M.TypeName = TypeName exports for all cross-file type declarations. While the checker resolves types through manifests (not runtime values), exporting types is idiomatic and enables runtime validation via :is().
When an identifier resolves to unknown through normal flow analysis but
matches a declared type name in scope, produce a Meta type wrapping
the declared type. This enables type values to flow through module
exports as first-class values.
Example: type Config = {host: string}; return {Config = Config}
Previously the Config field was unknown. Now it's typeof(Config),
enabling cross-module patterns like mylib.Config:is(data).
This matches the pattern used in wippy production code where type
declarations are exported as table fields for runtime validation.
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.
Summary
Root cause
applyIsNilin the constraint solver's type domain unconditionally set the narrowed type tonilwithout checking if the base type could actually be nil. When a contradictoryisNilconstraint was applied to a non-optional type (e.g., after truthiness narrowing), it corrupted the domain state instead of marking it unsatisfiable. This caused non-deterministic narrowing results depending on constraint evaluation order.Changes
types/flow/domain/type_domain.go:applyIsNilnow filters base type bykind.Niland marks domain unsatisfiable when narrowing producesNevertypes/constraint/solver.go: Propagate unsat from domain applicationstate_test.go: Drain pool + GC flush for registry-capacity-sensitive testtestdata/fixtures/realworld/: Export cross-file types asM.Type = TypeTest plan
go test -count=1 .go test -count=1 ./...— all packages passTestFixtureOrder_GenericRegistryThenMultiReturnregression testtype_domain_test.goandsolver_test.go