diff --git a/manual/src/reference/variables-and-scopes.md b/manual/src/reference/variables-and-scopes.md index 5b65e535..83662037 100644 --- a/manual/src/reference/variables-and-scopes.md +++ b/manual/src/reference/variables-and-scopes.md @@ -40,6 +40,24 @@ let x = { print(x); // 3 ``` +## Reassignment + +The `=` operator can be used to reassign a value to an existing variable. When you reassign a variable to a value of a different type, the variable's type is widened to the least upper bound (LUB) of the old and new types. + +```ndc +let x = 1; // type is Int +x = 2; // type is still Int +x = 3.14; // type widens to Number (LUB of Int and Float) +``` + +```ndc +let pos = (); // type is () +pos = (1, 2); // type widens to Sequence +pos = ("a", "b"); // type is still Sequence +``` + +> **Tip:** For the best type inference, initialize variables with a value that matches the intended type. For example, use `let pos = (0, 0);` instead of `let pos = ();` if you intend to store a 2-tuple of numbers. + ## Destructuring Destructuring is more similar to how it works in python and cares mostly about where commas are and not so much about the delimiters (`[]`, `()`) used. diff --git a/ndc_analyser/src/analyser.rs b/ndc_analyser/src/analyser.rs index 87ecf2c1..f6671a34 100644 --- a/ndc_analyser/src/analyser.rs +++ b/ndc_analyser/src/analyser.rs @@ -78,8 +78,22 @@ impl Analyser { Ok(StaticType::unit()) } Expression::Assignment { l_value, r_value } => { - self.resolve_lvalue(l_value, *span)?; - self.analyse(r_value)?; + let old_type = self.resolve_lvalue(l_value, *span)?; + let new_type = self.analyse(r_value)?; + + // Widen the binding's type to the LUB so subsequent uses + // see the broader type. + if let Lvalue::Identifier { + resolved: Some(target), + .. + } = l_value + { + let widened = old_type.lub(&new_type); + if widened != old_type { + self.scope_tree.update_binding_type(*target, widened); + } + } + Ok(StaticType::unit()) } Expression::OpAssignment { diff --git a/tests/programs/900_bugs/bug0015_unpack_mismatched_tuple_arity.ndc b/tests/programs/900_bugs/bug0015_unpack_mismatched_tuple_arity.ndc index ba9120ff..cdf6a84f 100644 --- a/tests/programs/900_bugs/bug0015_unpack_mismatched_tuple_arity.ndc +++ b/tests/programs/900_bugs/bug0015_unpack_mismatched_tuple_arity.ndc @@ -1,5 +1,6 @@ // Destructuring let where the variable was declared as () but later // reassigned to a 2-tuple should not cause a false "not declared" error. +// The analyser widens the type via LUB on reassignment. // expect-output: 4 let pos = ();