Skip to content

Commit c30ec7c

Browse files
committed
Typecheck let bindings using annotated type
1 parent da995eb commit c30ec7c

9 files changed

+201
-81
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@
2929

3030
([Surya Rose](https://github.com/GearsDatapacks))
3131

32+
- Let bindings now continue to assume their annotated type after mismatches,
33+
preventing misleading follow-up errors:
34+
```gleam
35+
pub fn main() {
36+
let x: String = 5 // type error: expected String, got Int
37+
let y: Int = x // valid
38+
let z: String = x // type error: expected String, got Int
39+
}
40+
```
41+
([Adi Salimgereyev](https://github.com/abs0luty))
42+
3243
### Build tool
3344

3445
- The help text displayed by `gleam dev --help`, `gleam test --help`, and

compiler-core/src/type_/expression.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,44 +1976,48 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
19761976
let type_ = value.type_();
19771977
let kind = self.infer_assignment_kind(kind.clone());
19781978

1979-
// Ensure the pattern matches the type of the value
1980-
let mut pattern_typer = pattern::PatternTyper::new(
1981-
self.environment,
1982-
&self.implementations,
1983-
&self.current_function_definition,
1984-
&self.hydrator,
1985-
self.problems,
1986-
PatternPosition::LetAssignment,
1987-
);
1979+
let mut annotation_type_override = None;
19881980

1989-
let pattern = pattern_typer.infer_single_pattern(pattern, &value);
1990-
1991-
let minimum_required_version = pattern_typer.minimum_required_version;
1992-
if minimum_required_version > self.minimum_required_version {
1993-
self.minimum_required_version = minimum_required_version;
1994-
}
1995-
1996-
let pattern_typechecked_successfully = !pattern_typer.error_encountered;
1997-
1998-
// Check that any type annotation is accurate.
19991981
if let Some(annotation) = &annotation {
20001982
match self
20011983
.type_from_ast(annotation)
20021984
.map(|type_| self.instantiate(type_, &mut hashmap![]))
20031985
{
20041986
Ok(annotated_type) => {
1987+
let annotation_type_for_pattern = annotated_type.clone();
20051988
if let Err(error) = unify(annotated_type, type_.clone())
20061989
.map_err(|e| convert_unify_error(e, value.type_defining_location()))
20071990
{
20081991
self.problems.error(error);
20091992
}
1993+
annotation_type_override = Some(annotation_type_for_pattern);
20101994
}
20111995
Err(error) => {
20121996
self.problems.error(error);
20131997
}
20141998
}
20151999
}
20162000

2001+
// Ensure the pattern matches the type of the value
2002+
let mut pattern_typer = pattern::PatternTyper::new(
2003+
self.environment,
2004+
&self.implementations,
2005+
&self.current_function_definition,
2006+
&self.hydrator,
2007+
self.problems,
2008+
PatternPosition::LetAssignment,
2009+
);
2010+
2011+
let pattern =
2012+
pattern_typer.infer_single_pattern(pattern, &value, annotation_type_override.clone());
2013+
2014+
let minimum_required_version = pattern_typer.minimum_required_version;
2015+
if minimum_required_version > self.minimum_required_version {
2016+
self.minimum_required_version = minimum_required_version;
2017+
}
2018+
2019+
let pattern_typechecked_successfully = !pattern_typer.error_encountered;
2020+
20172021
// The exhaustiveness checker expects patterns to be valid and to type check;
20182022
// if they are invalid, it will crash. Therefore, if any errors were found
20192023
// when type checking the pattern, we don't perform the exhaustiveness check.
@@ -2029,7 +2033,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
20292033
}
20302034

20312035
let (output, not_exhaustive_error) =
2032-
self.check_let_exhaustiveness(location, value.type_(), &pattern);
2036+
self.check_let_exhaustiveness(location, type_.clone(), &pattern);
20332037

20342038
match (&kind, not_exhaustive_error) {
20352039
// The pattern is exhaustive in a let assignment, there's no problem here.

compiler-core/src/type_/pattern.rs

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
317317
for (pattern, subject) in multi_pattern.into_iter().zip(subjects) {
318318
let subject_variable = Self::subject_variable(subject);
319319

320-
let pattern = self.unify(pattern, subject.type_(), subject_variable);
320+
let pattern = self.unify(pattern, subject.type_(), subject_variable, None);
321321
typed_multi.push(pattern);
322322
}
323323

@@ -330,10 +330,16 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
330330
&mut self,
331331
pattern: UntypedPattern,
332332
subject: &TypedExpr,
333+
subject_type_override: Option<Arc<Type>>,
333334
) -> TypedPattern {
334335
let subject_variable = Self::subject_variable(subject);
335-
336-
let typed_pattern = self.unify(pattern, subject.type_(), subject_variable);
336+
let subject_type = subject.type_();
337+
let typed_pattern = self.unify(
338+
pattern,
339+
subject_type,
340+
subject_variable,
341+
subject_type_override,
342+
);
337343
self.register_variables();
338344
typed_pattern
339345
}
@@ -453,7 +459,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
453459
.into_iter()
454460
.map(|option| {
455461
analyse::infer_bit_array_option(option, |value, type_| {
456-
Ok(self.unify(value, type_, None))
462+
Ok(self.unify(value, type_, None, None))
457463
})
458464
})
459465
.try_collect()
@@ -529,7 +535,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
529535
_ => segment_type,
530536
};
531537

532-
let typed_value = self.unify(*segment.value, type_.clone(), None);
538+
let typed_value = self.unify(*segment.value, type_.clone(), None, None);
533539

534540
match &typed_value {
535541
// We can't directly match on the contents of a `Box`, so we must
@@ -586,6 +592,18 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
586592
// in the inner scope, we can infer that the `some_wibble` variable is the `Wibble` variant
587593
//
588594
subject_variable: Option<EcoString>,
595+
// An optional type annotation provided for the pattern.
596+
//
597+
// Example:
598+
// ```gleam
599+
// let assert [first, ..rest]: List(String) = numbers
600+
// ```
601+
//
602+
// We pass `List(String)` annotation down so sub-patterns know
603+
// that `first` must be a `String`. We still check `numbers`
604+
// against its real inferred type and emit a mismatch if it is, say,
605+
// `List(Int)`.
606+
annotation_type: Option<Arc<Type>>,
589607
) -> TypedPattern {
590608
match pattern {
591609
Pattern::Discard { name, location, .. } => {
@@ -595,7 +613,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
595613
.discarded_names
596614
.insert(name.clone(), location);
597615
Pattern::Discard {
598-
type_,
616+
type_: annotation_type.unwrap_or_else(|| type_.clone()),
599617
name,
600618
location,
601619
}
@@ -609,10 +627,11 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
609627
origin,
610628
..
611629
} => {
612-
self.insert_variable(&name, type_.clone(), location, origin.clone());
630+
let variable_type = annotation_type.unwrap_or_else(|| type_.clone());
631+
self.insert_variable(&name, variable_type.clone(), location, origin.clone());
613632

614633
Pattern::Variable {
615-
type_,
634+
type_: variable_type,
616635
name,
617636
location,
618637
origin,
@@ -691,17 +710,24 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
691710
pattern,
692711
location,
693712
} => {
694-
let pattern = self.unify(*pattern, type_, subject_variable);
713+
let pattern_annotation = annotation_type.clone();
714+
let pattern = self.unify(
715+
*pattern,
716+
type_.clone(),
717+
subject_variable,
718+
pattern_annotation,
719+
);
695720

696721
if pattern.is_discard() {
697722
self.problems.warning(Warning::UnusedDiscardPattern {
698723
location,
699724
name: name.clone(),
700725
});
701726
}
727+
let assigned_type = annotation_type.unwrap_or_else(|| pattern.type_().clone());
702728
self.insert_variable(
703729
&name,
704-
pattern.type_().clone(),
730+
assigned_type.clone(),
705731
location,
706732
VariableOrigin {
707733
syntax: VariableSyntax::AssignmentPattern,
@@ -766,28 +792,53 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
766792
self.environment,
767793
) {
768794
Some(arguments) => {
769-
let type_ = arguments
795+
let element_type = arguments
770796
.first()
771797
.expect("Failed to get type argument of List")
772798
.clone();
799+
let list_type = list(element_type.clone());
800+
let annotation_element_type = annotation_type.as_ref().and_then(|annotation| {
801+
annotation
802+
.get_app_arguments(
803+
Publicity::Public,
804+
PRELUDE_PACKAGE_NAME,
805+
PRELUDE_MODULE_NAME,
806+
"List",
807+
1,
808+
self.environment,
809+
)
810+
.and_then(|arguments| arguments.into_iter().next())
811+
});
773812
let elements = elements
774813
.into_iter()
775-
.map(|element| self.unify(element, type_.clone(), None))
814+
.map(|element| {
815+
self.unify(
816+
element,
817+
element_type.clone(),
818+
None,
819+
annotation_element_type.clone(),
820+
)
821+
})
776822
.collect();
777-
let type_ = list(type_);
778823

779824
let tail = tail.map(|tail| {
825+
let tail_annotation = annotation_type.clone();
780826
Box::new(TailPattern {
781827
location: tail.location,
782-
pattern: self.unify(tail.pattern, type_.clone(), None),
828+
pattern: self.unify(
829+
tail.pattern,
830+
list_type.clone(),
831+
None,
832+
tail_annotation,
833+
),
783834
})
784835
});
785836

786837
Pattern::List {
787838
location,
788839
elements,
789840
tail,
790-
type_,
841+
type_: list_type,
791842
}
792843
}
793844

@@ -819,10 +870,22 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
819870
return Pattern::Invalid { location, type_ };
820871
}
821872

873+
let annotation_elements = annotation_type.as_ref().and_then(|annotation| {
874+
match collapse_links(annotation.clone()).deref() {
875+
Type::Tuple { elements } => Some(elements.clone()),
876+
_ => None,
877+
}
878+
});
822879
let elements = elements
823880
.into_iter()
824881
.zip(type_elements)
825-
.map(|(pattern, type_)| self.unify(pattern, type_.clone(), None))
882+
.enumerate()
883+
.map(|(index, (pattern, type_))| {
884+
let annotation = annotation_elements
885+
.as_ref()
886+
.and_then(|elements| elements.get(index).cloned());
887+
self.unify(pattern, type_.clone(), None, annotation)
888+
})
826889
.collect();
827890
Pattern::Tuple { elements, location }
828891
}
@@ -832,10 +895,22 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
832895
.map(|_| self.environment.new_unbound_var())
833896
.collect();
834897
self.unify_types(tuple(elements_types.clone()), type_, location);
898+
let annotation_elements = annotation_type.as_ref().and_then(|annotation| {
899+
match collapse_links(annotation.clone()).deref() {
900+
Type::Tuple { elements } => Some(elements.clone()),
901+
_ => None,
902+
}
903+
});
835904
let elements = elements
836905
.into_iter()
837906
.zip(elements_types)
838-
.map(|(pattern, type_)| self.unify(pattern, type_, None))
907+
.enumerate()
908+
.map(|(index, (pattern, type_))| {
909+
let annotation = annotation_elements
910+
.as_ref()
911+
.and_then(|elements| elements.get(index).cloned());
912+
self.unify(pattern, type_, None, annotation)
913+
})
839914
.collect();
840915
Pattern::Tuple { elements, location }
841916
}
@@ -1213,7 +1288,8 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
12131288
.cloned()
12141289
.unwrap_or_else(|| self.environment.new_unbound_var());
12151290

1216-
let value = self.unify(value, type_, None);
1291+
let annotation = Some(type_.clone());
1292+
let value = self.unify(value, type_, None, annotation);
12171293
CallArg {
12181294
value,
12191295
location,

compiler-core/src/type_/tests/errors.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,19 @@ fn module_could_not_unify6() {
743743
assert_module_error!("fn main() { let x: String = 5 x }");
744744
}
745745

746+
#[test]
747+
fn module_could_not_unify_let_binding_annotation_follow_up() {
748+
assert_module_error!(
749+
"
750+
pub fn main() {
751+
let x: String = 5
752+
let y: Int = x
753+
let z: String = x
754+
}
755+
"
756+
);
757+
}
758+
746759
#[test]
747760
fn module_could_not_unify7() {
748761
assert_module_error!("fn main() { let assert 5 = \"\" }");

compiler-core/src/type_/tests/functions.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -261,33 +261,6 @@ pub fn main() {
261261
);
262262
}
263263

264-
#[test]
265-
fn multiple_bad_statement_assignment_with_annotation_fault_tolerance() {
266-
assert_module_error!(
267-
r#"
268-
pub fn main() {
269-
let a: Int = "not an int"
270-
let b: String = 1
271-
let c = a + 2
272-
}
273-
"#
274-
);
275-
}
276-
277-
#[test]
278-
fn multiple_bad_statement_assignment_with_annotation_fault_tolerance2() {
279-
assert_module_error!(
280-
r#"
281-
pub fn main() {
282-
// Since the value is invalid the type is the annotation
283-
let a: Int = Junk
284-
let b: String = 1
285-
let c = a + 2
286-
}
287-
"#
288-
);
289-
}
290-
291264
#[test]
292265
fn multiple_bad_statement_assignment_with_pattern_fault_tolerance2() {
293266
assert_module_error!(

compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify9.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
---
22
source: compiler-core/src/type_/tests/errors.rs
3+
assertion_line: 771
34
expression: "fn main() { let assert [1, 2, ..x]: List(String) = [1,2,3] x }"
5+
snapshot_kind: text
46
---
57
----- SOURCE CODE
68
fn main() { let assert [1, 2, ..x]: List(String) = [1,2,3] x }

0 commit comments

Comments
 (0)