Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@
containing scientific notation or trailing zeros (i.e. `100` and `1e2`).
([ptdewey](https://github.com/ptdewey))

- Let bindings now continue to assume their annotated type after mismatches,
preventing misleading follow-up errors:
```gleam
pub fn main() {
let x: String = 5 // type error: expected String, got Int
let y: Int = x // valid
let z: String = x // type error: expected String, got Int
}
```
([Adi Salimgereyev](https://github.com/abs0luty))

### Build tool

- The help text displayed by `gleam dev --help`, `gleam test --help`, and
Expand Down
43 changes: 24 additions & 19 deletions compiler-core/src/type_/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2000,24 +2000,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
let type_ = value.type_();
let kind = self.infer_assignment_kind(kind.clone());

// Ensure the pattern matches the type of the value
let mut pattern_typer = pattern::PatternTyper::new(
self.environment,
&self.implementations,
&self.current_function_definition,
&self.hydrator,
self.problems,
PatternPosition::LetAssignment,
);

let pattern = pattern_typer.infer_single_pattern(pattern, &value);

let minimum_required_version = pattern_typer.minimum_required_version;
if minimum_required_version > self.minimum_required_version {
self.minimum_required_version = minimum_required_version;
}

let pattern_typechecked_successfully = !pattern_typer.error_encountered;
let mut annotation_type_override = None;

// Check that any type annotation is accurate.
if let Some(annotation) = &annotation {
Expand All @@ -2026,18 +2009,40 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.map(|type_| self.instantiate(type_, &mut hashmap![]))
{
Ok(annotated_type) => {
let annotation_type_for_pattern = annotated_type.clone();
if let Err(error) = unify(annotated_type, type_.clone())
.map_err(|e| convert_unify_error(e, value.type_defining_location()))
{
self.problems.error(error);
}
annotation_type_override = Some(annotation_type_for_pattern);
}
Err(error) => {
self.problems.error(error);
}
}
}

// Ensure the pattern matches the type of the value
let mut pattern_typer = pattern::PatternTyper::new(
self.environment,
&self.implementations,
&self.current_function_definition,
&self.hydrator,
self.problems,
PatternPosition::LetAssignment,
);

let pattern =
pattern_typer.infer_single_pattern(pattern, &value, annotation_type_override.clone());

let minimum_required_version = pattern_typer.minimum_required_version;
if minimum_required_version > self.minimum_required_version {
self.minimum_required_version = minimum_required_version;
}

let pattern_typechecked_successfully = !pattern_typer.error_encountered;

// The exhaustiveness checker expects patterns to be valid and to type check;
// if they are invalid, it will crash. Therefore, if any errors were found
// when type checking the pattern, we don't perform the exhaustiveness check.
Expand All @@ -2053,7 +2058,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}

let (output, not_exhaustive_error) =
self.check_let_exhaustiveness(location, value.type_(), &pattern);
self.check_let_exhaustiveness(location, type_.clone(), &pattern);

match (&kind, not_exhaustive_error) {
// The pattern is exhaustive in a let assignment, there's no problem here.
Expand Down
107 changes: 89 additions & 18 deletions compiler-core/src/type_/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
for (pattern, subject) in multi_pattern.into_iter().zip(subjects) {
let subject_variable = Self::subject_variable(subject);

let pattern = self.unify(pattern, subject.type_(), subject_variable);
let pattern = self.unify(pattern, subject.type_(), subject_variable, None);
typed_multi.push(pattern);
}

Expand All @@ -330,10 +330,11 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
&mut self,
pattern: UntypedPattern,
subject: &TypedExpr,
annotation_type: Option<Arc<Type>>,
) -> TypedPattern {
let subject_variable = Self::subject_variable(subject);

let typed_pattern = self.unify(pattern, subject.type_(), subject_variable);
let subject_type = subject.type_();
let typed_pattern = self.unify(pattern, subject_type, subject_variable, annotation_type);
self.register_variables();
typed_pattern
}
Expand Down Expand Up @@ -453,7 +454,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
.into_iter()
.map(|option| {
analyse::infer_bit_array_option(option, |value, type_| {
Ok(self.unify(value, type_, None))
Ok(self.unify(value, type_, None, None))
})
})
.try_collect()
Expand Down Expand Up @@ -529,7 +530,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
_ => segment_type,
};

let typed_value = self.unify(*segment.value, type_.clone(), None);
let typed_value = self.unify(*segment.value, type_.clone(), None, None);

match &typed_value {
// We can't directly match on the contents of a `Box`, so we must
Expand Down Expand Up @@ -586,6 +587,18 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
// in the inner scope, we can infer that the `some_wibble` variable is the `Wibble` variant
//
subject_variable: Option<EcoString>,
// An optional type annotation provided for the pattern.
//
// Example:
// ```gleam
// let assert [first, ..rest]: List(String) = numbers
// ```
//
// We pass `List(String)` annotation down so sub-patterns know
// that `first` must be a `String`. We still check `numbers`
// against its real inferred type and emit a mismatch if it is, say,
// `List(Int)`.
annotation_type: Option<Arc<Type>>,
) -> TypedPattern {
match pattern {
Pattern::Discard { name, location, .. } => {
Expand All @@ -595,7 +608,7 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
.discarded_names
.insert(name.clone(), location);
Pattern::Discard {
type_,
type_: annotation_type.unwrap_or_else(|| type_.clone()),
name,
location,
}
Expand All @@ -614,10 +627,11 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
Pattern::Invalid { location, type_ }
}
_ => {
self.insert_variable(&name, type_.clone(), location, origin.clone());
let variable_type = annotation_type.unwrap_or_else(|| type_.clone());
self.insert_variable(&name, variable_type.clone(), location, origin.clone());

Pattern::Variable {
type_,
type_: variable_type,
name,
location,
origin,
Expand Down Expand Up @@ -697,17 +711,24 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
pattern,
location,
} => {
let pattern = self.unify(*pattern, type_, subject_variable);
let pattern_annotation = annotation_type.clone();
let pattern = self.unify(
*pattern,
type_.clone(),
subject_variable,
pattern_annotation,
);

if pattern.is_discard() {
self.problems.warning(Warning::UnusedDiscardPattern {
location,
name: name.clone(),
});
}
let assigned_type = annotation_type.unwrap_or_else(|| pattern.type_().clone());
self.insert_variable(
&name,
pattern.type_().clone(),
assigned_type.clone(),
location,
VariableOrigin {
syntax: VariableSyntax::AssignmentPattern,
Expand Down Expand Up @@ -780,28 +801,53 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
self.environment,
) {
Some(arguments) => {
let type_ = arguments
let element_type = arguments
.first()
.expect("Failed to get type argument of List")
.clone();
let list_type = list(element_type.clone());
let annotation_element_type = annotation_type.as_ref().and_then(|annotation| {
annotation
.get_app_arguments(
Publicity::Public,
PRELUDE_PACKAGE_NAME,
PRELUDE_MODULE_NAME,
"List",
1,
self.environment,
)
.and_then(|arguments| arguments.into_iter().next())
});
let elements = elements
.into_iter()
.map(|element| self.unify(element, type_.clone(), None))
.map(|element| {
self.unify(
element,
element_type.clone(),
None,
annotation_element_type.clone(),
)
})
.collect();
let type_ = list(type_);

let tail = tail.map(|tail| {
let tail_annotation = annotation_type.clone();
Box::new(TailPattern {
location: tail.location,
pattern: self.unify(tail.pattern, type_.clone(), None),
pattern: self.unify(
tail.pattern,
list_type.clone(),
None,
tail_annotation,
),
})
});

Pattern::List {
location,
elements,
tail,
type_,
type_: list_type,
}
}

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

let annotation_elements = annotation_type.as_ref().and_then(|annotation| {
match collapse_links(annotation.clone()).deref() {
Type::Tuple { elements } => Some(elements.clone()),
_ => None,
}
});
let elements = elements
.into_iter()
.zip(type_elements)
.map(|(pattern, type_)| self.unify(pattern, type_.clone(), None))
.enumerate()
.map(|(index, (pattern, type_))| {
let annotation = annotation_elements
.as_ref()
.and_then(|elements| elements.get(index).cloned());
self.unify(pattern, type_.clone(), None, annotation)
})
.collect();
Pattern::Tuple { elements, location }
}
Expand All @@ -846,10 +904,22 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
.map(|_| self.environment.new_unbound_var())
.collect();
self.unify_types(tuple(elements_types.clone()), type_, location);
let annotation_elements = annotation_type.as_ref().and_then(|annotation| {
match collapse_links(annotation.clone()).deref() {
Type::Tuple { elements } => Some(elements.clone()),
_ => None,
}
});
let elements = elements
.into_iter()
.zip(elements_types)
.map(|(pattern, type_)| self.unify(pattern, type_, None))
.enumerate()
.map(|(index, (pattern, type_))| {
let annotation = annotation_elements
.as_ref()
.and_then(|elements| elements.get(index).cloned());
self.unify(pattern, type_, None, annotation)
})
.collect();
Pattern::Tuple { elements, location }
}
Expand Down Expand Up @@ -1227,7 +1297,8 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
.cloned()
.unwrap_or_else(|| self.environment.new_unbound_var());

let value = self.unify(value, type_, None);
let annotation = Some(type_.clone());
let value = self.unify(value, type_, None, annotation);
CallArg {
value,
location,
Expand Down
31 changes: 31 additions & 0 deletions compiler-core/src/type_/tests/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,37 @@ fn module_could_not_unify6() {
assert_module_error!("fn main() { let x: String = 5 x }");
}

#[test]
fn module_could_not_unify_let_binding_annotation_follow_up1() {
assert_module_error!(
"
fn main() {
let x: String = 5
let y: Int = x
let z: String = x
}
"
);
}

#[test]
fn module_could_not_unify_let_binding_annotation_follow_up2() {
assert_module_error!(
"
fn main() {
let assert #(
a,
[[_, ..] as b, ..],
_ as c
): #(String, List(List(Int)), String) = #(1, [[\"Hello\"]], 3)

let d: String = a <> c
let e: List(Int) = [0, ..b]
}
"
);
}

#[test]
fn module_could_not_unify7() {
assert_module_error!("fn main() { let assert 5 = \"\" }");
Expand Down
27 changes: 0 additions & 27 deletions compiler-core/src/type_/tests/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,33 +261,6 @@ pub fn main() {
);
}

#[test]
fn multiple_bad_statement_assignment_with_annotation_fault_tolerance() {
assert_module_error!(
r#"
pub fn main() {
let a: Int = "not an int"
let b: String = 1
let c = a + 2
}
"#
);
}

#[test]
fn multiple_bad_statement_assignment_with_annotation_fault_tolerance2() {
assert_module_error!(
r#"
pub fn main() {
// Since the value is invalid the type is the annotation
let a: Int = Junk
let b: String = 1
let c = a + 2
}
"#
);
}

#[test]
fn multiple_bad_statement_assignment_with_pattern_fault_tolerance2() {
assert_module_error!(
Expand Down
Loading
Loading