Add Forall type for polymorphism
Introduce `StaticType::Forall` to represent universally quantified types. This enables Hindley-Milner's let-polymorphism, allowing functions to be generalized and reused with different concrete types. - **Generalization at `def`:** Function values are now generalized using `self.generalize` when they are defined, wrapping their type in `Forall`. This occurs at `def` boundaries. - **Instantiation at use:** Function types are instantiated with fresh type variables at each call site using `self.instantiate`. This ensures that different uses of the same polymorphic function do not interfere with each other's type inference. - **Value restriction:** Only function-typed definitions are generalized. Mutable state like series and scalars remain monomorphic to prevent unexpected behavior. - **Type context awareness:** The `generalize` function considers the current type context to avoid quantifying type variables that are still in use elsewhere. Feat: Add Forall type for polymorphism Introduces the `Forall` type to support polymorphic functions and enable let-polymorphism. - `Forall(vars, body)`: Represents a universally quantified type where `vars` are the type variables bound by this quantification, and `body` is the type itself. - `TypeChecker::generalize`: Wraps a type in `Forall` when a `def` boundary is encountered for function-typed values. This ensures that each use site of a polymorphic function gets its own instantiation. - `TypeChecker::instantiate`: Replaces the type variables within a `Forall` type with fresh ones. This is performed at each call site of a polymorphic function. - Updates `TypeChecker::unify` and `TypeChecker::apply_subst` to correctly handle `Forall` types, ensuring that bound variables within a `Forall` are not affected by global substitutions and that `Forall` types are instantiated before unification. - Adds a new example script `examples/Propa.myc` to demonstrate basic usage. - Includes a regression test `test_let_poly_non_series_callable_no_false_positive` to ensure that passing non-series callables to generic functions does not lead to type errors. - Introduces `TypeChecker::bind_var` to handle lazy propagation of index-call constraints, crucial for correctly typing series lookups like `(s 0)`.
This commit is contained in:
@@ -189,3 +189,29 @@ fn test_let_poly_generic_return_used_in_field_series() {
|
||||
other => panic!("Expected Float(42.0), got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Regression test: passing a non-series callable to a generic `last` function
|
||||
/// must NOT produce a type error. Before the index-constraint fix, Step 9 would
|
||||
/// eagerly unify `TypeVar = Series(elem)`, making `last` Series-only and causing
|
||||
/// a spurious conflict when a Function-typed variable was passed.
|
||||
///
|
||||
/// After the fix, `last` is inferred as `∀α β. fn(α) → β` with a lazy constraint:
|
||||
/// only when `α` resolves to `Series(inner)` does `β` also resolve to `inner`.
|
||||
/// For any other callable, `β` stays unbound and the return type is `Any`.
|
||||
#[test]
|
||||
fn test_let_poly_non_series_callable_no_false_positive() {
|
||||
let env = Environment::new();
|
||||
let source = r#"
|
||||
(do
|
||||
(def f (fn [n] (n)))
|
||||
(def last (fn [s] (s 0)))
|
||||
(last f)
|
||||
)
|
||||
"#;
|
||||
let result = env.compile(source);
|
||||
assert!(
|
||||
!result.diagnostics.has_errors(),
|
||||
"Passing a non-series callable to a generic function must not produce a type error: {}",
|
||||
result.diagnostics.format_errors()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user