Fix nasty perf bug in parsing types

This commit is contained in:
Joshua Warner 2024-12-01 10:35:59 -08:00
parent 7cc24cbced
commit 085c5e2fe7
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
5 changed files with 82 additions and 32 deletions

View file

@ -836,7 +836,7 @@ where
}
// This should be enough for anyone. Right? RIGHT?
let indent_text = "| ; : ! ".repeat(20);
let indent_text = "| ; : ! ".repeat(100);
let cur_indent = INDENT.with(|i| *i.borrow());
@ -1669,6 +1669,21 @@ where
}
}
/// Creates a parser that fails if the next byte is the given byte.
pub fn error_on_byte<'a, T, E, F>(byte_to_match: u8, to_error: F) -> impl Parser<'a, T, E>
where
T: 'a,
E: 'a,
F: Fn(Position) -> E,
{
debug_assert_ne!(byte_to_match, b'\n');
move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| match state.bytes().first() {
Some(x) if *x == byte_to_match => Err((MadeProgress, to_error(state.pos()))),
_ => Err((NoProgress, to_error(state.pos()))),
}
}
/// Runs two parsers in succession. If both parsers succeed, the output is a tuple of both outputs.
/// Both parsers must have the same error type.
///

View file

@ -10,9 +10,9 @@ use crate::expr::record_field;
use crate::ident::{lowercase_ident, lowercase_ident_keyword_e};
use crate::keyword;
use crate::parser::{
absolute_column_min_indent, and, collection_trailing_sep_e, either, increment_min_indent,
indented_seq, loc, map, map_with_arena, skip_first, skip_second, succeed, then, zero_or_more,
ERecord, ETypeAbilityImpl,
absolute_column_min_indent, and, collection_trailing_sep_e, either, error_on_byte,
increment_min_indent, indented_seq, loc, map, map_with_arena, skip_first, skip_second, succeed,
then, zero_or_more, ERecord, ETypeAbilityImpl,
};
use crate::parser::{
allocated, backtrackable, byte, fail, optional, specialize_err, specialize_err_ref, two_bytes,
@ -582,37 +582,39 @@ fn expression<'a>(
let (p1, first, state) = space0_before_e(term(stop_at_surface_has), EType::TIndentStart)
.parse(arena, state, min_indent)?;
let result = and(
zero_or_more(skip_first(
byte(b',', EType::TFunctionArgument),
one_of![
space0_around_ee(
term(stop_at_surface_has),
EType::TIndentStart,
EType::TIndentEnd
let (p2, rest, rest_state) = zero_or_more(skip_first(
backtrackable(byte(b',', EType::TFunctionArgument)),
one_of![
map_with_arena(
and(
backtrackable(space0_e(EType::TIndentStart)),
and(term(stop_at_surface_has), space0_e(EType::TIndentEnd)),
),
fail(EType::TFunctionArgument)
],
))
.trace("type_annotation:expression:rest_args"),
and(
space0_e(EType::TIndentStart),
one_of![
map(two_bytes(b'-', b'>', EType::TStart), |_| {
FunctionArrow::Pure
}),
map(two_bytes(b'=', b'>', EType::TStart), |_| {
FunctionArrow::Effectful
}),
],
)
.trace("type_annotation:expression:arrow"),
comma_args_help,
),
error_on_byte(b',', EType::TFunctionArgument)
],
))
.trace("type_annotation:expression:rest_args")
.parse(arena, state.clone(), min_indent)?;
let result = and(
space0_e(EType::TIndentStart),
one_of![
map(two_bytes(b'-', b'>', EType::TStart), |_| {
FunctionArrow::Pure
}),
map(two_bytes(b'=', b'>', EType::TStart), |_| {
FunctionArrow::Effectful
}),
],
)
.parse(arena, state.clone(), min_indent);
.trace("type_annotation:expression:arrow")
.parse(arena, rest_state, min_indent);
let (progress, annot, state) = match result {
Ok((p2, (rest, (space_before_arrow, arrow)), state)) => {
let (p3, return_type, state) =
Ok((p3, (space_before_arrow, arrow), state)) => {
let (p4, return_type, state) =
space0_before_e(term(stop_at_surface_has), EType::TIndentStart)
.parse(arena, state, min_indent)?;
@ -636,7 +638,7 @@ fn expression<'a>(
region,
value: TypeAnnotation::Function(output, arrow, arena.alloc(return_type)),
};
let progress = p1.or(p2).or(p3);
let progress = p1.or(p2).or(p3).or(p4);
(progress, result, state)
}
Err(err) => {
@ -694,6 +696,36 @@ fn expression<'a>(
.trace("type_annotation:expression")
}
fn comma_args_help<'a>(
arena: &'a Bump,
(spaces_before, (loc_val, spaces_after)): (
&'a [CommentOrNewline<'a>],
(Loc<TypeAnnotation<'a>>, &'a [CommentOrNewline<'a>]),
),
) -> Loc<TypeAnnotation<'a>> {
if spaces_before.is_empty() {
if spaces_after.is_empty() {
loc_val
} else {
arena
.alloc(loc_val.value)
.with_spaces_after(spaces_after, loc_val.region)
}
} else if spaces_after.is_empty() {
arena
.alloc(loc_val.value)
.with_spaces_before(spaces_before, loc_val.region)
} else {
let wrapped_expr = arena
.alloc(loc_val.value)
.with_spaces_after(spaces_after, loc_val.region);
arena
.alloc(wrapped_expr.value)
.with_spaces_before(spaces_before, wrapped_expr.region)
}
}
/// Parse a basic type annotation that's a combination of variables
/// (which are lowercase and unqualified, e.g. `a` in `List a`),
/// type applications (which are uppercase and optionally qualified, e.g.