Skip to content

Commit

Permalink
Merge pull request #1083 from hash-org/fix-parser-bug
Browse files Browse the repository at this point in the history
Fix several parser bugs
  • Loading branch information
feds01 authored Dec 9, 2024
2 parents 9c5b2c3 + f8a031f commit bcf736e
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 30 deletions.
20 changes: 17 additions & 3 deletions compiler/hash-parser/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,11 +357,18 @@ impl AstGen<'_> {
pub(crate) fn parse_singular_expr(
&mut self,
mut subject: AstNode<Expr>,
subject_span: ByteRange,
mut subject_span: ByteRange,
) -> ParseResult<AstNode<Expr>> {
// so here we need to peek to see if this is either a index_access, field access
// or a function call...
while let Some(token) = self.peek() {
// if there exists a space between the `subject` and the
// next fragment of the expression, we treat them as explicitly
// non-singular expressions.
if !token.span.is_right_before(subject_span) {
break;
}

subject = match token.kind {
// Property access or method call
TokenKind::Dot => self.parse_property_access(subject, subject_span)?,
Expand All @@ -382,7 +389,11 @@ impl AstGen<'_> {
// Function call
TokenKind::Tree(Delimiter::Paren, _) => self.parse_call(subject, subject_span)?,
_ => break,
}
};

// We need to adjust the subject_span so we can compute whether
// we're still "physically" connected to the end of the expression.
subject_span = subject_span.join(self.previous_pos())
}

Ok(subject)
Expand Down Expand Up @@ -752,7 +763,10 @@ impl AstGen<'_> {

// We want to emit a redundant parentheses warning if it is not a binary-like
// expression since it does not affect the precedence...
if !matches!(expr.body(), Expr::BinaryExpr(_) | Expr::Cast(_) | Expr::FnDef(_)) {
if !matches!(
expr.body(),
Expr::BinaryExpr(_) | Expr::Cast(_) | Expr::FnDef(_) | Expr::Deref(_)
) {
gen.add_warning(ParseWarning::new(
WarningKind::RedundantParenthesis(expr.body().into()),
gen.make_span(gen.range()),
Expand Down
8 changes: 7 additions & 1 deletion compiler/hash-parser/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::{
import_resolver::ImportResolver,
};

#[derive(Deref)]
#[derive(Deref, Debug)]
pub(crate) struct AstGenFrame<'s> {
/// The token cursor.
#[deref]
Expand Down Expand Up @@ -108,6 +108,12 @@ impl<'s> AstGenFrame<'s> {
let pos = self.previous_pos().end() + 1;
ByteRange::new(pos, pos)
}

/// Check whether the frame has encountered an error.
#[inline(always)]
pub(crate) fn has_error(&self) -> bool {
self.error.get()
}
}

/// The [AstGen] struct it the primary parser for the Hash compiler. It
Expand Down
8 changes: 8 additions & 0 deletions compiler/hash-parser/src/parser/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,14 @@ impl AstGen<'_> {
),
};

// Abort early if we encountered some kind of error along the way,
// although I would think when the `gen` is consumed then we can
// send up all of the errors to the parent generator?
if gen.has_error() {
let err = gen.diagnostics.errors.pop().unwrap();
return Err(err);
}

// Here we check that the token tree has a comma at the end to later determine
// if this is a `TupleTy`...
let gen_has_comma = !gen.is_empty() && gen.previous_token().has_kind(TokenKind::Comma);
Expand Down
66 changes: 54 additions & 12 deletions compiler/hash-reporting/src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Utility macros for performing various operations when it comes to
//! working with reports
use hash_source::location::Span;
use hash_source::{location::Span, ModuleId, ModuleKind};
#[allow(unused_imports)]
use hash_utils::stream_less_ewriteln;

Expand Down Expand Up @@ -28,12 +28,59 @@ pub macro panic_on_span {
}
}

/// A guard on when to print a particular message using `note_on_span`
/// and `panic_on_span` family functions.
#[derive(Clone, Copy)]
pub enum SpanGuard {
/// No guard, we're always printing for any given module.
Always,

/// Guarded by a specific module.
Module(ModuleId),

/// Non-prelude print, we'll only print something when
/// we're in a non-prelude context.
NonPrelude,

/// Only print on the specified entry point of the given
/// workspace, handy for when debugging simple things.
EntryPoint,
}

/// This macro will produce a [crate::report::Report] and then print it to the
/// standard output, this does not panic, it is intended as a debugging utility
/// to quickly print the `span` of something and the `message` associated with
/// it.
///
/// Examples of use:
#[track_caller]
pub fn guarded_note_on_span(location: impl Into<Span>, message: impl ToString, guard: SpanGuard) {
let span: Span = location.into();

let should_execute = match (guard, span.id) {
(SpanGuard::Always, _) => true,
(SpanGuard::Module(id), span) if span.is_module() => id == ModuleId::from(span),
(SpanGuard::NonPrelude, span) => !span.is_prelude(),
(SpanGuard::EntryPoint, span) => matches!(span.module_kind(), Some(ModuleKind::EntryPoint)),
_ => false,
};

// Don't report if the span guard isn't met.
if !should_execute {
return;
}

let mut reporter = Reporter::new();
reporter
.info()
.title(message)
.add_labelled_span(span, "here")
.add_note(format!("invoked at {}", ::core::panic::Location::caller()));

stream_less_ewriteln!("{}", reporter);
}

/// This macro will produce a [crate::report::Report] and then print it to the
/// standard output, this does not panic, it is intended as a debugging utility
/// for use when debugging the compiler.
///
/// ```rust
/// // Don't print on `prelude` module.
Expand All @@ -42,16 +89,11 @@ pub macro panic_on_span {
/// // Always print.
/// note_on_span(item.span(), "compiling `item`");
/// ```
#[track_caller]
///
/// If you want to only print in certain conditions, i.e. only the entry point,
/// then you can use [guarded_note_on_span] instead.
pub fn note_on_span(location: impl Into<Span>, message: impl ToString) {
let mut reporter = Reporter::new();
reporter
.info()
.title(message)
.add_labelled_span(location.into(), "here")
.add_note(format!("invoked at {}", ::core::panic::Location::caller()));

stream_less_ewriteln!("{}", reporter);
guarded_note_on_span(location, message, SpanGuard::Always);
}

/// A macro which doesn't invoke the printing of a given format expression when
Expand Down
14 changes: 14 additions & 0 deletions compiler/hash-source/src/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ impl ByteRange {
pub fn into_location(self, source_id: SourceId) -> Span {
Span::new(self, source_id)
}

/// Check if one span intersects with another one.
///
/// We check whether the `current` token touches the other token, if it
/// does, then we consider it to be intersecting.
/// ```
///
/// (current token)
/// (other token)
/// ```
#[inline(always)]
pub fn is_right_before(&self, other: Self) -> bool {
self.start() == (other.end() + 1)
}
}

impl Default for ByteRange {
Expand Down
1 change: 1 addition & 0 deletions compiler/hash-token/src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub type TokenStream<'t> = &'t [Token];

/// A cursor over a token stream which handles internal traversal logic
/// and bookkeeping.
#[derive(Debug)]
pub struct TokenCursor<'t> {
/// The raw stream of tokens that is being traversed.
stream: TokenStream<'t>,
Expand Down
3 changes: 3 additions & 0 deletions compiler/hash-token/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ impl Token {
/// order to print literals.
///
/// ##Note: this does not print trees.
///
/// @@Todo: make this in a "TokenPrinter" context so that we don't
/// need to explicitly make this a method on token.
pub fn pretty_print(&self, source: SpannedSource<'_>) -> String {
match self.kind {
TokenKind::Int(_, _) | TokenKind::Float(_) | TokenKind::Char(_) | TokenKind::Str(_) => {
Expand Down
4 changes: 2 additions & 2 deletions tests/cases/parser/blocks/empty_for_loop.hash
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ main := () => {
// Test that the parser shouldn't get confused between a struct literal and a for block
for x in xs {}

// Test that struct literals are allowed in parenthesees...
for x in (Struct ( xs )) {}
// Test that struct literals are allowed in parentheses...
for x in (Struct( xs )) {}
}
2 changes: 1 addition & 1 deletion tests/cases/parser/constructors/struct_defn.hash
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Point := struct(

// Test that structs can be initialised with default field value and types.
Triangle := struct(
x: Point = Point (x = 0, y = 0),
x: Point = Point(x = 0, y = 0),
y: Point,
z := Point( x = 0, y = 0), // Type should be inferred from default value?
);
Expand Down
9 changes: 9 additions & 0 deletions tests/cases/parser/misc/while_implicit_call.hash
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// run=pass, stage=parse, warnings=compare

#dump_ast
main := () => {
i := 0;
while i < (*arr).len {
i += 1;
}
}
2 changes: 1 addition & 1 deletion tests/cases/parser/patterns/list_patterns.hash
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[a, b] := foo();
[] := foo();

[x, Struct ( name )] = *mu();
[x, Struct(name)] = *mu();

[(a, 3) | (a, 2), (c, d), (r, m)] := foo();

4 changes: 2 additions & 2 deletions tests/cases/typecheck/pat_binds/548.hash
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
main := () => {
// ~ERROR: variable `c` not declared in all patterns
// ~ERROR: variable `d` not declared in all patterns
(c | d) := 2;
(c | d) := 2;

k := (1, 2)

match k {
(a, b) | (t, a) => {}
(a, b) | (t, a) => {},
(2, 3) | (a, b) => {}
}
}
42 changes: 34 additions & 8 deletions tests/cases/typecheck/pat_binds/548.stderr
Original file line number Diff line number Diff line change
@@ -1,51 +1,77 @@
error[0081]: variable `c` is not bound in all patterns
--> $DIR/548.hash:6:10
5 | // ~ERROR: variable `d` not declared in all patterns
6 | (c | d) := 2;
6 | (c | d) := 2;
| ^ pattern doesn't bind `c`
7 |

--> $DIR/548.hash:6:6
5 | // ~ERROR: variable `d` not declared in all patterns
6 | (c | d) := 2;
6 | (c | d) := 2;
| ^ variable not in all patterns
7 |

error[0081]: variable `d` is not bound in all patterns
--> $DIR/548.hash:6:6
5 | // ~ERROR: variable `d` not declared in all patterns
6 | (c | d) := 2;
6 | (c | d) := 2;
| ^ pattern doesn't bind `d`
7 |

--> $DIR/548.hash:6:10
5 | // ~ERROR: variable `d` not declared in all patterns
6 | (c | d) := 2;
6 | (c | d) := 2;
| ^ variable not in all patterns
7 |

error[0081]: variable `b` is not bound in all patterns
--> $DIR/548.hash:11:18
10 | match k {
11 | (a, b) | (t, a) => {}
11 | (a, b) | (t, a) => {},
| ^^^^^^ pattern doesn't bind `b`
12 | (2, 3) | (a, b) => {}

--> $DIR/548.hash:11:13
10 | match k {
11 | (a, b) | (t, a) => {}
11 | (a, b) | (t, a) => {},
| ^ variable not in all patterns
12 | (2, 3) | (a, b) => {}

error[0081]: variable `t` is not bound in all patterns
--> $DIR/548.hash:11:9
10 | match k {
11 | (a, b) | (t, a) => {}
11 | (a, b) | (t, a) => {},
| ^^^^^^ pattern doesn't bind `t`
12 | (2, 3) | (a, b) => {}

--> $DIR/548.hash:11:19
10 | match k {
11 | (a, b) | (t, a) => {}
11 | (a, b) | (t, a) => {},
| ^ variable not in all patterns
12 | (2, 3) | (a, b) => {}

error[0081]: variable `a` is not bound in all patterns
--> $DIR/548.hash:12:9
11 | (a, b) | (t, a) => {},
12 | (2, 3) | (a, b) => {}
| ^^^^^^ pattern doesn't bind `a`
13 | }

--> $DIR/548.hash:12:19
11 | (a, b) | (t, a) => {},
12 | (2, 3) | (a, b) => {}
| ^ variable not in all patterns
13 | }

error[0081]: variable `b` is not bound in all patterns
--> $DIR/548.hash:12:9
11 | (a, b) | (t, a) => {},
12 | (2, 3) | (a, b) => {}
| ^^^^^^ pattern doesn't bind `b`
13 | }

--> $DIR/548.hash:12:22
11 | (a, b) | (t, a) => {},
12 | (2, 3) | (a, b) => {}
| ^ variable not in all patterns
13 | }

0 comments on commit bcf736e

Please sign in to comment.