Skip to content

Commit

Permalink
Add "error nodes" for types and expressions (#398)
Browse files Browse the repository at this point in the history
Types and expressions can form complex parse trees, but sometimes
something goes wrong in a sub-part of the tree. For example, a dict
field might have an invalid expression as its value. Rather than use
nil, which typically means "missing" and has no span information, we can
use `ExprError` with a span containing the skipped tokens.

This also means that we can have parser functions like e.g.
`tryParseExpr()` which can signal nil when the tokens that follow cannot
begin an expression, but `ExprError` upon committing to a parse.
  • Loading branch information
mcy authored Dec 20, 2024
1 parent 77b1b7c commit c8436b2
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 0 deletions.
35 changes: 35 additions & 0 deletions experimental/ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

const (
ExprKindInvalid ExprKind = iota
ExprKindError
ExprKindLiteral
ExprKindPrefixed
ExprKindPath
Expand Down Expand Up @@ -96,6 +97,22 @@ func (e ExprAny) Kind() ExprKind {
return ExprKindPath
}

// AsError converts a ExprAny into a ExprError, if that is the type
// it contains.
//
// Otherwise, returns nil.
func (e ExprAny) AsError() ExprError {
ptr := unwrapPathLike[arena.Pointer[rawExprError]](ExprKindError, e.raw)
if ptr.Nil() {
return ExprError{}
}

return ExprError{exprImpl[rawExprError]{
e.withContext,
e.Context().Nodes().exprs.errors.Deref(ptr),
}}
}

// AsLiteral converts a ExprAny into a ExprLiteral, if that is the type
// it contains.
//
Expand Down Expand Up @@ -215,6 +232,20 @@ func (e ExprAny) Span() report.Span {
)
}

// ExprError represents an unrecoverable parsing error in an expression context.
type ExprError struct{ exprImpl[rawExprError] }

// Span implements [report.Spanner].
func (e ExprError) Span() report.Span {
if e.IsZero() {
return report.Span{}
}

return report.Span(*e.raw)
}

type rawExprError report.Span

// typeImpl is the common implementation of pointer-like Expr* types.
type exprImpl[Raw any] struct {
// NOTE: These fields are sorted by alignment.
Expand All @@ -239,6 +270,7 @@ func (e exprImpl[Raw]) AsAny() ExprAny {

// exprs is storage for the various kinds of Exprs in a Context.
type exprs struct {
errors arena.Arena[rawExprError]
prefixes arena.Arena[rawExprPrefixed]
ranges arena.Arena[rawExprRange]
arrays arena.Arena[rawExprArray]
Expand Down Expand Up @@ -272,6 +304,9 @@ func exprArena[Raw any](exprs *exprs) (ExprKind, *arena.Arena[Raw]) {
case rawExprField:
kind = ExprKindField
arena_ = &exprs.fields
case rawExprError:
kind = ExprKindError
arena_ = &exprs.errors
default:
panic("unknown expr type " + reflect.TypeOf(raw).Name())
}
Expand Down
38 changes: 38 additions & 0 deletions experimental/ast/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

const (
TypeKindInvalid TypeKind = iota
TypeKindError
TypeKindPath
TypeKindPrefixed
TypeKindGeneric
Expand Down Expand Up @@ -89,6 +90,22 @@ func (t TypeAny) Kind() TypeKind {
return TypeKindPath
}

// AsError converts a TypeAny into a TypeError, if that is the type
// it contains.
//
// Otherwise, returns nil.
func (t TypeAny) AsError() TypeError {
ptr := unwrapPathLike[arena.Pointer[rawTypeError]](TypeKindError, t.raw)
if ptr.Nil() {
return TypeError{}
}

return TypeError{typeImpl[rawTypeError]{
t.withContext,
t.Context().Nodes().types.errors.Deref(ptr),
}}
}

// AsPath converts a TypeAny into a TypePath, if that is the type
// it contains.
//
Expand Down Expand Up @@ -143,6 +160,23 @@ func (t TypeAny) Span() report.Span {
)
}

// TypeError represents an unrecoverable parsing error in a type context.
//
// This type is so named to adhere to package ast's naming convention. It does
// not represent a "type error" as in "type-checking failure".
type TypeError struct{ typeImpl[rawTypeError] }

// Span implements [report.Spanner].
func (t TypeError) Span() report.Span {
if t.IsZero() {
return report.Span{}
}

return report.Span(*t.raw)
}

type rawTypeError report.Span

// typeImpl is the common implementation of pointer-like Type* types.
type typeImpl[Raw any] struct {
// NOTE: These fields are sorted by alignment.
Expand All @@ -166,6 +200,7 @@ func (t typeImpl[Raw]) AsAny() TypeAny {
type types struct {
prefixes arena.Arena[rawTypePrefixed]
generics arena.Arena[rawTypeGeneric]
errors arena.Arena[rawTypeError]
}

func typeArena[Raw any](types *types) (TypeKind, *arena.Arena[Raw]) {
Expand All @@ -185,6 +220,9 @@ func typeArena[Raw any](types *types) (TypeKind, *arena.Arena[Raw]) {
case rawTypeGeneric:
kind = TypeKindGeneric
arena_ = &types.generics
case rawTypeError:
kind = TypeKindError
arena_ = &types.errors
default:
panic("unknown type type " + reflect.TypeOf(raw).Name())
}
Expand Down
2 changes: 2 additions & 0 deletions experimental/ast/zero_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestZero(t *testing.T) {
testZero[ast.DefService](t)

testZero[ast.ExprAny](t)
testZero[ast.ExprError](t)
testZero[ast.ExprArray](t)
testZero[ast.ExprDict](t)
testZero[ast.ExprField](t)
Expand All @@ -57,6 +58,7 @@ func TestZero(t *testing.T) {
testZero[ast.ExprPrefixed](t)

testZero[ast.TypeAny](t)
testZero[ast.TypeError](t)
testZero[ast.TypeGeneric](t)
testZero[ast.TypeList](t)
testZero[ast.TypePath](t)
Expand Down

0 comments on commit c8436b2

Please sign in to comment.