Skip to content

Commit 8feba88

Browse files
committed
Typecheck let bindings using annotated type
1 parent afcd7e9 commit 8feba88

8 files changed

+182
-80
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@
5151
containing scientific notation or trailing zeros (i.e. `100` and `1e2`).
5252
([ptdewey](https://github.com/ptdewey))
5353

54+
- Let bindings now continue to assume their annotated type after mismatches,
55+
preventing misleading follow-up errors:
56+
```gleam
57+
pub fn main() {
58+
let x: String = 5 // type error: expected String, got Int
59+
let y: Int = x // valid
60+
let z: String = x // type error: expected String, got Int
61+
}
62+
```
63+
([Adi Salimgereyev](https://github.com/abs0luty))
64+
5465
### Build tool
5566

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

compiler-core/src/type_/expression.rs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,24 +2000,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
20002000
let type_ = value.type_();
20012001
let kind = self.infer_assignment_kind(kind.clone());
20022002

2003-
// Ensure the pattern matches the type of the value
2004-
let mut pattern_typer = pattern::PatternTyper::new(
2005-
self.environment,
2006-
&self.implementations,
2007-
&self.current_function_definition,
2008-
&self.hydrator,
2009-
self.problems,
2010-
PatternPosition::LetAssignment,
2011-
);
2012-
2013-
let pattern = pattern_typer.infer_single_pattern(pattern, &value);
2014-
2015-
let minimum_required_version = pattern_typer.minimum_required_version;
2016-
if minimum_required_version > self.minimum_required_version {
2017-
self.minimum_required_version = minimum_required_version;
2018-
}
2019-
2020-
let pattern_typechecked_successfully = !pattern_typer.error_encountered;
2003+
let mut annotation_type_override = None;
20212004

20222005
// Check that any type annotation is accurate.
20232006
if let Some(annotation) = &annotation {
@@ -2026,18 +2009,40 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
20262009
.map(|type_| self.instantiate(type_, &mut hashmap![]))
20272010
{
20282011
Ok(annotated_type) => {
2012+
let annotation_type_for_pattern = annotated_type.clone();
20292013
if let Err(error) = unify(annotated_type, type_.clone())
20302014
.map_err(|e| convert_unify_error(e, value.type_defining_location()))
20312015
{
20322016
self.problems.error(error);
20332017
}
2018+
annotation_type_override = Some(annotation_type_for_pattern);
20342019
}
20352020
Err(error) => {
20362021
self.problems.error(error);
20372022
}
20382023
}
20392024
}
20402025

2026+
// Ensure the pattern matches the type of the value
2027+
let mut pattern_typer = pattern::PatternTyper::new(
2028+
self.environment,
2029+
&self.implementations,
2030+
&self.current_function_definition,
2031+
&self.hydrator,
2032+
self.problems,
2033+
PatternPosition::LetAssignment,
2034+
);
2035+
2036+
let pattern =
2037+
pattern_typer.infer_single_pattern(pattern, &value, annotation_type_override.clone());
2038+
2039+
let minimum_required_version = pattern_typer.minimum_required_version;
2040+
if minimum_required_version > self.minimum_required_version {
2041+
self.minimum_required_version = minimum_required_version;
2042+
}
2043+
2044+
let pattern_typechecked_successfully = !pattern_typer.error_encountered;
2045+
20412046
// The exhaustiveness checker expects patterns to be valid and to type check;
20422047
// if they are invalid, it will crash. Therefore, if any errors were found
20432048
// when type checking the pattern, we don't perform the exhaustiveness check.
@@ -2053,7 +2058,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
20532058
}
20542059

20552060
let (output, not_exhaustive_error) =
2056-
self.check_let_exhaustiveness(location, value.type_(), &pattern);
2061+
self.check_let_exhaustiveness(location, type_.clone(), &pattern);
20572062

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

compiler-core/src/type_/pattern.rs

Lines changed: 89 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,11 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
330330
&mut self,
331331
pattern: UntypedPattern,
332332
subject: &TypedExpr,
333+
annotation_type: 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(pattern, subject_type, subject_variable, annotation_type);
337338
self.register_variables();
338339
typed_pattern
339340
}
@@ -453,7 +454,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
453454
.into_iter()
454455
.map(|option| {
455456
analyse::infer_bit_array_option(option, |value, type_| {
456-
Ok(self.unify(value, type_, None))
457+
Ok(self.unify(value, type_, None, None))
457458
})
458459
})
459460
.try_collect()
@@ -529,7 +530,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
529530
_ => segment_type,
530531
};
531532

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

534535
match &typed_value {
535536
// We can't directly match on the contents of a `Box`, so we must
@@ -586,6 +587,18 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
586587
// in the inner scope, we can infer that the `some_wibble` variable is the `Wibble` variant
587588
//
588589
subject_variable: Option<EcoString>,
590+
// An optional type annotation provided for the pattern.
591+
//
592+
// Example:
593+
// ```gleam
594+
// let assert [first, ..rest]: List(String) = numbers
595+
// ```
596+
//
597+
// We pass `List(String)` annotation down so sub-patterns know
598+
// that `first` must be a `String`. We still check `numbers`
599+
// against its real inferred type and emit a mismatch if it is, say,
600+
// `List(Int)`.
601+
annotation_type: Option<Arc<Type>>,
589602
) -> TypedPattern {
590603
match pattern {
591604
Pattern::Discard { name, location, .. } => {
@@ -595,7 +608,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
595608
.discarded_names
596609
.insert(name.clone(), location);
597610
Pattern::Discard {
598-
type_,
611+
type_: annotation_type.unwrap_or_else(|| type_.clone()),
599612
name,
600613
location,
601614
}
@@ -614,10 +627,11 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
614627
Pattern::Invalid { location, type_ }
615628
}
616629
_ => {
617-
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());
618632

619633
Pattern::Variable {
620-
type_,
634+
type_: variable_type,
621635
name,
622636
location,
623637
origin,
@@ -697,17 +711,24 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
697711
pattern,
698712
location,
699713
} => {
700-
let pattern = self.unify(*pattern, type_, subject_variable);
714+
let pattern_annotation = annotation_type.clone();
715+
let pattern = self.unify(
716+
*pattern,
717+
type_.clone(),
718+
subject_variable,
719+
pattern_annotation,
720+
);
701721

702722
if pattern.is_discard() {
703723
self.problems.warning(Warning::UnusedDiscardPattern {
704724
location,
705725
name: name.clone(),
706726
});
707727
}
728+
let assigned_type = annotation_type.unwrap_or_else(|| pattern.type_().clone());
708729
self.insert_variable(
709730
&name,
710-
pattern.type_().clone(),
731+
assigned_type.clone(),
711732
location,
712733
VariableOrigin {
713734
syntax: VariableSyntax::AssignmentPattern,
@@ -780,28 +801,53 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
780801
self.environment,
781802
) {
782803
Some(arguments) => {
783-
let type_ = arguments
804+
let element_type = arguments
784805
.first()
785806
.expect("Failed to get type argument of List")
786807
.clone();
808+
let list_type = list(element_type.clone());
809+
let annotation_element_type = annotation_type.as_ref().and_then(|annotation| {
810+
annotation
811+
.get_app_arguments(
812+
Publicity::Public,
813+
PRELUDE_PACKAGE_NAME,
814+
PRELUDE_MODULE_NAME,
815+
"List",
816+
1,
817+
self.environment,
818+
)
819+
.and_then(|arguments| arguments.into_iter().next())
820+
});
787821
let elements = elements
788822
.into_iter()
789-
.map(|element| self.unify(element, type_.clone(), None))
823+
.map(|element| {
824+
self.unify(
825+
element,
826+
element_type.clone(),
827+
None,
828+
annotation_element_type.clone(),
829+
)
830+
})
790831
.collect();
791-
let type_ = list(type_);
792832

793833
let tail = tail.map(|tail| {
834+
let tail_annotation = annotation_type.clone();
794835
Box::new(TailPattern {
795836
location: tail.location,
796-
pattern: self.unify(tail.pattern, type_.clone(), None),
837+
pattern: self.unify(
838+
tail.pattern,
839+
list_type.clone(),
840+
None,
841+
tail_annotation,
842+
),
797843
})
798844
});
799845

800846
Pattern::List {
801847
location,
802848
elements,
803849
tail,
804-
type_,
850+
type_: list_type,
805851
}
806852
}
807853

@@ -833,10 +879,22 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
833879
return Pattern::Invalid { location, type_ };
834880
}
835881

882+
let annotation_elements = annotation_type.as_ref().and_then(|annotation| {
883+
match collapse_links(annotation.clone()).deref() {
884+
Type::Tuple { elements } => Some(elements.clone()),
885+
_ => None,
886+
}
887+
});
836888
let elements = elements
837889
.into_iter()
838890
.zip(type_elements)
839-
.map(|(pattern, type_)| self.unify(pattern, type_.clone(), None))
891+
.enumerate()
892+
.map(|(index, (pattern, type_))| {
893+
let annotation = annotation_elements
894+
.as_ref()
895+
.and_then(|elements| elements.get(index).cloned());
896+
self.unify(pattern, type_.clone(), None, annotation)
897+
})
840898
.collect();
841899
Pattern::Tuple { elements, location }
842900
}
@@ -846,10 +904,22 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
846904
.map(|_| self.environment.new_unbound_var())
847905
.collect();
848906
self.unify_types(tuple(elements_types.clone()), type_, location);
907+
let annotation_elements = annotation_type.as_ref().and_then(|annotation| {
908+
match collapse_links(annotation.clone()).deref() {
909+
Type::Tuple { elements } => Some(elements.clone()),
910+
_ => None,
911+
}
912+
});
849913
let elements = elements
850914
.into_iter()
851915
.zip(elements_types)
852-
.map(|(pattern, type_)| self.unify(pattern, type_, None))
916+
.enumerate()
917+
.map(|(index, (pattern, type_))| {
918+
let annotation = annotation_elements
919+
.as_ref()
920+
.and_then(|elements| elements.get(index).cloned());
921+
self.unify(pattern, type_, None, annotation)
922+
})
853923
.collect();
854924
Pattern::Tuple { elements, location }
855925
}
@@ -1227,7 +1297,8 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
12271297
.cloned()
12281298
.unwrap_or_else(|| self.environment.new_unbound_var());
12291299

1230-
let value = self.unify(value, type_, None);
1300+
let annotation = Some(type_.clone());
1301+
let value = self.unify(value, type_, None, annotation);
12311302
CallArg {
12321303
value,
12331304
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)