Fix a bunch of bugs in parsing/formatting found by fuzzing

This commit is contained in:
Joshua Warner 2023-02-05 11:48:05 -08:00
parent acd446f6bd
commit 3fee0d3e8f
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
43 changed files with 593 additions and 70 deletions

View file

@ -1106,7 +1106,10 @@ fn finish_parsing_alias_or_opaque<'a>(
let mut defs = Defs::default();
let state = match &expr.value.extract_spaces().item {
Expr::Tag(name) => {
Expr::ParensAround(Expr::SpaceBefore(Expr::Tag(name), _))
| Expr::ParensAround(Expr::SpaceAfter(Expr::Tag(name), _))
| Expr::ParensAround(Expr::Tag(name))
| Expr::Tag(name) => {
let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
for argument in arguments {
@ -1796,20 +1799,47 @@ pub fn loc_expr<'a>(accept_multi_backpassing: bool) -> impl Parser<'a, Loc<Expr<
})
}
pub fn merge_spaces<'a>(
arena: &'a Bump,
a: &'a [CommentOrNewline<'a>],
b: &'a [CommentOrNewline<'a>],
) -> &'a [CommentOrNewline<'a>] {
if a.is_empty() {
b
} else if b.is_empty() {
a
} else {
let mut merged = Vec::with_capacity_in(a.len() + b.len(), arena);
merged.extend_from_slice(a);
merged.extend_from_slice(b);
merged.into_bump_slice()
}
}
/// If the given Expr would parse the same way as a valid Pattern, convert it.
/// Example: (foo) could be either an Expr::Var("foo") or Pattern::Identifier("foo")
fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>, ()> {
match expr {
let mut expr = expr.extract_spaces();
if let Expr::ParensAround(loc_expr) = &expr.item {
let expr_inner = loc_expr.extract_spaces();
expr.before = merge_spaces(arena, expr.before, expr_inner.before);
expr.after = merge_spaces(arena, expr_inner.after, expr.after);
expr.item = expr_inner.item;
}
let mut pat = match expr.item {
Expr::Var { module_name, ident } => {
if module_name.is_empty() {
Ok(Pattern::Identifier(ident))
Pattern::Identifier(ident)
} else {
Ok(Pattern::QualifiedIdentifier { module_name, ident })
Pattern::QualifiedIdentifier { module_name, ident }
}
}
Expr::Underscore(opt_name) => Ok(Pattern::Underscore(opt_name)),
Expr::Tag(value) => Ok(Pattern::Tag(value)),
Expr::OpaqueRef(value) => Ok(Pattern::OpaqueRef(value)),
Expr::Underscore(opt_name) => Pattern::Underscore(opt_name),
Expr::Tag(value) => Pattern::Tag(value),
Expr::OpaqueRef(value) => Pattern::OpaqueRef(value),
Expr::Apply(loc_val, loc_args, _) => {
let region = loc_val.region;
let value = expr_to_pattern_help(arena, &loc_val.value)?;
@ -1826,19 +1856,10 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
let pattern = Pattern::Apply(val_pattern, arg_patterns.into_bump_slice());
Ok(pattern)
pattern
}
Expr::SpaceBefore(sub_expr, spaces) => Ok(Pattern::SpaceBefore(
arena.alloc(expr_to_pattern_help(arena, sub_expr)?),
spaces,
)),
Expr::SpaceAfter(sub_expr, spaces) => Ok(Pattern::SpaceAfter(
arena.alloc(expr_to_pattern_help(arena, sub_expr)?),
spaces,
)),
Expr::ParensAround(sub_expr) => expr_to_pattern_help(arena, sub_expr),
Expr::SpaceBefore(..) | Expr::SpaceAfter(..) | Expr::ParensAround(..) => unreachable!(),
Expr::Record(fields) => {
let patterns = fields.map_items_result(arena, |loc_assigned_field| {
@ -1847,30 +1868,27 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
Ok(Loc { region, value })
})?;
Ok(Pattern::RecordDestructure(patterns))
Pattern::RecordDestructure(patterns)
}
Expr::Tuple(fields) => Ok(Pattern::Tuple(fields.map_items_result(
arena,
|loc_expr| {
Ok(Loc {
region: loc_expr.region,
value: expr_to_pattern_help(arena, &loc_expr.value)?,
})
},
)?)),
Expr::Tuple(fields) => Pattern::Tuple(fields.map_items_result(arena, |loc_expr| {
Ok(Loc {
region: loc_expr.region,
value: expr_to_pattern_help(arena, &loc_expr.value)?,
})
})?),
&Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
&Expr::Num(string) => Ok(Pattern::NumLiteral(string)),
Expr::Float(string) => Pattern::FloatLiteral(string),
Expr::Num(string) => Pattern::NumLiteral(string),
Expr::NonBase10Int {
string,
base,
is_negative,
} => Ok(Pattern::NonBase10Literal {
} => Pattern::NonBase10Literal {
string,
base: *base,
is_negative: *is_negative,
}),
base,
is_negative,
},
// These would not have parsed as patterns
Expr::RecordAccessorFunction(_)
| Expr::RecordAccess(_, _)
@ -1889,12 +1907,23 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::PrecedenceConflict { .. }
| Expr::RecordUpdate { .. }
| Expr::UnaryOp(_, _)
| Expr::Crash => Err(()),
| Expr::Crash => return Err(()),
Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(string)),
Expr::MalformedIdent(string, problem) => Ok(Pattern::MalformedIdent(string, *problem)),
Expr::Str(string) => Pattern::StrLiteral(string),
Expr::SingleQuote(string) => Pattern::SingleQuote(string),
Expr::MalformedIdent(string, problem) => Pattern::MalformedIdent(string, problem),
};
// Now we re-add the spaces
if !expr.before.is_empty() {
pat = Pattern::SpaceBefore(arena.alloc(pat), expr.before);
}
if !expr.after.is_empty() {
pat = Pattern::SpaceAfter(arena.alloc(pat), expr.after);
}
Ok(pat)
}
fn assigned_expr_field_to_pattern_help<'a>(

View file

@ -264,6 +264,7 @@ pub enum BadIdent {
WeirdDotQualified(Position),
StrayDot(Position),
BadOpaqueRef(Position),
QualifiedTupleAccessor(Position),
}
fn is_alnum(ch: char) -> bool {
@ -500,6 +501,13 @@ fn chomp_identifier_chain<'a>(
match chomp_access_chain(&buffer[chomped..], &mut parts) {
Ok(width) => {
if matches!(parts[0], Accessor::TupleIndex(_)) && first_is_uppercase {
return Err((
chomped as u32,
BadIdent::QualifiedTupleAccessor(pos.bump_column(chomped as u32)),
));
}
chomped += width as usize;
let ident = Ident::Access {