mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Restrict usages of type variables in non-generalized contexts
Type variables can only be used on functions (and in number literals as a carve-out for now). In all other cases, a type variable takes on a single, concrete type based on later usages. This check emits errors when this is violated. The implementation is to check the rank of a variable after it could be generalized. If the variable is not generalized but annotated as a type variable, emit an error.
This commit is contained in:
parent
f5961cbb22
commit
a0461679dd
13 changed files with 230 additions and 114 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3282,6 +3282,7 @@ dependencies = [
|
||||||
name = "roc_types"
|
name = "roc_types"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_debug_flags",
|
"roc_debug_flags",
|
||||||
|
|
|
@ -532,7 +532,7 @@ pi = 3.14159265358979323846264338327950288419716939937510
|
||||||
|
|
||||||
## Circle constant (τ)
|
## Circle constant (τ)
|
||||||
tau : Frac *
|
tau : Frac *
|
||||||
tau = 2 * pi
|
tau = 6.2831853071795864769252867665590057683943387987502
|
||||||
|
|
||||||
# ------- Functions
|
# ------- Functions
|
||||||
## Convert a number to a [Str].
|
## Convert a number to a [Str].
|
||||||
|
|
|
@ -915,7 +915,7 @@ pub struct DefTypes {
|
||||||
pub loc_symbols: Slice<(Symbol, Region)>,
|
pub loc_symbols: Slice<(Symbol, Region)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Generalizable(pub bool);
|
pub struct Generalizable(pub bool);
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -95,22 +95,6 @@ flags! {
|
||||||
/// Prints all type variables entered for fixpoint-fixing.
|
/// Prints all type variables entered for fixpoint-fixing.
|
||||||
ROC_PRINT_FIXPOINT_FIXING
|
ROC_PRINT_FIXPOINT_FIXING
|
||||||
|
|
||||||
/// Verifies that after let-generalization of a def, any rigid variables in the type annotation
|
|
||||||
/// of the def are indeed generalized.
|
|
||||||
///
|
|
||||||
/// Note that rigids need not always be generalized in a def. For example, they may be
|
|
||||||
/// constrained by a type from a lower rank, as `b` is in the following def:
|
|
||||||
///
|
|
||||||
/// F a : { foo : a }
|
|
||||||
/// foo = \arg ->
|
|
||||||
/// x : F b
|
|
||||||
/// x = arg
|
|
||||||
/// x.foo
|
|
||||||
///
|
|
||||||
/// Instead, this flag is useful for checking that in general, introduction is correct, when
|
|
||||||
/// chainging how defs are constrained.
|
|
||||||
ROC_VERIFY_RIGID_LET_GENERALIZED
|
|
||||||
|
|
||||||
/// Verifies that an `occurs` check indeed only contains non-recursive types that need to be
|
/// Verifies that an `occurs` check indeed only contains non-recursive types that need to be
|
||||||
/// fixed-up with one new recursion variable.
|
/// fixed-up with one new recursion variable.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1519,18 +1519,18 @@ mod test_reporting {
|
||||||
from_annotation_if,
|
from_annotation_if,
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
x : Num.Int *
|
x : Num.Int _
|
||||||
x = if Bool.true then 3.14 else 4
|
x = if Bool.true then 3.14 else 4
|
||||||
|
|
||||||
x
|
x
|
||||||
"
|
"
|
||||||
),
|
),
|
||||||
@r"
|
@r###"
|
||||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||||
|
|
||||||
Something is off with the `then` branch of this `if` expression:
|
Something is off with the `then` branch of this `if` expression:
|
||||||
|
|
||||||
4│ x : Num.Int *
|
4│ x : Num.Int _
|
||||||
5│ x = if Bool.true then 3.14 else 4
|
5│ x = if Bool.true then 3.14 else 4
|
||||||
^^^^
|
^^^^
|
||||||
|
|
||||||
|
@ -1544,14 +1544,14 @@ mod test_reporting {
|
||||||
|
|
||||||
Tip: You can convert between integers and fractions using functions
|
Tip: You can convert between integers and fractions using functions
|
||||||
like `Num.toFrac` and `Num.round`.
|
like `Num.toFrac` and `Num.round`.
|
||||||
"
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
test_report!(
|
test_report!(
|
||||||
from_annotation_when,
|
from_annotation_when,
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
x : Num.Int *
|
x : Num.Int _
|
||||||
x =
|
x =
|
||||||
when True is
|
when True is
|
||||||
_ -> 3.14
|
_ -> 3.14
|
||||||
|
@ -1559,12 +1559,12 @@ mod test_reporting {
|
||||||
x
|
x
|
||||||
"
|
"
|
||||||
),
|
),
|
||||||
@r"
|
@r###"
|
||||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||||
|
|
||||||
Something is off with the body of the `x` definition:
|
Something is off with the body of the `x` definition:
|
||||||
|
|
||||||
4│ x : Num.Int *
|
4│ x : Num.Int _
|
||||||
5│ x =
|
5│ x =
|
||||||
6│> when True is
|
6│> when True is
|
||||||
7│> _ -> 3.14
|
7│> _ -> 3.14
|
||||||
|
@ -1579,7 +1579,7 @@ mod test_reporting {
|
||||||
|
|
||||||
Tip: You can convert between integers and fractions using functions
|
Tip: You can convert between integers and fractions using functions
|
||||||
like `Num.toFrac` and `Num.round`.
|
like `Num.toFrac` and `Num.round`.
|
||||||
"
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
test_report!(
|
test_report!(
|
||||||
|
@ -1907,7 +1907,7 @@ mod test_reporting {
|
||||||
from_annotation_complex_pattern,
|
from_annotation_complex_pattern,
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
{ x } : { x : Num.Int * }
|
{ x } : { x : Num.Int _ }
|
||||||
{ x } = { x: 4.0 }
|
{ x } = { x: 4.0 }
|
||||||
|
|
||||||
x
|
x
|
||||||
|
@ -1918,7 +1918,7 @@ mod test_reporting {
|
||||||
|
|
||||||
Something is off with the body of this definition:
|
Something is off with the body of this definition:
|
||||||
|
|
||||||
4│ { x } : { x : Num.Int * }
|
4│ { x } : { x : Num.Int _ }
|
||||||
5│ { x } = { x: 4.0 }
|
5│ { x } = { x: 4.0 }
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -2044,18 +2044,18 @@ mod test_reporting {
|
||||||
missing_fields,
|
missing_fields,
|
||||||
indoc!(
|
indoc!(
|
||||||
r"
|
r"
|
||||||
x : { a : Num.Int *, b : Num.Frac *, c : Str }
|
x : { a : Num.Int _, b : Num.Frac _, c : Str }
|
||||||
x = { b: 4.0 }
|
x = { b: 4.0 }
|
||||||
|
|
||||||
x
|
x
|
||||||
"
|
"
|
||||||
),
|
),
|
||||||
@r"
|
@r###"
|
||||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||||
|
|
||||||
Something is off with the body of the `x` definition:
|
Something is off with the body of the `x` definition:
|
||||||
|
|
||||||
4│ x : { a : Num.Int *, b : Num.Frac *, c : Str }
|
4│ x : { a : Num.Int _, b : Num.Frac _, c : Str }
|
||||||
5│ x = { b: 4.0 }
|
5│ x = { b: 4.0 }
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -2072,7 +2072,7 @@ mod test_reporting {
|
||||||
}
|
}
|
||||||
|
|
||||||
Tip: Looks like the c and a fields are missing.
|
Tip: Looks like the c and a fields are missing.
|
||||||
"
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
// this previously reported the message below, not sure which is better
|
// this previously reported the message below, not sure which is better
|
||||||
|
@ -3445,7 +3445,7 @@ mod test_reporting {
|
||||||
x : AList Num.I64 Num.I64
|
x : AList Num.I64 Num.I64
|
||||||
x = ACons 0 (BCons 1 (ACons "foo" BNil ))
|
x = ACons 0 (BCons 1 (ACons "foo" BNil ))
|
||||||
|
|
||||||
y : BList a a
|
y : BList _ _
|
||||||
y = BNil
|
y = BNil
|
||||||
|
|
||||||
{ x, y }
|
{ x, y }
|
||||||
|
@ -4186,9 +4186,8 @@ mod test_reporting {
|
||||||
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
|
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
|
||||||
|
|
||||||
# Create an empty dictionary.
|
# Create an empty dictionary.
|
||||||
empty : RBTree k v
|
empty : {} -> RBTree k v
|
||||||
empty =
|
empty = \{} -> Empty
|
||||||
Empty
|
|
||||||
|
|
||||||
empty
|
empty
|
||||||
"
|
"
|
||||||
|
@ -11129,10 +11128,10 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
import Decode exposing [decoder]
|
import Decode exposing [decoder]
|
||||||
|
|
||||||
main =
|
myDecoder : Decoder (_ -> _) _
|
||||||
myDecoder : Decoder (a -> a) fmt where fmt implements DecoderFormatting
|
myDecoder = decoder
|
||||||
myDecoder = decoder
|
|
||||||
|
|
||||||
|
main =
|
||||||
myDecoder
|
myDecoder
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
|
@ -11141,12 +11140,12 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
This expression has a type that does not implement the abilities it's expected to:
|
This expression has a type that does not implement the abilities it's expected to:
|
||||||
|
|
||||||
7│ myDecoder = decoder
|
6│ myDecoder = decoder
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
I can't generate an implementation of the `Decoding` ability for
|
I can't generate an implementation of the `Decoding` ability for
|
||||||
|
|
||||||
a -> a
|
* -> *
|
||||||
|
|
||||||
Note: `Decoding` cannot be generated for functions.
|
Note: `Decoding` cannot be generated for functions.
|
||||||
"###
|
"###
|
||||||
|
@ -11162,10 +11161,10 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
A := {}
|
A := {}
|
||||||
|
|
||||||
main =
|
myDecoder : Decoder {x : A} _
|
||||||
myDecoder : Decoder {x : A} fmt where fmt implements DecoderFormatting
|
myDecoder = decoder
|
||||||
myDecoder = decoder
|
|
||||||
|
|
||||||
|
main =
|
||||||
myDecoder
|
myDecoder
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
|
@ -11174,8 +11173,8 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
This expression has a type that does not implement the abilities it's expected to:
|
This expression has a type that does not implement the abilities it's expected to:
|
||||||
|
|
||||||
9│ myDecoder = decoder
|
8│ myDecoder = decoder
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
I can't generate an implementation of the `Decoding` ability for
|
I can't generate an implementation of the `Decoding` ability for
|
||||||
|
|
||||||
|
@ -11425,11 +11424,10 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
import Decode exposing [decoder]
|
import Decode exposing [decoder]
|
||||||
|
|
||||||
main =
|
myDecoder : Decoder {x : Str, y ? Str} _
|
||||||
myDecoder : Decoder {x : Str, y ? Str} fmt where fmt implements DecoderFormatting
|
myDecoder = decoder
|
||||||
myDecoder = decoder
|
|
||||||
|
|
||||||
myDecoder
|
main = myDecoder
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
@r###"
|
@r###"
|
||||||
|
@ -11437,8 +11435,8 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
This expression has a type that does not implement the abilities it's expected to:
|
This expression has a type that does not implement the abilities it's expected to:
|
||||||
|
|
||||||
7│ myDecoder = decoder
|
6│ myDecoder = decoder
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
I can't generate an implementation of the `Decoding` ability for
|
I can't generate an implementation of the `Decoding` ability for
|
||||||
|
|
||||||
|
@ -14047,11 +14045,10 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
import Decode exposing [decoder]
|
import Decode exposing [decoder]
|
||||||
|
|
||||||
main =
|
myDecoder : Decoder (U32, Str) _
|
||||||
myDecoder : Decoder (U32, Str) fmt where fmt implements DecoderFormatting
|
myDecoder = decoder
|
||||||
myDecoder = decoder
|
|
||||||
|
|
||||||
myDecoder
|
main = myDecoder
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -14064,11 +14061,10 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
import Decode exposing [decoder]
|
import Decode exposing [decoder]
|
||||||
|
|
||||||
main =
|
myDecoder : Decoder (U32, {} -> {}) _
|
||||||
myDecoder : Decoder (U32, {} -> {}) fmt where fmt implements DecoderFormatting
|
myDecoder = decoder
|
||||||
myDecoder = decoder
|
|
||||||
|
|
||||||
myDecoder
|
main = myDecoder
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
@r###"
|
@r###"
|
||||||
|
@ -14076,8 +14072,8 @@ All branches in an `if` must have the same type!
|
||||||
|
|
||||||
This expression has a type that does not implement the abilities it's expected to:
|
This expression has a type that does not implement the abilities it's expected to:
|
||||||
|
|
||||||
7│ myDecoder = decoder
|
6│ myDecoder = decoder
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
I can't generate an implementation of the `Decoding` ability for
|
I can't generate an implementation of the `Decoding` ability for
|
||||||
|
|
||||||
|
@ -15933,4 +15929,66 @@ All branches in an `if` must have the same type!
|
||||||
Str -> {}
|
Str -> {}
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_report!(
|
||||||
|
invalid_generic_literal,
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
module [v]
|
||||||
|
|
||||||
|
v : *
|
||||||
|
v = 1
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
@r###"
|
||||||
|
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||||
|
|
||||||
|
Something is off with the body of the `v` definition:
|
||||||
|
|
||||||
|
3│ v : *
|
||||||
|
4│ v = 1
|
||||||
|
^
|
||||||
|
|
||||||
|
The body is a number of type:
|
||||||
|
|
||||||
|
Num *
|
||||||
|
|
||||||
|
But the type annotation on `v` says it should be:
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
Tip: The type annotation uses the type variable `*` to say that this
|
||||||
|
definition can produce any type of value. But in the body I see that
|
||||||
|
it will only produce a `Num` value of a single specific type. Maybe
|
||||||
|
change the type annotation to be more specific? Maybe change the code
|
||||||
|
to be more general?
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
test_report!(
|
||||||
|
invalid_generic_literal_list,
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
module [v]
|
||||||
|
|
||||||
|
v : List *
|
||||||
|
v = []
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
@r###"
|
||||||
|
── TYPE VARIABLE IS NOT GENERIC in /code/proj/Main.roc ─────────────────────────
|
||||||
|
|
||||||
|
This type variable has a single type:
|
||||||
|
|
||||||
|
3│ v : List *
|
||||||
|
^
|
||||||
|
|
||||||
|
Type variables tell me that they can be used with any type, but they
|
||||||
|
can only be used with functions. All other values have exactly one
|
||||||
|
type.
|
||||||
|
|
||||||
|
Hint: If you would like the type to be inferred for you, use an
|
||||||
|
underscore _ instead.
|
||||||
|
"###
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,8 @@ pub fn remove_module_param_arguments(
|
||||||
| TypeError::ExpectedEffectful(_, _)
|
| TypeError::ExpectedEffectful(_, _)
|
||||||
| TypeError::UnsuffixedEffectfulFunction(_, _)
|
| TypeError::UnsuffixedEffectfulFunction(_, _)
|
||||||
| TypeError::SuffixedPureFunction(_, _)
|
| TypeError::SuffixedPureFunction(_, _)
|
||||||
| TypeError::InvalidTryTarget(_, _, _) => {}
|
| TypeError::InvalidTryTarget(_, _, _)
|
||||||
|
| TypeError::TypeIsNotGeneralized(..) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,6 +214,7 @@ fn drop_last_argument(err_type: &mut ErrorType) {
|
||||||
| ErrorType::Alias(_, _, _, _)
|
| ErrorType::Alias(_, _, _, _)
|
||||||
| ErrorType::Range(_)
|
| ErrorType::Range(_)
|
||||||
| ErrorType::Error
|
| ErrorType::Error
|
||||||
| ErrorType::EffectfulFunc => {}
|
| ErrorType::EffectfulFunc
|
||||||
|
| ErrorType::InferenceVar => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,12 @@ use bumpalo::Bump;
|
||||||
use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo};
|
use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo};
|
||||||
use roc_can::constraint::Constraint::{self, *};
|
use roc_can::constraint::Constraint::{self, *};
|
||||||
use roc_can::constraint::{
|
use roc_can::constraint::{
|
||||||
Cycle, FxCallConstraint, FxSuffixConstraint, FxSuffixKind, LetConstraint, OpportunisticResolve,
|
Cycle, FxCallConstraint, FxSuffixConstraint, FxSuffixKind, Generalizable, LetConstraint,
|
||||||
TryTargetConstraint,
|
OpportunisticResolve, TryTargetConstraint,
|
||||||
};
|
};
|
||||||
use roc_can::expected::{Expected, PExpected};
|
use roc_can::expected::{Expected, PExpected};
|
||||||
use roc_can::module::ModuleParams;
|
use roc_can::module::ModuleParams;
|
||||||
use roc_collections::{VecMap, VecSet};
|
use roc_collections::{VecMap, VecSet};
|
||||||
use roc_debug_flags::dbg_do;
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED;
|
|
||||||
use roc_error_macros::internal_error;
|
use roc_error_macros::internal_error;
|
||||||
use roc_module::ident::IdentSuffix;
|
use roc_module::ident::IdentSuffix;
|
||||||
use roc_module::symbol::{ModuleId, Symbol};
|
use roc_module::symbol::{ModuleId, Symbol};
|
||||||
|
@ -32,8 +29,8 @@ use roc_region::all::{Loc, Region};
|
||||||
use roc_solve_problem::TypeError;
|
use roc_solve_problem::TypeError;
|
||||||
use roc_solve_schema::UnificationMode;
|
use roc_solve_schema::UnificationMode;
|
||||||
use roc_types::subs::{
|
use roc_types::subs::{
|
||||||
self, Content, FlatType, GetSubsSlice, Mark, OptVariable, Rank, Subs, TagExt, UlsOfVar,
|
self, Content, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, Rank, Subs, TagExt,
|
||||||
Variable,
|
UlsOfVar, Variable,
|
||||||
};
|
};
|
||||||
use roc_types::types::{Category, Polarity, Reason, RecordField, Type, TypeExtension, Types, Uls};
|
use roc_types::types::{Category, Polarity, Reason, RecordField, Type, TypeExtension, Types, Uls};
|
||||||
use roc_unify::unify::{
|
use roc_unify::unify::{
|
||||||
|
@ -356,29 +353,13 @@ fn solve(
|
||||||
generalize(env, young_mark, visit_mark, rank.next());
|
generalize(env, young_mark, visit_mark, rank.next());
|
||||||
debug_assert!(env.pools.get(rank.next()).is_empty(), "variables left over in let-binding scope, but they should all be in a lower scope or generalized now");
|
debug_assert!(env.pools.get(rank.next()).is_empty(), "variables left over in let-binding scope, but they should all be in a lower scope or generalized now");
|
||||||
|
|
||||||
// check that things went well
|
let named_variables = &env.constraints[let_con.rigid_vars];
|
||||||
dbg_do!(ROC_VERIFY_RIGID_LET_GENERALIZED, {
|
check_named_variables_are_generalized(
|
||||||
let rigid_vars = &env.constraints[let_con.rigid_vars];
|
env,
|
||||||
|
problems,
|
||||||
// NOTE the `subs.redundant` check does not come from elm.
|
named_variables,
|
||||||
// It's unclear whether this is a bug with our implementation
|
let_con.generalizable,
|
||||||
// (something is redundant that shouldn't be)
|
);
|
||||||
// or that it just never came up in elm.
|
|
||||||
let mut it = rigid_vars
|
|
||||||
.iter()
|
|
||||||
.filter(|loc_var| {
|
|
||||||
let var = loc_var.value;
|
|
||||||
!env.subs.redundant(var) && env.subs.get_rank(var) != Rank::GENERALIZED
|
|
||||||
})
|
|
||||||
.peekable();
|
|
||||||
|
|
||||||
if it.peek().is_some() {
|
|
||||||
let failing: Vec<_> = it.collect();
|
|
||||||
println!("Rigids {:?}", &rigid_vars);
|
|
||||||
println!("Failing {failing:?}");
|
|
||||||
debug_assert!(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut new_scope = scope.clone();
|
let mut new_scope = scope.clone();
|
||||||
for (symbol, loc_var) in local_def_vars.iter() {
|
for (symbol, loc_var) in local_def_vars.iter() {
|
||||||
|
@ -1636,6 +1617,30 @@ fn solve(
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_named_variables_are_generalized(
|
||||||
|
env: &mut InferenceEnv<'_>,
|
||||||
|
problems: &mut Vec<TypeError>,
|
||||||
|
named_variables: &[Loc<Variable>],
|
||||||
|
generalizable: Generalizable,
|
||||||
|
) {
|
||||||
|
for loc_var in named_variables {
|
||||||
|
let is_generalized = env.subs.get_rank(loc_var.value) == Rank::GENERALIZED;
|
||||||
|
if !is_generalized {
|
||||||
|
// TODO: should be OF_PATTERN if on the LHS of a function, otherwise OF_VALUE.
|
||||||
|
let polarity = Polarity::OF_VALUE;
|
||||||
|
let ctx = ErrorTypeContext::NON_GENERALIZED_AS_INFERRED;
|
||||||
|
let error_type = env
|
||||||
|
.subs
|
||||||
|
.var_to_error_type_contextual(loc_var.value, ctx, polarity);
|
||||||
|
problems.push(TypeError::TypeIsNotGeneralized(
|
||||||
|
loc_var.region,
|
||||||
|
error_type,
|
||||||
|
generalizable,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn solve_suffix_fx(
|
fn solve_suffix_fx(
|
||||||
env: &mut InferenceEnv<'_>,
|
env: &mut InferenceEnv<'_>,
|
||||||
problems: &mut Vec<TypeError>,
|
problems: &mut Vec<TypeError>,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Provides types to describe problems that can occur during solving.
|
//! Provides types to describe problems that can occur during solving.
|
||||||
use std::{path::PathBuf, str::Utf8Error};
|
use std::{path::PathBuf, str::Utf8Error};
|
||||||
|
|
||||||
use roc_can::constraint::{ExpectEffectfulReason, FxSuffixKind};
|
use roc_can::constraint::{ExpectEffectfulReason, FxSuffixKind, Generalizable};
|
||||||
use roc_can::expr::TryKind;
|
use roc_can::expr::TryKind;
|
||||||
use roc_can::{
|
use roc_can::{
|
||||||
constraint::FxCallKind,
|
constraint::FxCallKind,
|
||||||
|
@ -50,6 +50,7 @@ pub enum TypeError {
|
||||||
UnsuffixedEffectfulFunction(Region, FxSuffixKind),
|
UnsuffixedEffectfulFunction(Region, FxSuffixKind),
|
||||||
SuffixedPureFunction(Region, FxSuffixKind),
|
SuffixedPureFunction(Region, FxSuffixKind),
|
||||||
InvalidTryTarget(Region, ErrorType, TryKind),
|
InvalidTryTarget(Region, ErrorType, TryKind),
|
||||||
|
TypeIsNotGeneralized(Region, ErrorType, Generalizable),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeError {
|
impl TypeError {
|
||||||
|
@ -80,6 +81,7 @@ impl TypeError {
|
||||||
TypeError::UnsuffixedEffectfulFunction(_, _) => Warning,
|
TypeError::UnsuffixedEffectfulFunction(_, _) => Warning,
|
||||||
TypeError::SuffixedPureFunction(_, _) => Warning,
|
TypeError::SuffixedPureFunction(_, _) => Warning,
|
||||||
TypeError::InvalidTryTarget(_, _, _) => RuntimeError,
|
TypeError::InvalidTryTarget(_, _, _) => RuntimeError,
|
||||||
|
TypeError::TypeIsNotGeneralized(..) => RuntimeError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +103,8 @@ impl TypeError {
|
||||||
| TypeError::ExpectedEffectful(region, _)
|
| TypeError::ExpectedEffectful(region, _)
|
||||||
| TypeError::UnsuffixedEffectfulFunction(region, _)
|
| TypeError::UnsuffixedEffectfulFunction(region, _)
|
||||||
| TypeError::SuffixedPureFunction(region, _)
|
| TypeError::SuffixedPureFunction(region, _)
|
||||||
| TypeError::InvalidTryTarget(region, _, _) => Some(*region),
|
| TypeError::InvalidTryTarget(region, _, _)
|
||||||
|
| TypeError::TypeIsNotGeneralized(region, _, _) => Some(*region),
|
||||||
TypeError::UnfulfilledAbility(ab, ..) => ab.region(),
|
TypeError::UnfulfilledAbility(ab, ..) => ab.region(),
|
||||||
TypeError::Exhaustive(e) => Some(e.region()),
|
TypeError::Exhaustive(e) => Some(e.region()),
|
||||||
TypeError::CircularDef(c) => c.first().map(|ce| ce.symbol_region),
|
TypeError::CircularDef(c) => c.first().map(|ce| ce.symbol_region),
|
||||||
|
|
|
@ -22,3 +22,4 @@ bumpalo.workspace = true
|
||||||
static_assertions.workspace = true
|
static_assertions.workspace = true
|
||||||
|
|
||||||
soa.workspace = true
|
soa.workspace = true
|
||||||
|
bitflags.workspace = true
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::types::{
|
||||||
Polarity, RecordField, RecordFieldsError, TupleElemsError, TypeExt, Uls,
|
Polarity, RecordField, RecordFieldsError, TupleElemsError, TypeExt, Uls,
|
||||||
};
|
};
|
||||||
use crate::unification_table::{self, UnificationTable};
|
use crate::unification_table::{self, UnificationTable};
|
||||||
|
use bitflags::bitflags;
|
||||||
use roc_collections::all::{FnvMap, ImMap, ImSet, MutSet, SendMap};
|
use roc_collections::all::{FnvMap, ImMap, ImSet, MutSet, SendMap};
|
||||||
use roc_collections::{VecMap, VecSet};
|
use roc_collections::{VecMap, VecSet};
|
||||||
use roc_error_macros::internal_error;
|
use roc_error_macros::internal_error;
|
||||||
|
@ -50,10 +51,24 @@ impl fmt::Debug for Mark {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
bitflags! {
|
||||||
pub enum ErrorTypeContext {
|
pub struct ErrorTypeContext : u8 {
|
||||||
None,
|
const NONE = 1 << 0;
|
||||||
ExpandRanges,
|
/// List all number types that satisfy number range constraints.
|
||||||
|
const EXPAND_RANGES = 1 << 1;
|
||||||
|
/// Re-write non-generalized types like to inference variables.
|
||||||
|
const NON_GENERALIZED_AS_INFERRED = 1 << 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorTypeContext {
|
||||||
|
fn expand_ranges(&self) -> bool {
|
||||||
|
self.contains(Self::EXPAND_RANGES)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn non_generalized_as_inferred(&self) -> bool {
|
||||||
|
self.contains(Self::NON_GENERALIZED_AS_INFERRED)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ErrorTypeState {
|
struct ErrorTypeState {
|
||||||
|
@ -2055,7 +2070,7 @@ impl Subs {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn var_to_error_type(&mut self, var: Variable, observed_pol: Polarity) -> ErrorType {
|
pub fn var_to_error_type(&mut self, var: Variable, observed_pol: Polarity) -> ErrorType {
|
||||||
self.var_to_error_type_contextual(var, ErrorTypeContext::None, observed_pol)
|
self.var_to_error_type_contextual(var, ErrorTypeContext::empty(), observed_pol)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn var_to_error_type_contextual(
|
pub fn var_to_error_type_contextual(
|
||||||
|
@ -4020,6 +4035,13 @@ fn content_to_err_type(
|
||||||
match content {
|
match content {
|
||||||
Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type, pol),
|
Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type, pol),
|
||||||
|
|
||||||
|
RigidVar(..) | RigidAbleVar(..)
|
||||||
|
if state.context.non_generalized_as_inferred()
|
||||||
|
&& subs.get_rank(var) != Rank::GENERALIZED =>
|
||||||
|
{
|
||||||
|
ErrorType::InferenceVar
|
||||||
|
}
|
||||||
|
|
||||||
FlexVar(opt_name) => {
|
FlexVar(opt_name) => {
|
||||||
let name = match opt_name {
|
let name = match opt_name {
|
||||||
Some(name_index) => subs.field_names[name_index.index()].clone(),
|
Some(name_index) => subs.field_names[name_index.index()].clone(),
|
||||||
|
@ -4123,7 +4145,7 @@ fn content_to_err_type(
|
||||||
}
|
}
|
||||||
|
|
||||||
RangedNumber(range) => {
|
RangedNumber(range) => {
|
||||||
if state.context == ErrorTypeContext::ExpandRanges {
|
if state.context.expand_ranges() {
|
||||||
let mut types = Vec::new();
|
let mut types = Vec::new();
|
||||||
for var in range.variable_slice() {
|
for var in range.variable_slice() {
|
||||||
types.push(var_to_err_type(subs, state, *var, pol));
|
types.push(var_to_err_type(subs, state, *var, pol));
|
||||||
|
|
|
@ -3679,6 +3679,7 @@ pub enum ErrorType {
|
||||||
/// If the name was auto-generated, it will start with a `#`.
|
/// If the name was auto-generated, it will start with a `#`.
|
||||||
FlexVar(Lowercase),
|
FlexVar(Lowercase),
|
||||||
RigidVar(Lowercase),
|
RigidVar(Lowercase),
|
||||||
|
InferenceVar,
|
||||||
EffectfulFunc,
|
EffectfulFunc,
|
||||||
/// If the name was auto-generated, it will start with a `#`.
|
/// If the name was auto-generated, it will start with a `#`.
|
||||||
FlexAbleVar(Lowercase, AbilitySet),
|
FlexAbleVar(Lowercase, AbilitySet),
|
||||||
|
@ -3733,6 +3734,7 @@ impl ErrorType {
|
||||||
FlexVar(v) | RigidVar(v) | FlexAbleVar(v, _) | RigidAbleVar(v, _) => {
|
FlexVar(v) | RigidVar(v) | FlexAbleVar(v, _) | RigidAbleVar(v, _) => {
|
||||||
taken.insert(v.clone());
|
taken.insert(v.clone());
|
||||||
}
|
}
|
||||||
|
InferenceVar => {}
|
||||||
Record(fields, ext) => {
|
Record(fields, ext) => {
|
||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -3912,13 +3914,14 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
||||||
Infinite => buf.push('∞'),
|
Infinite => buf.push('∞'),
|
||||||
Error => buf.push('?'),
|
Error => buf.push('?'),
|
||||||
FlexVar(name) | RigidVar(name) => buf.push_str(name.as_str()),
|
FlexVar(name) | RigidVar(name) => buf.push_str(name.as_str()),
|
||||||
FlexAbleVar(name, symbol) | RigidAbleVar(name, symbol) => {
|
InferenceVar => buf.push('_'),
|
||||||
|
FlexAbleVar(name, abilities) | RigidAbleVar(name, abilities) => {
|
||||||
let write_parens = parens == Parens::InTypeParam;
|
let write_parens = parens == Parens::InTypeParam;
|
||||||
if write_parens {
|
if write_parens {
|
||||||
buf.push('(');
|
buf.push('(');
|
||||||
}
|
}
|
||||||
buf.push_str(name.as_str());
|
buf.push_str(name.as_str());
|
||||||
write!(buf, "{} {:?}", roc_parse::keyword::IMPLEMENTS, symbol).unwrap();
|
write!(buf, "{} {:?}", roc_parse::keyword::IMPLEMENTS, abilities).unwrap();
|
||||||
if write_parens {
|
if write_parens {
|
||||||
buf.push(')');
|
buf.push(')');
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,9 +356,9 @@ fn unify_help<M: MetaCollector>(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) {
|
let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) {
|
||||||
ErrorTypeContext::ExpandRanges
|
ErrorTypeContext::EXPAND_RANGES
|
||||||
} else {
|
} else {
|
||||||
ErrorTypeContext::None
|
ErrorTypeContext::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
let type1 = env.var_to_error_type_contextual(var1, error_context, observed_pol);
|
let type1 = env.var_to_error_type_contextual(var1, error_context, observed_pol);
|
||||||
|
|
|
@ -513,6 +513,38 @@ pub fn type_problem<'b>(
|
||||||
severity,
|
severity,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
TypeIsNotGeneralized(region, actual_type, generalizable) => {
|
||||||
|
let doc = alloc.stack([
|
||||||
|
alloc.reflow("This type variable has a single type:"),
|
||||||
|
alloc.region(lines.convert_region(region), severity),
|
||||||
|
if !generalizable.0 {
|
||||||
|
alloc.concat([
|
||||||
|
alloc.reflow("Type variables tell me that they can be used with any type, but they can only be used with functions. All other values have exactly one type."),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
alloc.concat([
|
||||||
|
alloc.reflow("Type variables tell me that they can be used as any type."),
|
||||||
|
alloc.reflow("But, I found that this type can only be used as this single type:"),
|
||||||
|
alloc.type_block(to_doc(alloc, Parens::Unnecessary, actual_type).0)
|
||||||
|
])
|
||||||
|
},
|
||||||
|
alloc.concat([
|
||||||
|
alloc.hint(""),
|
||||||
|
alloc.reflow(
|
||||||
|
"If you would like the type to be inferred for you, use an underscore ",
|
||||||
|
),
|
||||||
|
alloc.type_str("_"),
|
||||||
|
alloc.reflow(" instead."),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Some(Report {
|
||||||
|
title: "TYPE VARIABLE IS NOT GENERIC".to_string(),
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
severity,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,9 +688,9 @@ fn underivable_hint<'b>(
|
||||||
},
|
},
|
||||||
]))),
|
]))),
|
||||||
NotDerivableContext::UnboundVar => {
|
NotDerivableContext::UnboundVar => {
|
||||||
let v = match typ {
|
let formatted_var = match typ {
|
||||||
ErrorType::FlexVar(v) => v,
|
ErrorType::FlexVar(v) | ErrorType::RigidVar(v) => alloc.type_variable(v.clone()),
|
||||||
ErrorType::RigidVar(v) => v,
|
ErrorType::InferenceVar => alloc.type_str("_"),
|
||||||
_ => internal_error!("unbound variable context only applicable for variables"),
|
_ => internal_error!("unbound variable context only applicable for variables"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -671,7 +703,7 @@ fn underivable_hint<'b>(
|
||||||
alloc.inline_type_block(alloc.concat([
|
alloc.inline_type_block(alloc.concat([
|
||||||
alloc.keyword(roc_parse::keyword::WHERE),
|
alloc.keyword(roc_parse::keyword::WHERE),
|
||||||
alloc.space(),
|
alloc.space(),
|
||||||
alloc.type_variable(v.clone()),
|
formatted_var,
|
||||||
alloc.space(),
|
alloc.space(),
|
||||||
alloc.keyword(roc_parse::keyword::IMPLEMENTS),
|
alloc.keyword(roc_parse::keyword::IMPLEMENTS),
|
||||||
alloc.space(),
|
alloc.space(),
|
||||||
|
@ -2868,6 +2900,7 @@ fn to_doc_help<'b>(
|
||||||
),
|
),
|
||||||
Infinite => alloc.text("∞"),
|
Infinite => alloc.text("∞"),
|
||||||
Error => alloc.text("?"),
|
Error => alloc.text("?"),
|
||||||
|
InferenceVar => alloc.text("_"),
|
||||||
|
|
||||||
FlexVar(lowercase) if is_generated_name(&lowercase) => {
|
FlexVar(lowercase) if is_generated_name(&lowercase) => {
|
||||||
let &usages = gen_usages
|
let &usages = gen_usages
|
||||||
|
@ -3082,6 +3115,7 @@ fn count_generated_name_usages<'a>(
|
||||||
RigidVar(name) | RigidAbleVar(name, _) => {
|
RigidVar(name) | RigidAbleVar(name, _) => {
|
||||||
debug_assert!(!is_generated_name(name));
|
debug_assert!(!is_generated_name(name));
|
||||||
}
|
}
|
||||||
|
InferenceVar => {}
|
||||||
EffectfulFunc => {}
|
EffectfulFunc => {}
|
||||||
Type(_, tys) => {
|
Type(_, tys) => {
|
||||||
stack.extend(tys.iter().map(|t| (t, only_unseen)));
|
stack.extend(tys.iter().map(|t| (t, only_unseen)));
|
||||||
|
@ -3752,6 +3786,7 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool {
|
||||||
// If either is flex, it will unify to the other type; no diff is needed.
|
// If either is flex, it will unify to the other type; no diff is needed.
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
(InferenceVar, InferenceVar) => false,
|
||||||
(FlexAbleVar(v1, _set1), FlexAbleVar(v2, _set2))
|
(FlexAbleVar(v1, _set1), FlexAbleVar(v2, _set2))
|
||||||
| (RigidAbleVar(v1, _set1), RigidAbleVar(v2, _set2)) => {
|
| (RigidAbleVar(v1, _set1), RigidAbleVar(v2, _set2)) => {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -3944,7 +3979,9 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool {
|
||||||
| (Function(_, _, _, _), _)
|
| (Function(_, _, _, _), _)
|
||||||
| (_, Function(_, _, _, _))
|
| (_, Function(_, _, _, _))
|
||||||
| (EffectfulFunc, _)
|
| (EffectfulFunc, _)
|
||||||
| (_, EffectfulFunc) => true,
|
| (_, EffectfulFunc)
|
||||||
|
| (InferenceVar, _)
|
||||||
|
| (_, InferenceVar) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4985,7 +5022,7 @@ fn type_problem_to_pretty<'b>(
|
||||||
};
|
};
|
||||||
|
|
||||||
match tipe {
|
match tipe {
|
||||||
Infinite | Error | FlexVar(_) | EffectfulFunc => alloc.nil(),
|
Infinite | Error | FlexVar(_) | InferenceVar | EffectfulFunc => alloc.nil(),
|
||||||
FlexAbleVar(_, other_abilities) => {
|
FlexAbleVar(_, other_abilities) => {
|
||||||
rigid_able_vs_different_flex_able(x, abilities, other_abilities)
|
rigid_able_vs_different_flex_able(x, abilities, other_abilities)
|
||||||
}
|
}
|
||||||
|
@ -5058,7 +5095,7 @@ fn type_problem_to_pretty<'b>(
|
||||||
};
|
};
|
||||||
|
|
||||||
match tipe {
|
match tipe {
|
||||||
Infinite | Error | FlexVar(_) => alloc.nil(),
|
Infinite | Error | FlexVar(_) | InferenceVar => alloc.nil(),
|
||||||
FlexAbleVar(_, abilities) => {
|
FlexAbleVar(_, abilities) => {
|
||||||
let mut abilities = abilities.into_sorted_iter();
|
let mut abilities = abilities.into_sorted_iter();
|
||||||
let msg = if abilities.len() == 1 {
|
let msg = if abilities.len() == 1 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue