From f87c550be1294a62d1921239e39dccbf14454279 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Thu, 6 Mar 2025 16:53:57 -0500 Subject: [PATCH] lang: ast, interfaces: Improve speculation safety checks We want to speculate in more cases, so make sure that speculation is safe! --- lang/ast/structs.go | 11 +++++++++++ lang/interfaces/error.go | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/lang/ast/structs.go b/lang/ast/structs.go index 31bb1c19..6b97d1a6 100644 --- a/lang/ast/structs.go +++ b/lang/ast/structs.go @@ -10484,6 +10484,13 @@ func (obj *ExprVar) SetType(typ *types.Type) error { func (obj *ExprVar) Type() (*types.Type, error) { // TODO: should this look more like Type() in ExprCall or vice-versa? + if obj.scope == nil { // avoid a possible nil panic if we speculate here + if obj.typ == nil { + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) + } + return obj.typ, nil + } + // Return the type if it is already known statically... It is useful for // type unification to have some extra info early. expr, exists := obj.scope.Variables[obj.Name] @@ -10602,6 +10609,10 @@ func (obj *ExprVar) SetValue(value types.Value) error { // it can lookup in the previous set scope which expression this points to, and // then it can call Value on that expression. func (obj *ExprVar) Value() (types.Value, error) { + if obj.scope == nil { // avoid a possible nil panic if we speculate here + return nil, errwrap.Wrapf(interfaces.ErrValueCurrentlyUnknown, obj.String()) + } + expr, exists := obj.scope.Variables[obj.Name] if !exists { return nil, fmt.Errorf("var `%s` does not exist in scope", obj.Name) diff --git a/lang/interfaces/error.go b/lang/interfaces/error.go index 2f61a79a..34e7e391 100644 --- a/lang/interfaces/error.go +++ b/lang/interfaces/error.go @@ -36,8 +36,16 @@ import ( const ( // ErrTypeCurrentlyUnknown is returned from the Type() call on Expr if // unification didn't run successfully and the type isn't obvious yet. + // Note that it is perfectly legal to return any error, but this one can + // be used instead of inventing your own. ErrTypeCurrentlyUnknown = util.Error("type is currently unknown") + // ErrValueCurrentlyUnknown is returned from the Value() call on Expr if + // we're speculating and we don't know a value statically. Note that it + // is perfectly legal to return any error, but this one can be used + // instead of inventing your own. + ErrValueCurrentlyUnknown = util.Error("value is currently unknown") + // ErrExpectedFileMissing is returned when a file that is used by an // import is missing. This might signal the downloader, or it might // signal a permanent error.