From c8436b227b683e00f3fa6f77cb7bd83ebab1c1c5 Mon Sep 17 00:00:00 2001 From: Miguel Young Date: Fri, 20 Dec 2024 14:56:19 -0500 Subject: [PATCH] Add "error nodes" for types and expressions (#398) 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. --- experimental/ast/expr.go | 35 ++++++++++++++++++++++++++++++++ experimental/ast/type.go | 38 +++++++++++++++++++++++++++++++++++ experimental/ast/zero_test.go | 2 ++ 3 files changed, 75 insertions(+) diff --git a/experimental/ast/expr.go b/experimental/ast/expr.go index 0d0b93e9..3ead68d1 100644 --- a/experimental/ast/expr.go +++ b/experimental/ast/expr.go @@ -26,6 +26,7 @@ import ( const ( ExprKindInvalid ExprKind = iota + ExprKindError ExprKindLiteral ExprKindPrefixed ExprKindPath @@ -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. // @@ -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. @@ -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] @@ -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()) } diff --git a/experimental/ast/type.go b/experimental/ast/type.go index 2eb18408..85941c58 100644 --- a/experimental/ast/type.go +++ b/experimental/ast/type.go @@ -25,6 +25,7 @@ import ( const ( TypeKindInvalid TypeKind = iota + TypeKindError TypeKindPath TypeKindPrefixed TypeKindGeneric @@ -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. // @@ -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. @@ -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]) { @@ -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()) } diff --git a/experimental/ast/zero_test.go b/experimental/ast/zero_test.go index 7c7ac884..1a63d427 100644 --- a/experimental/ast/zero_test.go +++ b/experimental/ast/zero_test.go @@ -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) @@ -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)