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:
Ayaz Hafiz 2025-01-02 14:26:37 -06:00
parent f5961cbb22
commit a0461679dd
13 changed files with 230 additions and 114 deletions

View file

@ -22,3 +22,4 @@ bumpalo.workspace = true
static_assertions.workspace = true
soa.workspace = true
bitflags.workspace = true

View file

@ -4,6 +4,7 @@ use crate::types::{
Polarity, RecordField, RecordFieldsError, TupleElemsError, TypeExt, Uls,
};
use crate::unification_table::{self, UnificationTable};
use bitflags::bitflags;
use roc_collections::all::{FnvMap, ImMap, ImSet, MutSet, SendMap};
use roc_collections::{VecMap, VecSet};
use roc_error_macros::internal_error;
@ -50,10 +51,24 @@ impl fmt::Debug for Mark {
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ErrorTypeContext {
None,
ExpandRanges,
bitflags! {
pub struct ErrorTypeContext : u8 {
const NONE = 1 << 0;
/// 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 {
@ -2055,7 +2070,7 @@ impl Subs {
}
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(
@ -4020,6 +4035,13 @@ fn content_to_err_type(
match content {
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) => {
let name = match opt_name {
Some(name_index) => subs.field_names[name_index.index()].clone(),
@ -4123,7 +4145,7 @@ fn content_to_err_type(
}
RangedNumber(range) => {
if state.context == ErrorTypeContext::ExpandRanges {
if state.context.expand_ranges() {
let mut types = Vec::new();
for var in range.variable_slice() {
types.push(var_to_err_type(subs, state, *var, pol));

View file

@ -3679,6 +3679,7 @@ pub enum ErrorType {
/// If the name was auto-generated, it will start with a `#`.
FlexVar(Lowercase),
RigidVar(Lowercase),
InferenceVar,
EffectfulFunc,
/// If the name was auto-generated, it will start with a `#`.
FlexAbleVar(Lowercase, AbilitySet),
@ -3733,6 +3734,7 @@ impl ErrorType {
FlexVar(v) | RigidVar(v) | FlexAbleVar(v, _) | RigidAbleVar(v, _) => {
taken.insert(v.clone());
}
InferenceVar => {}
Record(fields, ext) => {
fields
.iter()
@ -3912,13 +3914,14 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
Infinite => buf.push('∞'),
Error => buf.push('?'),
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;
if write_parens {
buf.push('(');
}
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 {
buf.push(')');
}