internal/rosa/azalea: implement nil identifier
Test / Hakurei (race detector) (push) Successful in 6m59s
Test / Sandbox (push) Successful in 2m55s
Test / Sandbox (race detector) (push) Successful in 5m43s
Test / ShareFS (push) Successful in 7m12s
Test / Create distribution (push) Successful in 59s
Test / Hakurei (push) Successful in 3m4s
Test / Flake checks (push) Successful in 1m24s
Test / Hakurei (race detector) (push) Successful in 6m59s
Test / Sandbox (push) Successful in 2m55s
Test / Sandbox (race detector) (push) Successful in 5m43s
Test / ShareFS (push) Successful in 7m12s
Test / Create distribution (push) Successful in 59s
Test / Hakurei (push) Successful in 3m4s
Test / Flake checks (push) Successful in 1m24s
This also implements a nil-capable string type. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// Value are types supported by the language.
|
// Value are types supported by the language.
|
||||||
type Value interface {
|
type Value interface {
|
||||||
bool | int64 | string | []string | []int64 | [][2]string
|
bool | int64 | string | []string | []int64 | [][2]string | Nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -33,6 +33,29 @@ type (
|
|||||||
F func(args FArgs) (v any, set bool, err error)
|
F func(args FArgs) (v any, set bool, err error)
|
||||||
V map[unique.Handle[Ident]]any
|
V map[unique.Handle[Ident]]any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nil represents an untyped nil.
|
||||||
|
Nil struct{}
|
||||||
|
|
||||||
|
// AString represents an argument string that may be nil.
|
||||||
|
AString struct {
|
||||||
|
V string
|
||||||
|
Nil bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NilError is returned for an invalid nil assignment.
|
||||||
|
type NilError struct{ reflect.Type }
|
||||||
|
|
||||||
|
func (e NilError) Error() string {
|
||||||
|
return fmt.Sprintf("attempting to assign nil to %s", e.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// nilStringV is the [reflect.Value] of an Azalea nil assigned to an [AString].
|
||||||
|
nilStringV = reflect.ValueOf(AString{Nil: true})
|
||||||
|
// nilStringT is the [reflect.Type] of nilStringV.
|
||||||
|
nilStringT = nilStringV.Type()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apply applies named arguments and rejects unused arguments.
|
// Apply applies named arguments and rejects unused arguments.
|
||||||
@@ -50,8 +73,8 @@ func (args FArgs) Apply(v map[unique.Handle[Ident]]any) error {
|
|||||||
}
|
}
|
||||||
return UndefinedError(arg.K.Value())
|
return UndefinedError(arg.K.Value())
|
||||||
}
|
}
|
||||||
err := storeE(r, arg.V)
|
|
||||||
if err != nil {
|
if err := storeE(r, arg.V); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,11 +138,38 @@ func (e TypeError) Is(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// storeE is a convenience function to set the value of a result pointer.
|
// storeE is a convenience function to set the value of a result pointer.
|
||||||
func storeE(rp any, r any) error {
|
func storeE(rp, r any) error {
|
||||||
pv := reflect.ValueOf(rp).Elem()
|
pv := reflect.ValueOf(rp).Elem()
|
||||||
|
pt := pv.Type()
|
||||||
|
as := nilStringT.AssignableTo(pt)
|
||||||
|
|
||||||
|
if r == (Nil{}) {
|
||||||
|
switch kind := pt.Kind(); kind {
|
||||||
|
case reflect.Bool, reflect.String:
|
||||||
|
return NilError{pt}
|
||||||
|
default:
|
||||||
|
if rv, ok := rp.(*any); ok && !reflect.ValueOf(*rv).IsValid() {
|
||||||
|
*rv = Nil{}
|
||||||
|
} else if as {
|
||||||
|
pv.Set(nilStringV)
|
||||||
|
} else {
|
||||||
|
pv.SetZero()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
v := reflect.ValueOf(r)
|
v := reflect.ValueOf(r)
|
||||||
pt, vt := pv.Type(), v.Type()
|
if vt := v.Type(); !vt.AssignableTo(pt) {
|
||||||
if !vt.AssignableTo(pt) {
|
if as {
|
||||||
|
var asv AString
|
||||||
|
if err := storeE(&asv.V, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pv.Set(reflect.ValueOf(asv))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return TypeError{vt, pt}
|
return TypeError{vt, pt}
|
||||||
}
|
}
|
||||||
pv.Set(v)
|
pv.Set(v)
|
||||||
@@ -226,6 +276,9 @@ func evaluateAny(d PF, s []Frame, expr, rp any) bool {
|
|||||||
case "false":
|
case "false":
|
||||||
store(rp, false)
|
store(rp, false)
|
||||||
return true
|
return true
|
||||||
|
case "nil":
|
||||||
|
store(rp, Nil{})
|
||||||
|
return true
|
||||||
default:
|
default:
|
||||||
return evaluateAny(d, s, v, rp)
|
return evaluateAny(d, s, v, rp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,15 +100,79 @@ func TestEvaluate(t *testing.T) {
|
|||||||
Err: UndefinedError("f"),
|
Err: UndefinedError("f"),
|
||||||
}},
|
}},
|
||||||
|
|
||||||
{"error wrap deep", `f { v = nil; }`, makeStackCheck(func(
|
{"error wrap deep", `f { v = nonexistent; }`, makeStackCheck(func(
|
||||||
FArgs,
|
FArgs,
|
||||||
) (any, error) {
|
) (any, error) {
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}), "", EvaluationError{
|
}), "", EvaluationError{
|
||||||
Expr: Ident("nil"),
|
Expr: Ident("nonexistent"),
|
||||||
Err: UndefinedError("nil"),
|
Err: UndefinedError("nonexistent"),
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
{"nil", `f { v = nil; }`, checkArgs(FArgs{
|
||||||
|
{K: unique.Make(Ident("v")), V: Nil{}},
|
||||||
|
}), "\xfd", nil},
|
||||||
|
|
||||||
|
{"apply nil string", `f { v = nil; }`, makeStackCheck(func(
|
||||||
|
args FArgs,
|
||||||
|
) (v any, err error) {
|
||||||
|
var s string
|
||||||
|
err = args.Apply(map[unique.Handle[Ident]]any{
|
||||||
|
unique.Make(Ident("v")): &s,
|
||||||
|
})
|
||||||
|
v = s
|
||||||
|
return
|
||||||
|
}), "", EvaluationError{
|
||||||
|
Expr: Func{
|
||||||
|
Ident: Ident("f"),
|
||||||
|
Args: []Arg{
|
||||||
|
{K: []Ident{"v"}, V: Val{Ident("nil")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err: NilError{Type: reflect.TypeFor[string]()},
|
||||||
|
}},
|
||||||
|
|
||||||
|
{"apply nil AString", `f { v = nil; }`, makeStackCheck(func(
|
||||||
|
args FArgs,
|
||||||
|
) (v any, err error) {
|
||||||
|
s := AString{}
|
||||||
|
err = args.Apply(map[unique.Handle[Ident]]any{
|
||||||
|
unique.Make(Ident("v")): &s,
|
||||||
|
})
|
||||||
|
v = s.V
|
||||||
|
if !s.Nil {
|
||||||
|
err = fmt.Errorf("got non-nil string %q", s.V)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}), "", nil},
|
||||||
|
|
||||||
|
{"apply AString", `f { v = "\xfd"; }`, makeStackCheck(func(
|
||||||
|
args FArgs,
|
||||||
|
) (v any, err error) {
|
||||||
|
s := AString{}
|
||||||
|
err = args.Apply(map[unique.Handle[Ident]]any{
|
||||||
|
unique.Make(Ident("v")): &s,
|
||||||
|
})
|
||||||
|
v = s.V
|
||||||
|
if s.Nil {
|
||||||
|
err = fmt.Errorf("unexpected nil %q", s.V)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}), "\xfd", nil},
|
||||||
|
|
||||||
|
{"apply nil", `f { v = nil; }`, makeStackCheck(func(
|
||||||
|
args FArgs,
|
||||||
|
) (v any, err error) {
|
||||||
|
s := make([]struct{}, 9)
|
||||||
|
err = args.Apply(map[unique.Handle[Ident]]any{
|
||||||
|
unique.Make(Ident("v")): &s,
|
||||||
|
})
|
||||||
|
if s != nil {
|
||||||
|
err = fmt.Errorf("got non-nil value %#v", s)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}), "", nil},
|
||||||
|
|
||||||
{"common inputs", `package name { inputs, v = []; }`, nil, "", EvaluationError{
|
{"common inputs", `package name { inputs, v = []; }`, nil, "", EvaluationError{
|
||||||
Expr: Func{
|
Expr: Func{
|
||||||
Ident: Ident("name"),
|
Ident: Ident("name"),
|
||||||
@@ -214,8 +278,8 @@ func TestEvaluate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var errEquals bool
|
var errEquals bool
|
||||||
if _, ok := errors.AsType[TypeError](err); ok {
|
if e, ok := errors.AsType[TypeError](err); ok {
|
||||||
errEquals = errors.Is(err, tc.err)
|
errEquals = e.Is(tc.err)
|
||||||
} else {
|
} else {
|
||||||
errEquals = reflect.DeepEqual(err, tc.err)
|
errEquals = reflect.DeepEqual(err, tc.err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user