Add some intrinsics tests
This change adds some machinery to make it easier to write evaluator tests, and also implements some tests for the lumi:runtime/dynamic:isFunction intrinsic.
This commit is contained in:
parent
bad62854a9
commit
2bbc4739bd
5 changed files with 276 additions and 58 deletions
|
@ -218,16 +218,16 @@ var _ Node = (*CallArgument)(nil)
|
||||||
|
|
||||||
const CallArgumentKind NodeKind = "CallArgument"
|
const CallArgumentKind NodeKind = "CallArgument"
|
||||||
|
|
||||||
type callExpressionNode struct {
|
type CallExpressionNode struct {
|
||||||
ExpressionNode
|
ExpressionNode
|
||||||
Arguments *[]*CallArgument `json:"arguments,omitempty"`
|
Arguments *[]*CallArgument `json:"arguments,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *callExpressionNode) GetArguments() *[]*CallArgument { return node.Arguments }
|
func (node *CallExpressionNode) GetArguments() *[]*CallArgument { return node.Arguments }
|
||||||
|
|
||||||
// NewExpression allocates a new object and calls its constructor.
|
// NewExpression allocates a new object and calls its constructor.
|
||||||
type NewExpression struct {
|
type NewExpression struct {
|
||||||
callExpressionNode
|
CallExpressionNode
|
||||||
Type *TypeToken `json:"type"` // the object type to allocate.
|
Type *TypeToken `json:"type"` // the object type to allocate.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ const NewExpressionKind NodeKind = "NewExpression"
|
||||||
|
|
||||||
// InvokeFunctionExpression invokes a target expression that must evaluate to a function.
|
// InvokeFunctionExpression invokes a target expression that must evaluate to a function.
|
||||||
type InvokeFunctionExpression struct {
|
type InvokeFunctionExpression struct {
|
||||||
callExpressionNode
|
CallExpressionNode
|
||||||
Function Expression `json:"function"` // a function to invoke (of a function type).
|
Function Expression `json:"function"` // a function to invoke (of a function type).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *binder) bindModuleDeclarations(node *ast.Module, parent *symbols.Package) *symbols.Module {
|
func (b *binder) bindModuleDeclarations(node *ast.Module, parent *symbols.Package) *symbols.Module {
|
||||||
|
contract.Assert(node != nil)
|
||||||
|
contract.Assert(parent != nil)
|
||||||
glog.V(3).Infof("Binding package '%v' module '%v' decls", parent.Name(), node.Name.Ident)
|
glog.V(3).Infof("Binding package '%v' module '%v' decls", parent.Name(), node.Name.Ident)
|
||||||
|
|
||||||
// Create the module symbol and register it.
|
// Create the module symbol and register it.
|
||||||
|
|
113
pkg/eval/eval.go
113
pkg/eval/eval.go
|
@ -42,11 +42,11 @@ type Interpreter interface {
|
||||||
Ctx() *binder.Context // the binding context object.
|
Ctx() *binder.Context // the binding context object.
|
||||||
|
|
||||||
// EvaluatePackage performs evaluation on the given blueprint package.
|
// EvaluatePackage performs evaluation on the given blueprint package.
|
||||||
EvaluatePackage(pkg *symbols.Package, args core.Args)
|
EvaluatePackage(pkg *symbols.Package, args core.Args) (*rt.Object, *rt.Unwind)
|
||||||
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
||||||
EvaluateModule(mod *symbols.Module, args core.Args)
|
EvaluateModule(mod *symbols.Module, args core.Args) (*rt.Object, *rt.Unwind)
|
||||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments.
|
// EvaluateFunction performs an evaluation of the given function, using the provided arguments.
|
||||||
EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args)
|
EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args) (*rt.Object, *rt.Unwind)
|
||||||
|
|
||||||
// LoadLocation loads a location by symbol; lval controls whether it is an l-value or just a value.
|
// LoadLocation loads a location by symbol; lval controls whether it is an l-value or just a value.
|
||||||
LoadLocation(tree diag.Diagable, sym symbols.Symbol, this *rt.Object, lval bool) *Location
|
LoadLocation(tree diag.Diagable, sym symbols.Symbol, this *rt.Object, lval bool) *Location
|
||||||
|
@ -108,7 +108,7 @@ func (e *evaluator) Ctx() *binder.Context { return e.ctx }
|
||||||
func (e *evaluator) Diag() diag.Sink { return e.ctx.Diag }
|
func (e *evaluator) Diag() diag.Sink { return e.ctx.Diag }
|
||||||
|
|
||||||
// EvaluatePackage performs evaluation on the given blueprint package.
|
// EvaluatePackage performs evaluation on the given blueprint package.
|
||||||
func (e *evaluator) EvaluatePackage(pkg *symbols.Package, args core.Args) {
|
func (e *evaluator) EvaluatePackage(pkg *symbols.Package, args core.Args) (*rt.Object, *rt.Unwind) {
|
||||||
glog.Infof("Evaluating package '%v'", pkg.Name())
|
glog.Infof("Evaluating package '%v'", pkg.Name())
|
||||||
if e.hooks != nil {
|
if e.hooks != nil {
|
||||||
if leave := e.hooks.OnEnterPackage(pkg); leave != nil {
|
if leave := e.hooks.OnEnterPackage(pkg); leave != nil {
|
||||||
|
@ -122,16 +122,19 @@ func (e *evaluator) EvaluatePackage(pkg *symbols.Package, args core.Args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search the package for a default module to evaluate.
|
// Search the package for a default module to evaluate.
|
||||||
|
var ret *rt.Object
|
||||||
|
var uw *rt.Unwind
|
||||||
defmod := pkg.Default()
|
defmod := pkg.Default()
|
||||||
if defmod == nil {
|
if defmod == nil {
|
||||||
e.Diag().Errorf(errors.ErrorPackageHasNoDefaultModule.At(pkg.Tree()), pkg.Name())
|
e.Diag().Errorf(errors.ErrorPackageHasNoDefaultModule.At(pkg.Tree()), pkg.Name())
|
||||||
} else {
|
} else {
|
||||||
e.EvaluateModule(defmod, args)
|
ret, uw = e.EvaluateModule(defmod, args)
|
||||||
}
|
}
|
||||||
|
return ret, uw
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
// EvaluateModule performs evaluation on the given module's entrypoint function.
|
||||||
func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) {
|
func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) (*rt.Object, *rt.Unwind) {
|
||||||
glog.Infof("Evaluating module '%v'", mod.Token())
|
glog.Infof("Evaluating module '%v'", mod.Token())
|
||||||
if e.hooks != nil {
|
if e.hooks != nil {
|
||||||
if leave := e.hooks.OnEnterModule(mod); leave != nil {
|
if leave := e.hooks.OnEnterModule(mod); leave != nil {
|
||||||
|
@ -145,10 +148,12 @@ func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the module's entrypoint function, erroring out if it doesn't have one.
|
// Fetch the module's entrypoint function, erroring out if it doesn't have one.
|
||||||
|
var ret *rt.Object
|
||||||
|
var uw *rt.Unwind
|
||||||
hadEntry := false
|
hadEntry := false
|
||||||
if entry, has := mod.Members[tokens.EntryPointFunction]; has {
|
if entry, has := mod.Members[tokens.EntryPointFunction]; has {
|
||||||
if entryfnc, ok := entry.(symbols.Function); ok {
|
if entryfnc, ok := entry.(symbols.Function); ok {
|
||||||
e.EvaluateFunction(entryfnc, nil, args)
|
ret, uw = e.EvaluateFunction(entryfnc, nil, args)
|
||||||
hadEntry = true
|
hadEntry = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,10 +161,12 @@ func (e *evaluator) EvaluateModule(mod *symbols.Module, args core.Args) {
|
||||||
if !hadEntry {
|
if !hadEntry {
|
||||||
e.Diag().Errorf(errors.ErrorModuleHasNoEntryPoint.At(mod.Tree()), mod.Name())
|
e.Diag().Errorf(errors.ErrorModuleHasNoEntryPoint.At(mod.Tree()), mod.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret, uw
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
// EvaluateFunction performs an evaluation of the given function, using the provided arguments, returning its graph.
|
||||||
func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args) {
|
func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args core.Args) (*rt.Object, *rt.Unwind) {
|
||||||
glog.Infof("Evaluating function '%v'", fnc.Token())
|
glog.Infof("Evaluating function '%v'", fnc.Token())
|
||||||
if e.hooks != nil {
|
if e.hooks != nil {
|
||||||
if leave := e.hooks.OnEnterFunction(fnc); leave != nil {
|
if leave := e.hooks.OnEnterFunction(fnc); leave != nil {
|
||||||
|
@ -217,9 +224,11 @@ func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ret *rt.Object
|
||||||
|
var uw *rt.Unwind
|
||||||
if e.Diag().Success() {
|
if e.Diag().Success() {
|
||||||
// If the arguments bound correctly, make the call.
|
// If the arguments bound correctly, make the call.
|
||||||
if _, uw := e.evalCallSymbol(fnc.Tree(), fnc, this, argos...); uw != nil {
|
if ret, uw = e.evalCallSymbol(fnc.Tree(), fnc, this, argos...); uw != nil {
|
||||||
// If the call had an unwind out of it, then presumably we have an unhandled exception.
|
// If the call had an unwind out of it, then presumably we have an unhandled exception.
|
||||||
e.issueUnhandledException(uw, errors.ErrorUnhandledException.At(fnc.Tree()))
|
e.issueUnhandledException(uw, errors.ErrorUnhandledException.At(fnc.Tree()))
|
||||||
}
|
}
|
||||||
|
@ -227,6 +236,8 @@ func (e *evaluator) EvaluateFunction(fnc symbols.Function, this *rt.Object, args
|
||||||
|
|
||||||
// Dump the evaluation state at log-level 5, if it is enabled.
|
// Dump the evaluation state at log-level 5, if it is enabled.
|
||||||
e.dumpEvalState(5)
|
e.dumpEvalState(5)
|
||||||
|
|
||||||
|
return ret, uw
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
|
@ -609,48 +620,54 @@ func (e *evaluator) evalCall(node diag.Diagable,
|
||||||
var thisVariable *symbols.LocalVariable
|
var thisVariable *symbols.LocalVariable
|
||||||
var superVariable *symbols.LocalVariable
|
var superVariable *symbols.LocalVariable
|
||||||
if sym != nil {
|
if sym != nil {
|
||||||
// Set up the appropriate this/super variables, and also ensure that we enter the right module/class context
|
for fsym, done := sym, false; !done; {
|
||||||
// (otherwise module-sensitive binding won't work).
|
contract.Assert(fsym != nil)
|
||||||
switch f := sym.(type) {
|
// Set up the appropriate this/super variables, and also ensure that we enter the right module/class
|
||||||
case *symbols.ClassMethod:
|
// context (otherwise module-sensitive binding won't work).
|
||||||
if f.Static() {
|
switch f := fsym.(type) {
|
||||||
|
case *symbols.ClassMethod:
|
||||||
|
if f.Static() {
|
||||||
|
if this != nil {
|
||||||
|
// A non-nil `this` is okay if we loaded this function from a prototype object.
|
||||||
|
prototy, isproto := this.Type().(*symbols.PrototypeType)
|
||||||
|
contract.Assert(isproto)
|
||||||
|
contract.Assert(prototy.Type == f.Parent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
contract.Assertf(this != nil, "Expect non-nil this to invoke '%v'", f)
|
||||||
|
if uw := e.checkThis(node, this); uw != nil {
|
||||||
|
return nil, uw
|
||||||
|
}
|
||||||
|
thisVariable = f.Parent.This
|
||||||
|
superVariable = f.Parent.Super
|
||||||
|
}
|
||||||
|
|
||||||
|
popModule := e.pushModuleScope(f.Parent.Parent)
|
||||||
|
defer popModule()
|
||||||
|
popClass := e.pushClassScope(f.Parent, this)
|
||||||
|
defer popClass()
|
||||||
|
done = true
|
||||||
|
|
||||||
|
case *symbols.ModuleMethod:
|
||||||
if this != nil {
|
if this != nil {
|
||||||
// A non-nil `this` is okay if we loaded this function from a prototype object.
|
// A non-nil `this` is okay if we loaded this function from a module object. Because modules can
|
||||||
prototy, isproto := this.Type().(*symbols.PrototypeType)
|
// re-export members from other modules, we cannot require that the type's parent matches.
|
||||||
contract.Assert(isproto)
|
_, ismod := this.Type().(*symbols.ModuleType)
|
||||||
contract.Assert(prototy.Type == f.Parent)
|
contract.Assert(ismod)
|
||||||
|
this = nil // the this parameter isn't required during invocation.
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
contract.Assertf(this != nil, "Expect non-nil this to invoke '%v'", f)
|
popModule := e.pushModuleScope(f.Parent)
|
||||||
if uw := e.checkThis(node, this); uw != nil {
|
defer popModule()
|
||||||
return nil, uw
|
done = true
|
||||||
}
|
|
||||||
thisVariable = f.Parent.This
|
case *Intrinsic:
|
||||||
superVariable = f.Parent.Super
|
intrinsic = true
|
||||||
|
fsym = f.Func // swap in the underlying symbol for purposes of this/super/scoping.
|
||||||
|
|
||||||
|
default:
|
||||||
|
contract.Failf("Unrecognized function type during call: %v", reflect.TypeOf(fnc))
|
||||||
}
|
}
|
||||||
|
|
||||||
popModule := e.pushModuleScope(f.Parent.Parent)
|
|
||||||
defer popModule()
|
|
||||||
popClass := e.pushClassScope(f.Parent, this)
|
|
||||||
defer popClass()
|
|
||||||
|
|
||||||
case *symbols.ModuleMethod:
|
|
||||||
if this != nil {
|
|
||||||
// A non-nil `this` is okay if we loaded this function from a module object. Note that because modules
|
|
||||||
// can re-export members from other modules, we cannot require that the type's parent matches.
|
|
||||||
_, ismod := this.Type().(*symbols.ModuleType)
|
|
||||||
contract.Assert(ismod)
|
|
||||||
}
|
|
||||||
|
|
||||||
popModule := e.pushModuleScope(f.Parent)
|
|
||||||
defer popModule()
|
|
||||||
|
|
||||||
case *Intrinsic:
|
|
||||||
contract.Assert(this == nil)
|
|
||||||
intrinsic = true
|
|
||||||
|
|
||||||
default:
|
|
||||||
contract.Failf("Unrecognized function type during call: %v", reflect.TypeOf(fnc))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ func init() {
|
||||||
|
|
||||||
// Intrinsic is a special intrinsic function whose behavior is implemented by the runtime.
|
// Intrinsic is a special intrinsic function whose behavior is implemented by the runtime.
|
||||||
type Intrinsic struct {
|
type Intrinsic struct {
|
||||||
Node diag.Diagable // the contextual node representing the place where this intrinsic got created.
|
Node diag.Diagable // the contextual node representing the place where this intrinsic got created.
|
||||||
Func ast.Function // the underlying function's node (before mapping to an intrinsic).
|
Func symbols.Function // the underlying function symbol (before mapping to an intrinsic).
|
||||||
Nm tokens.Name
|
Nm tokens.Name
|
||||||
Tok tokens.Token
|
Tok tokens.Token
|
||||||
Sig *symbols.FunctionType
|
Sig *symbols.FunctionType
|
||||||
|
@ -56,8 +56,8 @@ func (node *Intrinsic) Name() tokens.Name { return node.Nm }
|
||||||
func (node *Intrinsic) Token() tokens.Token { return node.Tok }
|
func (node *Intrinsic) Token() tokens.Token { return node.Tok }
|
||||||
func (node *Intrinsic) Special() bool { return false }
|
func (node *Intrinsic) Special() bool { return false }
|
||||||
func (node *Intrinsic) SpecialModInit() bool { return false }
|
func (node *Intrinsic) SpecialModInit() bool { return false }
|
||||||
func (node *Intrinsic) Tree() diag.Diagable { return node.Func }
|
func (node *Intrinsic) Tree() diag.Diagable { return node.Func.Function() }
|
||||||
func (node *Intrinsic) Function() ast.Function { return node.Func }
|
func (node *Intrinsic) Function() ast.Function { return node.Func.Function() }
|
||||||
func (node *Intrinsic) Signature() *symbols.FunctionType { return node.Sig }
|
func (node *Intrinsic) Signature() *symbols.FunctionType { return node.Sig }
|
||||||
func (node *Intrinsic) String() string { return string(node.Name()) }
|
func (node *Intrinsic) String() string { return string(node.Name()) }
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ func (node *Intrinsic) Invoke(e *evaluator, this *rt.Object, args []*rt.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIntrinsic returns a new intrinsic function symbol with the given information.
|
// NewIntrinsic returns a new intrinsic function symbol with the given information.
|
||||||
func NewIntrinsic(tree diag.Diagable, fnc ast.Function, tok tokens.Token, nm tokens.Name,
|
func NewIntrinsic(tree diag.Diagable, fnc symbols.Function, tok tokens.Token, nm tokens.Name,
|
||||||
sig *symbols.FunctionType, invoker Invoker) *Intrinsic {
|
sig *symbols.FunctionType, invoker Invoker) *Intrinsic {
|
||||||
return &Intrinsic{
|
return &Intrinsic{
|
||||||
Node: tree,
|
Node: tree,
|
||||||
|
@ -93,7 +93,7 @@ func MaybeIntrinsic(tree diag.Diagable, sym symbols.Symbol) symbols.Symbol {
|
||||||
if invoker, isintrinsic := Intrinsics[tok]; isintrinsic {
|
if invoker, isintrinsic := Intrinsics[tok]; isintrinsic {
|
||||||
contract.Assertf(tok.HasModuleMember(), "only module member intrinsics currently supported")
|
contract.Assertf(tok.HasModuleMember(), "only module member intrinsics currently supported")
|
||||||
name := tokens.Name(tokens.ModuleMember(tok).Name())
|
name := tokens.Name(tokens.ModuleMember(tok).Name())
|
||||||
sym = NewIntrinsic(tree, s.Function(), tok, name, s.Signature(), invoker)
|
sym = NewIntrinsic(tree, s, tok, name, s.Signature(), invoker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sym
|
return sym
|
||||||
|
|
199
pkg/eval/intrinsics_dynamic_test.go
Normal file
199
pkg/eval/intrinsics_dynamic_test.go
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
// Licensed to Pulumi Corporation ("Pulumi") under one or more
|
||||||
|
// contributor license agreements. See the NOTICE file distributed with
|
||||||
|
// this work for additional information regarding copyright ownership.
|
||||||
|
// Pulumi licenses this file to You under the Apache License, Version 2.0
|
||||||
|
// (the "License"); you may not use this file except in compliance with
|
||||||
|
// the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package eval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/pulumi/lumi/pkg/compiler/ast"
|
||||||
|
"github.com/pulumi/lumi/pkg/compiler/binder"
|
||||||
|
"github.com/pulumi/lumi/pkg/compiler/core"
|
||||||
|
"github.com/pulumi/lumi/pkg/compiler/metadata"
|
||||||
|
"github.com/pulumi/lumi/pkg/compiler/types"
|
||||||
|
"github.com/pulumi/lumi/pkg/pack"
|
||||||
|
"github.com/pulumi/lumi/pkg/tokens"
|
||||||
|
"github.com/pulumi/lumi/pkg/util/contract"
|
||||||
|
"github.com/pulumi/lumi/pkg/workspace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newTestEval makes an interpreter that can be used for testing purposes.
|
||||||
|
func newTestEval() (binder.Binder, Interpreter) {
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
contract.Assert(err == nil)
|
||||||
|
ctx := core.NewContext(pwd, core.DefaultSink(pwd), nil)
|
||||||
|
w, err := workspace.New(ctx)
|
||||||
|
contract.Assert(err == nil)
|
||||||
|
reader := metadata.NewReader(ctx)
|
||||||
|
b := binder.New(w, ctx, reader)
|
||||||
|
return b, New(b.Ctx(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFunctionIntrin = tokens.ModuleMember("lumi:runtime/dynamic:isFunction")
|
||||||
|
|
||||||
|
func makeIsFunctionExprAST(dynamic bool) ast.Expression {
|
||||||
|
if dynamic {
|
||||||
|
var loadLumiMod ast.Expression = &ast.LoadDynamicExpression{
|
||||||
|
Name: &ast.StringLiteral{
|
||||||
|
Value: isFunctionIntrin.Module().Name().String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &ast.LoadDynamicExpression{
|
||||||
|
Object: &loadLumiMod,
|
||||||
|
Name: &ast.StringLiteral{
|
||||||
|
Value: isFunctionIntrin.Name().String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ast.LoadLocationExpression{
|
||||||
|
Name: &ast.Token{
|
||||||
|
Tok: tokens.Token(isFunctionIntrin),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestIsFunctionAST(dynamic bool, realFunc bool) *pack.Package {
|
||||||
|
|
||||||
|
// Make the function body.
|
||||||
|
var body []ast.Statement
|
||||||
|
|
||||||
|
// If an intrinsic, we need to import the module so that it's available dynamically through a name.
|
||||||
|
if dynamic {
|
||||||
|
body = append(body, &ast.Import{
|
||||||
|
Referent: &ast.Token{
|
||||||
|
Tok: tokens.Token(isFunctionIntrin.Module()),
|
||||||
|
},
|
||||||
|
Name: &ast.Identifier{
|
||||||
|
Ident: tokens.Name(isFunctionIntrin.Module().Name().String()),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var invokeArg *ast.CallArgument
|
||||||
|
if realFunc {
|
||||||
|
// for real functions, just pass the isFunction function object itself.
|
||||||
|
loadFuncExpr := makeIsFunctionExprAST(dynamic)
|
||||||
|
invokeArg = &ast.CallArgument{
|
||||||
|
Expr: loadFuncExpr,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// for others, just pass a null literal.
|
||||||
|
invokeArg = &ast.CallArgument{
|
||||||
|
Expr: &ast.NullLiteral{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFuncExpr := makeIsFunctionExprAST(dynamic)
|
||||||
|
var invokeExpr ast.Expression = &ast.InvokeFunctionExpression{
|
||||||
|
CallExpressionNode: ast.CallExpressionNode{
|
||||||
|
Arguments: &[]*ast.CallArgument{invokeArg},
|
||||||
|
},
|
||||||
|
Function: loadFuncExpr,
|
||||||
|
}
|
||||||
|
body = append(body, &ast.ReturnStatement{
|
||||||
|
Expression: &invokeExpr,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Now return a package with a default module and single entrypoint main function.
|
||||||
|
return &pack.Package{
|
||||||
|
Name: "testIsFunction",
|
||||||
|
Dependencies: &pack.Dependencies{
|
||||||
|
"lumi": "*",
|
||||||
|
},
|
||||||
|
Modules: &ast.Modules{
|
||||||
|
tokens.ModuleName(".default"): &ast.Module{
|
||||||
|
DefinitionNode: ast.DefinitionNode{
|
||||||
|
Name: &ast.Identifier{
|
||||||
|
Ident: tokens.Name(".default"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Members: &ast.ModuleMembers{
|
||||||
|
tokens.ModuleMemberName(".main"): &ast.ModuleMethod{
|
||||||
|
FunctionNode: ast.FunctionNode{
|
||||||
|
ReturnType: &ast.TypeToken{
|
||||||
|
Tok: types.Bool.TypeToken(),
|
||||||
|
},
|
||||||
|
Body: &ast.Block{
|
||||||
|
Statements: body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ModuleMemberNode: ast.ModuleMemberNode{
|
||||||
|
DefinitionNode: ast.DefinitionNode{
|
||||||
|
Name: &ast.Identifier{
|
||||||
|
Ident: tokens.Name(".main"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIsFunction verifies the `lumi:runtime/dynamic:isFunction` intrinsic.
|
||||||
|
func TestIsFunction(t *testing.T) {
|
||||||
|
// variant #1: invoke the function statically, passing a null literal; expect a false return.
|
||||||
|
{
|
||||||
|
b, e := newTestEval()
|
||||||
|
pack := makeTestIsFunctionAST(false, false)
|
||||||
|
sym := b.BindPackage(pack)
|
||||||
|
ret, uw := e.EvaluatePackage(sym, nil)
|
||||||
|
assert.True(t, b.Diag().Success(), "Expected a successful evaluation")
|
||||||
|
assert.Nil(t, uw, "Did not expect a out-of-the-ordinary unwind to occur (expected a return)")
|
||||||
|
assert.NotNil(t, ret, "Expected a non-nil return value")
|
||||||
|
assert.True(t, ret.IsBool(), "Expected a bool return value; got %v", ret.Type())
|
||||||
|
assert.Equal(t, ret.BoolValue(), false, "Expected a return value of false; got %v", ret.BoolValue())
|
||||||
|
}
|
||||||
|
// variant #2: invoke the function dynamically, passing a null literal; expect a false return.
|
||||||
|
{
|
||||||
|
b, e := newTestEval()
|
||||||
|
pack := makeTestIsFunctionAST(true, false)
|
||||||
|
sym := b.BindPackage(pack)
|
||||||
|
ret, uw := e.EvaluatePackage(sym, nil)
|
||||||
|
assert.True(t, b.Diag().Success(), "Expected a successful evaluation")
|
||||||
|
assert.Nil(t, uw, "Did not expect a out-of-the-ordinary unwind to occur (expected a return)")
|
||||||
|
assert.NotNil(t, ret, "Expected a non-nil return value")
|
||||||
|
assert.True(t, ret.IsBool(), "Expected a bool return value; got %v", ret.Type())
|
||||||
|
assert.Equal(t, ret.BoolValue(), false, "Expected a return value of false; got %v", ret.BoolValue())
|
||||||
|
}
|
||||||
|
// variant #3: invoke the function statically, passing a real function; expect a true return.
|
||||||
|
{
|
||||||
|
b, e := newTestEval()
|
||||||
|
pack := makeTestIsFunctionAST(false, true)
|
||||||
|
sym := b.BindPackage(pack)
|
||||||
|
ret, uw := e.EvaluatePackage(sym, nil)
|
||||||
|
assert.True(t, b.Diag().Success(), "Expected a successful evaluation")
|
||||||
|
assert.Nil(t, uw, "Did not expect a out-of-the-ordinary unwind to occur (expected a return)")
|
||||||
|
assert.NotNil(t, ret, "Expected a non-nil return value")
|
||||||
|
assert.True(t, ret.IsBool(), "Expected a bool return value; got %v", ret.Type())
|
||||||
|
assert.Equal(t, ret.BoolValue(), true, "Expected a return value of true; got %v", ret.BoolValue())
|
||||||
|
}
|
||||||
|
// variant #4: invoke the function dynamically, passing a real function; expect a true return.
|
||||||
|
{
|
||||||
|
b, e := newTestEval()
|
||||||
|
pack := makeTestIsFunctionAST(true, true)
|
||||||
|
sym := b.BindPackage(pack)
|
||||||
|
ret, uw := e.EvaluatePackage(sym, nil)
|
||||||
|
assert.True(t, b.Diag().Success(), "Expected a successful evaluation")
|
||||||
|
assert.Nil(t, uw, "Did not expect a out-of-the-ordinary unwind to occur (expected a return)")
|
||||||
|
assert.NotNil(t, ret, "Expected a non-nil return value")
|
||||||
|
assert.True(t, ret.IsBool(), "Expected a bool return value; got %v", ret.Type())
|
||||||
|
assert.Equal(t, ret.BoolValue(), true, "Expected a return value of true; got %v", ret.BoolValue())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue