Introduce proper type for TypeVar's, mark anything not a lowercase ident as malformed

This commit is contained in:
Joshua Warner 2025-01-08 21:12:53 -08:00
parent a9c25563b2
commit d43ad92789
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
65 changed files with 729 additions and 460 deletions

View file

@ -633,7 +633,7 @@ pub struct PrecedenceConflict<'a> {
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TypeHeader<'a> {
pub name: Loc<&'a str>,
pub vars: &'a [Loc<Pattern<'a>>],
pub vars: &'a [Loc<TypeVar<'a>>],
}
impl<'a> TypeHeader<'a> {
@ -646,6 +646,17 @@ impl<'a> TypeHeader<'a> {
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TypeVar<'a> {
Identifier(&'a str),
SpaceBefore(&'a TypeVar<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a TypeVar<'a>, &'a [CommentOrNewline<'a>]),
// These are syntactically parsed as exprs first, so if there's anything else here,
// we consider it malformed but preserve it for error reporting and more resilient parsing.
Malformed(&'a Expr<'a>),
}
/// The `implements` keyword associated with ability definitions.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Implements<'a> {
@ -1642,6 +1653,7 @@ pub enum Pattern<'a> {
// Malformed
Malformed(&'a str),
MalformedIdent(&'a str, crate::ident::BadIdent),
MalformedExpr(&'a Expr<'a>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
@ -1868,6 +1880,11 @@ impl<'a> Pattern<'a> {
false
}
}
MalformedExpr(_expr_x) => {
// conservatively assume all malformed expr patterns are not-equivalient
false
}
}
}
}
@ -2256,6 +2273,7 @@ impl_extract_spaces!(AssignedField<T>);
impl_extract_spaces!(TypeAnnotation);
impl_extract_spaces!(ImplementsAbility);
impl_extract_spaces!(Implements);
impl_extract_spaces!(TypeVar);
impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> {
type Item = T;
@ -2560,6 +2578,7 @@ impl<'a> Malformed for Pattern<'a> {
Malformed(_) |
MalformedIdent(_, _) |
MalformedExpr(_) |
QualifiedIdentifier { .. } => true,
}
}
@ -2730,6 +2749,16 @@ impl<'a> Malformed for TypeHeader<'a> {
}
}
impl<'a> Malformed for TypeVar<'a> {
fn is_malformed(&self) -> bool {
match self {
TypeVar::Identifier(_) => false,
TypeVar::Malformed(_) => true,
TypeVar::SpaceBefore(var, _) | TypeVar::SpaceAfter(var, _) => var.is_malformed(),
}
}
}
impl<'a> Malformed for Tag<'a> {
fn is_malformed(&self) -> bool {
match self {

View file

@ -2,7 +2,8 @@ use crate::ast::{
AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Implements,
ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword, ImportedModuleName,
IngestedFileAnnotation, IngestedFileImport, ModuleImport, ModuleImportParams, Pattern,
Spaceable, Spaced, Spaces, SpacesBefore, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
Spaceable, Spaced, Spaces, SpacesBefore, TypeAnnotation, TypeDef, TypeHeader, TypeVar,
ValueDef,
};
use crate::blankspace::{
loc_space0_e, require_newline_or_eof, space0_after_e, space0_around_ee, space0_before_e,
@ -17,8 +18,8 @@ use crate::parser::{
collection_trailing_sep_e, either, increment_min_indent, indented_seq_skip_first, loc, map,
map_with_arena, optional, reset_min_indent, sep_by1, sep_by1_e, set_min_indent, skip_first,
skip_second, specialize_err, specialize_err_ref, then, two_bytes, zero_or_more, EClosure,
EExpect, EExpr, EIf, EImport, EImportParams, EInParens, EList, ENumber, EPattern, ERecord,
EReturn, EString, EType, EWhen, Either, ParseResult, Parser, SpaceProblem,
EExpect, EExpr, EIf, EImport, EImportParams, EInParens, EList, ENumber, ERecord, EReturn,
EString, EType, EWhen, Either, ParseResult, Parser, SpaceProblem,
};
use crate::pattern::closure_param;
use crate::state::State;
@ -1244,20 +1245,10 @@ fn parse_stmt_alias_or_opaque<'a>(
let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
for argument in arguments {
match expr_to_pattern_help(arena, &argument.value) {
Ok(good) => {
type_arguments.push(Loc::at(argument.region, good));
}
Err(()) => {
return Err((
MadeProgress,
EExpr::Pattern(
arena.alloc(EPattern::NotAPattern(state.pos())),
state.pos(),
),
));
}
}
type_arguments.push(Loc::at(
argument.region,
expr_to_type_var(arena, &argument.value),
));
}
match kind.value {
@ -1344,6 +1335,29 @@ fn parse_stmt_alias_or_opaque<'a>(
Ok((MadeProgress, res, state))
}
fn expr_to_type_var<'a>(arena: &'a Bump, expr: &'a Expr<'a>) -> TypeVar<'a> {
let expr = expr.extract_spaces();
let mut ty = match expr.item {
Expr::Var {
module_name: "",
ident,
} => TypeVar::Identifier(ident),
_ => TypeVar::Malformed(arena.alloc(expr.item)),
};
// Now we re-add the spaces
if !expr.before.is_empty() {
ty = TypeVar::SpaceBefore(arena.alloc(ty), expr.before);
}
if !expr.after.is_empty() {
ty = TypeVar::SpaceAfter(arena.alloc(ty), expr.after);
}
ty
}
mod ability {
use parser::absolute_indented_seq;
@ -1464,7 +1478,7 @@ mod ability {
fn finish_parsing_ability_def_help<'a>(
call_min_indent: u32,
name: Loc<&'a str>,
args: &'a [Loc<Pattern<'a>>],
args: &'a [Loc<TypeVar<'a>>],
loc_implements: Loc<Implements<'a>>,
arena: &'a Bump,
state: State<'a>,
@ -2028,16 +2042,10 @@ fn parse_ability_def<'a>(
let mut arguments = Vec::with_capacity_in(expr_state.arguments.len(), arena);
for argument in expr_state.arguments {
match expr_to_pattern_help(arena, &argument.value) {
Ok(good) => {
arguments.push(Loc::at(argument.region, good));
}
Err(_) => {
let start = argument.region.start();
let err = &*arena.alloc(EPattern::Start(start));
return Err((MadeProgress, EExpr::Pattern(err, argument.region.start())));
}
}
arguments.push(Loc::at(
argument.region,
expr_to_type_var(arena, &argument.value),
));
}
// Attach any spaces to the `implements` keyword
@ -3209,7 +3217,7 @@ fn stmts_to_defs<'a>(
if (spaces_middle.len() <= 1
&& !ends_with_spaces_conservative(&ann_type.value)
&& !starts_with_spaces_conservative(&loc_pattern.value))
|| header_to_pat(arena, header).equivalent(&loc_pattern.value)
|| type_header_equivalent_to_pat(&header, &loc_pattern.value)
{
// This is a case like
// UserId x : [UserId Int]
@ -3351,18 +3359,33 @@ fn starts_with_spaces_conservative(value: &Pattern<'_>) -> bool {
Pattern::RequiredField(_, _) | Pattern::OptionalField(_, _) => false,
Pattern::SpaceBefore(_, _) => true,
Pattern::SpaceAfter(inner, _) => starts_with_spaces_conservative(inner),
Pattern::Malformed(_) | Pattern::MalformedIdent(_, _) => true,
Pattern::Malformed(_) | Pattern::MalformedIdent(_, _) | Pattern::MalformedExpr(_) => true,
}
}
fn header_to_pat<'a>(arena: &'a Bump, header: TypeHeader<'a>) -> Pattern<'a> {
if header.vars.is_empty() {
Pattern::Tag(header.name.value)
} else {
Pattern::Apply(
arena.alloc(Loc::at(header.name.region, Pattern::Tag(header.name.value))),
header.vars,
)
fn type_header_equivalent_to_pat<'a>(header: &TypeHeader<'a>, pat: &Pattern<'a>) -> bool {
match pat {
Pattern::Apply(func, args) => {
if !matches!(func.value, Pattern::Tag(tag) if header.name.value == tag) {
return false;
}
if args.len() != header.vars.len() {
return false;
}
for (arg, var) in (*args).iter().zip(header.vars) {
match (arg.value, var.value) {
(Pattern::Identifier { ident: left }, TypeVar::Identifier(right)) => {
if left != right {
return false;
}
}
_ => return false,
}
}
true
}
Pattern::Tag(tag) => header.vars.is_empty() && header.name.value == *tag,
_ => false,
}
}
@ -3375,7 +3398,7 @@ fn ends_with_spaces_conservative(ty: &TypeAnnotation<'_>) -> bool {
TypeAnnotation::As(_, _, type_header) => type_header
.vars
.last()
.map_or(false, |v| pat_ends_with_spaces_conservative(&v.value)),
.map_or(false, |v| type_var_ends_with_spaces_conservative(&v.value)),
TypeAnnotation::Record { fields: _, ext }
| TypeAnnotation::Tuple { elems: _, ext }
| TypeAnnotation::TagUnion { ext, tags: _ } => {
@ -3396,32 +3419,15 @@ fn ends_with_spaces_conservative(ty: &TypeAnnotation<'_>) -> bool {
}
}
fn pat_ends_with_spaces_conservative(pat: &Pattern<'_>) -> bool {
match pat {
Pattern::Identifier { .. }
| Pattern::QualifiedIdentifier { .. }
| Pattern::Tag(_)
| Pattern::NumLiteral(_)
| Pattern::FloatLiteral(_)
| Pattern::StrLiteral(_)
| Pattern::Underscore(_)
| Pattern::SingleQuote(_)
| Pattern::Tuple(_)
| Pattern::List(_)
| Pattern::NonBase10Literal { .. }
| Pattern::ListRest(_)
| Pattern::As(_, _)
| Pattern::OpaqueRef(_)
| Pattern::PncApply(_, _) => false,
Pattern::Apply(_, args) => args
.last()
.map_or(false, |a| pat_ends_with_spaces_conservative(&a.value)),
Pattern::RecordDestructure(_) => false,
Pattern::RequiredField(_, _) => unreachable!(),
Pattern::OptionalField(_, _) => unreachable!(),
Pattern::SpaceBefore(inner, _) => pat_ends_with_spaces_conservative(inner),
Pattern::SpaceAfter(_, _) => true,
Pattern::Malformed(_) | Pattern::MalformedIdent(_, _) => false,
fn type_var_ends_with_spaces_conservative(value: &TypeVar<'_>) -> bool {
match value {
TypeVar::Identifier(_) => false,
TypeVar::Malformed(_) => {
// conservativly assume it might end in a space
true
}
TypeVar::SpaceBefore(inner, _sp) => type_var_ends_with_spaces_conservative(inner),
TypeVar::SpaceAfter(_inner, _sp) => true,
}
}
@ -3435,7 +3441,7 @@ pub fn join_alias_to_body<'a>(
body_expr: &'a Loc<Expr<'a>>,
) -> ValueDef<'a> {
let loc_name = arena.alloc(header.name.map(|x| Pattern::Tag(x)));
let ann_pattern = Pattern::Apply(loc_name, header.vars);
let ann_pattern = Pattern::Apply(loc_name, type_vars_to_patterns(arena, header.vars));
let vars_region = Region::across_all(header.vars.iter().map(|v| &v.region));
let region_ann_pattern = Region::span_across(&loc_name.region, &vars_region);
@ -3450,6 +3456,35 @@ pub fn join_alias_to_body<'a>(
}
}
fn type_vars_to_patterns<'a>(
arena: &'a Bump,
vars: &'a [Loc<TypeVar<'a>>],
) -> &'a [Loc<Pattern<'a>>] {
let mut result = Vec::with_capacity_in(vars.len(), arena);
for var in vars {
let pat = type_var_to_pat(arena, &var.value);
result.push(Loc::at(var.region, pat));
}
result.into_bump_slice()
}
fn type_var_to_pat<'a>(arena: &'a Bump, var: &TypeVar<'a>) -> Pattern<'a> {
match var {
TypeVar::Identifier(ident) => Pattern::Identifier { ident },
TypeVar::Malformed(expr) => match expr_to_pattern_help(arena, expr) {
Ok(pat) => pat,
Err(()) => Pattern::MalformedExpr(expr),
},
TypeVar::SpaceBefore(inner, sp) => {
Pattern::SpaceBefore(arena.alloc(type_var_to_pat(arena, inner)), sp)
}
TypeVar::SpaceAfter(inner, sp) => {
Pattern::SpaceAfter(arena.alloc(type_var_to_pat(arena, inner)), sp)
}
}
}
/// This is a helper function for parsing function args.
/// The rules for (-) are special-cased, and they come up in function args.
///

View file

@ -3,7 +3,7 @@ use bumpalo::Bump;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_region::all::{Loc, Position, Region};
use crate::ast::ImplementsAbilities;
use crate::ast::{ImplementsAbilities, TypeVar};
use crate::{
ast::{
AbilityImpls, AbilityMember, AssignedField, Collection, Defs, Expr, FullAst, Header,
@ -390,6 +390,18 @@ impl<'a> Normalize<'a> for TypeDef<'a> {
}
}
impl<'a> Normalize<'a> for TypeVar<'a> {
fn normalize(&self, arena: &'a Bump) -> Self {
match self {
TypeVar::Identifier(ident) => TypeVar::Identifier(ident),
TypeVar::Malformed(expr) => TypeVar::Malformed(expr.normalize(arena)),
TypeVar::SpaceBefore(inner, _sp) | TypeVar::SpaceAfter(inner, _sp) => {
*inner.normalize(arena)
}
}
}
}
impl<'a> Normalize<'a> for ValueDef<'a> {
fn normalize(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
@ -919,6 +931,7 @@ impl<'a> Normalize<'a> for Pattern<'a> {
Pattern::StrLiteral(a) => Pattern::StrLiteral(a.normalize(arena)),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedExpr(a) => Pattern::MalformedExpr(arena.alloc(a.normalize(arena))),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, remove_spaces_bad_ident(b)),
Pattern::QualifiedIdentifier { module_name, ident } => {
Pattern::QualifiedIdentifier { module_name, ident }

View file

@ -1,7 +1,7 @@
use crate::ast::{
AbilityImpls, AssignedField, Collection, CommentOrNewline, Expr, FunctionArrow,
ImplementsAbilities, ImplementsAbility, ImplementsClause, Pattern, Spaceable, Spaced,
SpacesBefore, Tag, TypeAnnotation, TypeHeader,
ImplementsAbilities, ImplementsAbility, ImplementsClause, Spaceable, Spaced, SpacesBefore, Tag,
TypeAnnotation, TypeHeader, TypeVar,
};
use crate::blankspace::{
self, plain_spaces_before, space0_around_ee, space0_before, space0_before_e, space0_e,
@ -74,7 +74,7 @@ fn check_type_alias<'a>(
var_names.reserve(vars.len());
for var in vars {
if let TypeAnnotation::BoundVariable(v) = var.value {
var_names.push(Loc::at(var.region, Pattern::Identifier { ident: v }));
var_names.push(Loc::at(var.region, TypeVar::Identifier(v)));
} else {
return Err(ETypeInlineAlias::ArgumentNotLowercase(var.region.start()));
}