Skip to content

Commit

Permalink
Restrict usages of type variables in non-generalized contexts
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ayazhafiz committed Jan 2, 2025
1 parent f5961cb commit a046167
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 114 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/compiler/builtins/roc/Num.roc
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ pi = 3.14159265358979323846264338327950288419716939937510

## Circle constant (τ)
tau : Frac *
tau = 2 * pi
tau = 6.2831853071795864769252867665590057683943387987502

# ------- Functions
## Convert a number to a [Str].
Expand Down
2 changes: 1 addition & 1 deletion crates/compiler/can/src/constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ pub struct DefTypes {
pub loc_symbols: Slice<(Symbol, Region)>,
}

#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Generalizable(pub bool);

#[derive(Debug, Clone)]
Expand Down
16 changes: 0 additions & 16 deletions crates/compiler/debug_flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,6 @@ flags! {
/// Prints all type variables entered for 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
/// fixed-up with one new recursion variable.
///
Expand Down
148 changes: 103 additions & 45 deletions crates/compiler/load/tests/test_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1519,18 +1519,18 @@ mod test_reporting {
from_annotation_if,
indoc!(
r"
x : Num.Int *
x : Num.Int _
x = if Bool.true then 3.14 else 4
x
"
),
@r"
@r###"
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
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
^^^^
Expand All @@ -1544,27 +1544,27 @@ mod test_reporting {
Tip: You can convert between integers and fractions using functions
like `Num.toFrac` and `Num.round`.
"
"###
);

test_report!(
from_annotation_when,
indoc!(
r"
x : Num.Int *
x : Num.Int _
x =
when True is
_ -> 3.14
x
"
),
@r"
@r###"
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
Something is off with the body of the `x` definition:
4│ x : Num.Int *
4│ x : Num.Int _
5│ x =
6│> when True is
7│> _ -> 3.14
Expand All @@ -1579,7 +1579,7 @@ mod test_reporting {
Tip: You can convert between integers and fractions using functions
like `Num.toFrac` and `Num.round`.
"
"###
);

test_report!(
Expand Down Expand Up @@ -1907,7 +1907,7 @@ mod test_reporting {
from_annotation_complex_pattern,
indoc!(
r"
{ x } : { x : Num.Int * }
{ x } : { x : Num.Int _ }
{ x } = { x: 4.0 }
x
Expand All @@ -1918,7 +1918,7 @@ mod test_reporting {
Something is off with the body of this definition:
4│ { x } : { x : Num.Int * }
4│ { x } : { x : Num.Int _ }
5│ { x } = { x: 4.0 }
^^^^^^^^^^
Expand Down Expand Up @@ -2044,18 +2044,18 @@ mod test_reporting {
missing_fields,
indoc!(
r"
x : { a : Num.Int *, b : Num.Frac *, c : Str }
x : { a : Num.Int _, b : Num.Frac _, c : Str }
x = { b: 4.0 }
x
"
),
@r"
@r###"
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
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 }
^^^^^^^^^^
Expand All @@ -2072,7 +2072,7 @@ mod test_reporting {
}
Tip: Looks like the c and a fields are missing.
"
"###
);

// this previously reported the message below, not sure which is better
Expand Down Expand Up @@ -3445,7 +3445,7 @@ mod test_reporting {
x : AList Num.I64 Num.I64
x = ACons 0 (BCons 1 (ACons "foo" BNil ))
y : BList a a
y : BList _ _
y = BNil
{ x, y }
Expand Down Expand Up @@ -4186,9 +4186,8 @@ mod test_reporting {
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
# Create an empty dictionary.
empty : RBTree k v
empty =
Empty
empty : {} -> RBTree k v
empty = \{} -> Empty
empty
"
Expand Down Expand Up @@ -11129,10 +11128,10 @@ All branches in an `if` must have the same type!
import Decode exposing [decoder]
main =
myDecoder : Decoder (a -> a) fmt where fmt implements DecoderFormatting
myDecoder = decoder
myDecoder : Decoder (_ -> _) _
myDecoder = decoder
main =
myDecoder
"#
),
Expand All @@ -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:
7│ myDecoder = decoder
^^^^^^^
6│ myDecoder = decoder
^^^^^^^
I can't generate an implementation of the `Decoding` ability for
a -> a
* -> *
Note: `Decoding` cannot be generated for functions.
"###
Expand All @@ -11162,10 +11161,10 @@ All branches in an `if` must have the same type!
A := {}
main =
myDecoder : Decoder {x : A} fmt where fmt implements DecoderFormatting
myDecoder = decoder
myDecoder : Decoder {x : A} _
myDecoder = decoder
main =
myDecoder
"#
),
Expand All @@ -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:
9│ myDecoder = decoder
^^^^^^^
8│ myDecoder = decoder
^^^^^^^
I can't generate an implementation of the `Decoding` ability for
Expand Down Expand Up @@ -11425,20 +11424,19 @@ All branches in an `if` must have the same type!
import Decode exposing [decoder]
main =
myDecoder : Decoder {x : Str, y ? Str} fmt where fmt implements DecoderFormatting
myDecoder = decoder
myDecoder : Decoder {x : Str, y ? Str} _
myDecoder = decoder
myDecoder
main = myDecoder
"#
),
@r###"
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
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
Expand Down Expand Up @@ -14047,11 +14045,10 @@ All branches in an `if` must have the same type!
import Decode exposing [decoder]
main =
myDecoder : Decoder (U32, Str) fmt where fmt implements DecoderFormatting
myDecoder = decoder
myDecoder : Decoder (U32, Str) _
myDecoder = decoder
myDecoder
main = myDecoder
"#
)
);
Expand All @@ -14064,20 +14061,19 @@ All branches in an `if` must have the same type!
import Decode exposing [decoder]
main =
myDecoder : Decoder (U32, {} -> {}) fmt where fmt implements DecoderFormatting
myDecoder = decoder
myDecoder : Decoder (U32, {} -> {}) _
myDecoder = decoder
myDecoder
main = myDecoder
"#
),
@r###"
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
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
Expand Down Expand Up @@ -15933,4 +15929,66 @@ All branches in an `if` must have the same type!
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.
"###
);
}
6 changes: 4 additions & 2 deletions crates/compiler/lower_params/src/type_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ pub fn remove_module_param_arguments(
| TypeError::ExpectedEffectful(_, _)
| TypeError::UnsuffixedEffectfulFunction(_, _)
| TypeError::SuffixedPureFunction(_, _)
| TypeError::InvalidTryTarget(_, _, _) => {}
| TypeError::InvalidTryTarget(_, _, _)
| TypeError::TypeIsNotGeneralized(..) => {}
}
}
}
Expand Down Expand Up @@ -213,6 +214,7 @@ fn drop_last_argument(err_type: &mut ErrorType) {
| ErrorType::Alias(_, _, _, _)
| ErrorType::Range(_)
| ErrorType::Error
| ErrorType::EffectfulFunc => {}
| ErrorType::EffectfulFunc
| ErrorType::InferenceVar => {}
}
}
Loading

0 comments on commit a046167

Please sign in to comment.