Add type conversion tests
This commit is contained in:
parent
a58cd7e2a0
commit
02d7ba2417
|
@ -16,21 +16,21 @@ type Definition interface {
|
|||
GetDescription() *string // an optional informative description.
|
||||
}
|
||||
|
||||
type definitionNode struct {
|
||||
node
|
||||
type DefinitionNode struct {
|
||||
NodeValue
|
||||
Name *Identifier `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
func (node *definitionNode) definition() {}
|
||||
func (node *definitionNode) GetName() *Identifier { return node.Name }
|
||||
func (node *definitionNode) GetDescription() *string { return node.Description }
|
||||
func (node *DefinitionNode) definition() {}
|
||||
func (node *DefinitionNode) GetName() *Identifier { return node.Name }
|
||||
func (node *DefinitionNode) GetDescription() *string { return node.Description }
|
||||
|
||||
/* Modules */
|
||||
|
||||
// Module contains members, including variables, functions, and/or classes.
|
||||
type Module struct {
|
||||
definitionNode
|
||||
DefinitionNode
|
||||
Imports *[]tokens.Module `json:"imports,omitempty"`
|
||||
Members *ModuleMembers `json:"members,omitempty"`
|
||||
}
|
||||
|
@ -50,20 +50,20 @@ type ModuleMember interface {
|
|||
GetAccess() *tokens.Accessibility
|
||||
}
|
||||
|
||||
type moduleMemberNode struct {
|
||||
definitionNode
|
||||
type ModuleMemberNode struct {
|
||||
DefinitionNode
|
||||
Access *tokens.Accessibility `json:"access,omitempty"`
|
||||
}
|
||||
|
||||
func (node *moduleMemberNode) moduleMember() {}
|
||||
func (node *moduleMemberNode) GetAccess() *tokens.Accessibility { return node.Access }
|
||||
func (node *ModuleMemberNode) moduleMember() {}
|
||||
func (node *ModuleMemberNode) GetAccess() *tokens.Accessibility { return node.Access }
|
||||
|
||||
// ModuleMembers is a map of member name to ModuleMember symbol.
|
||||
type ModuleMembers map[tokens.ModuleMemberName]ModuleMember
|
||||
|
||||
// Export re-exports a Definition from another Module, possibly with a different name.
|
||||
type Export struct {
|
||||
moduleMemberNode
|
||||
ModuleMemberNode
|
||||
Referent tokens.Token `json:"referent"`
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ const ExportKind NodeKind = "Export"
|
|||
|
||||
// Class can be constructed to create an object, and exports properties, methods, and has a number of attributes.
|
||||
type Class struct {
|
||||
moduleMemberNode
|
||||
ModuleMemberNode
|
||||
Extends *tokens.Type `json:"extends,omitempty"`
|
||||
Implements *[]tokens.Type `json:"implements,omitempty"`
|
||||
Sealed *bool `json:"sealed,omitempty"`
|
||||
|
@ -100,15 +100,15 @@ type ClassMember interface {
|
|||
GetStatic() *bool
|
||||
}
|
||||
|
||||
type classMemberNode struct {
|
||||
definitionNode
|
||||
type ClassMemberNode struct {
|
||||
DefinitionNode
|
||||
Access *tokens.ClassMemberAccessibility `json:"access,omitempty"`
|
||||
Static *bool `json:"static,omitempty"`
|
||||
}
|
||||
|
||||
func (node *classMemberNode) classMember() {}
|
||||
func (node *classMemberNode) GetAccess() *tokens.ClassMemberAccessibility { return node.Access }
|
||||
func (node *classMemberNode) GetStatic() *bool { return node.Static }
|
||||
func (node *ClassMemberNode) classMember() {}
|
||||
func (node *ClassMemberNode) GetAccess() *tokens.ClassMemberAccessibility { return node.Access }
|
||||
func (node *ClassMemberNode) GetStatic() *bool { return node.Static }
|
||||
|
||||
// ClassMembers is a map of class member name to ClassMember symbol.
|
||||
type ClassMembers map[tokens.ClassMemberName]ClassMember
|
||||
|
@ -123,21 +123,21 @@ type Variable interface {
|
|||
GetReadonly() *bool
|
||||
}
|
||||
|
||||
type variableNode struct {
|
||||
type VariableNode struct {
|
||||
// note that this node intentionally omits any embedded base, to avoid diamond "inheritance".
|
||||
Type *tokens.Type `json:"type,omitempty"`
|
||||
Default *interface{} `json:"default,omitempty"`
|
||||
Readonly *bool `json:"readonly,omitempty"`
|
||||
}
|
||||
|
||||
func (node *variableNode) GetType() *tokens.Type { return node.Type }
|
||||
func (node *variableNode) GetDefault() *interface{} { return node.Default }
|
||||
func (node *variableNode) GetReadonly() *bool { return node.Readonly }
|
||||
func (node *VariableNode) GetType() *tokens.Type { return node.Type }
|
||||
func (node *VariableNode) GetDefault() *interface{} { return node.Default }
|
||||
func (node *VariableNode) GetReadonly() *bool { return node.Readonly }
|
||||
|
||||
// LocalVariable is a variable that is lexically scoped within a function (either a parameter or local).
|
||||
type LocalVariable struct {
|
||||
variableNode
|
||||
definitionNode
|
||||
VariableNode
|
||||
DefinitionNode
|
||||
}
|
||||
|
||||
var _ Node = (*LocalVariable)(nil)
|
||||
|
@ -147,8 +147,8 @@ const LocalVariableKind NodeKind = "LocalVariable"
|
|||
|
||||
// ModuleProperty is like a variable but belongs to a module.
|
||||
type ModuleProperty struct {
|
||||
variableNode
|
||||
moduleMemberNode
|
||||
VariableNode
|
||||
ModuleMemberNode
|
||||
}
|
||||
|
||||
var _ Node = (*ModuleProperty)(nil)
|
||||
|
@ -159,8 +159,8 @@ const ModulePropertyKind NodeKind = "ModuleProperty"
|
|||
|
||||
// ClassProperty is like a module property with some extra attributes.
|
||||
type ClassProperty struct {
|
||||
variableNode
|
||||
classMemberNode
|
||||
VariableNode
|
||||
ClassMemberNode
|
||||
Primary *bool `json:"primary,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -180,21 +180,21 @@ type Function interface {
|
|||
GetBody() *Block
|
||||
}
|
||||
|
||||
type functionNode struct {
|
||||
type FunctionNode struct {
|
||||
// note that this node intentionally omits any embedded base, to avoid diamond "inheritance".
|
||||
Parameters *[]*LocalVariable `json:"parameters,omitempty"`
|
||||
ReturnType *tokens.Type `json:"returnType,omitempty"`
|
||||
Body *Block `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
func (node *functionNode) GetParameters() *[]*LocalVariable { return node.Parameters }
|
||||
func (node *functionNode) GetReturnType() *tokens.Type { return node.ReturnType }
|
||||
func (node *functionNode) GetBody() *Block { return node.Body }
|
||||
func (node *FunctionNode) GetParameters() *[]*LocalVariable { return node.Parameters }
|
||||
func (node *FunctionNode) GetReturnType() *tokens.Type { return node.ReturnType }
|
||||
func (node *FunctionNode) GetBody() *Block { return node.Body }
|
||||
|
||||
// ModuleMethod is just a function with an accessibility modifier.
|
||||
type ModuleMethod struct {
|
||||
functionNode
|
||||
moduleMemberNode
|
||||
FunctionNode
|
||||
ModuleMemberNode
|
||||
}
|
||||
|
||||
var _ Node = (*ModuleMethod)(nil)
|
||||
|
@ -205,8 +205,8 @@ const ModuleMethodKind NodeKind = "ModuleMethod"
|
|||
|
||||
// ClassMethod is just like a module method with some extra attributes.
|
||||
type ClassMethod struct {
|
||||
functionNode
|
||||
classMemberNode
|
||||
FunctionNode
|
||||
ClassMemberNode
|
||||
Sealed *bool `json:"sealed,omitempty"`
|
||||
Abstract *bool `json:"abstract,omitempty"`
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@ type Expression interface {
|
|||
expression()
|
||||
}
|
||||
|
||||
type expressionNode struct {
|
||||
node
|
||||
type ExpressionNode struct {
|
||||
NodeValue
|
||||
}
|
||||
|
||||
func (node *expressionNode) expression() {}
|
||||
func (node *ExpressionNode) expression() {}
|
||||
|
||||
/* Literals */
|
||||
|
||||
|
@ -24,16 +24,16 @@ type Literal interface {
|
|||
GetRaw() *string // the raw literal, for round tripping purposes.
|
||||
}
|
||||
|
||||
type literalNode struct {
|
||||
expressionNode
|
||||
type LiteralNode struct {
|
||||
ExpressionNode
|
||||
Raw *string `json:"raw,omitempty"`
|
||||
}
|
||||
|
||||
func (node *literalNode) GetRaw() *string { return node.Raw }
|
||||
func (node *LiteralNode) GetRaw() *string { return node.Raw }
|
||||
|
||||
// NullLiteral represents the usual `null` constant.
|
||||
type NullLiteral struct {
|
||||
literalNode
|
||||
LiteralNode
|
||||
}
|
||||
|
||||
var _ Node = (*NullLiteral)(nil)
|
||||
|
@ -44,7 +44,7 @@ const NullLiteralKind NodeKind = "NullLiteral"
|
|||
|
||||
// BoolLiteral represents the usual Boolean literal constant (`true` or `false`).
|
||||
type BoolLiteral struct {
|
||||
literalNode
|
||||
LiteralNode
|
||||
Value bool `json:"value"`
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ const BoolLiteralKind NodeKind = "BoolLiteral"
|
|||
|
||||
// NumberLiteral represents a floating point IEEE 754 literal value.
|
||||
type NumberLiteral struct {
|
||||
literalNode
|
||||
LiteralNode
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ const NumberLiteralKind NodeKind = "NumberLiteral"
|
|||
|
||||
// StringLiteral represents a UTF8-encoded string literal.
|
||||
type StringLiteral struct {
|
||||
literalNode
|
||||
LiteralNode
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ const StringLiteralKind NodeKind = "StringLiteral"
|
|||
|
||||
// ArrayLiteral evaluates to a newly allocated array, with optional initialized elements.
|
||||
type ArrayLiteral struct {
|
||||
literalNode
|
||||
LiteralNode
|
||||
Type *tokens.Type `json:"type,omitempty"` // the optional type of array being produced.
|
||||
Size *Expression `json:"size,omitempty"` // an optional expression for the array size.
|
||||
Elements *[]Expression `json:"elements,omitempty"` // an optional array of element initializers.
|
||||
|
@ -94,7 +94,7 @@ const ArrayLiteralKind NodeKind = "ArrayLiteral"
|
|||
|
||||
// ObjectLiteral evaluates to a new object, with optional property initializers for primary properties.
|
||||
type ObjectLiteral struct {
|
||||
literalNode
|
||||
LiteralNode
|
||||
Type *tokens.Type `json:"type,omitempty"` // the optional type of object to produce.
|
||||
Properties *[]*ObjectLiteralProperty `json:"properties,omitempty"` // an optional array of property initializers.
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ const ObjectLiteralKind NodeKind = "ObjectLiteral"
|
|||
|
||||
// ObjectLiteralProperty initializes a single object literal property.
|
||||
type ObjectLiteralProperty struct {
|
||||
node
|
||||
NodeValue
|
||||
Name *Identifier `json:"name"` // the property to initialize.
|
||||
Value Expression `json:"value"` // the expression whose value to store into the property.
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ type LoadExpression interface {
|
|||
}
|
||||
|
||||
type loadExpressionNode struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
}
|
||||
|
||||
func (node *loadExpressionNode) loadExpression() {}
|
||||
|
@ -163,7 +163,7 @@ type CallExpression interface {
|
|||
}
|
||||
|
||||
type callExpressionNode struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
Arguments *[]Expression `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -195,8 +195,8 @@ const InvokeFunctionExpressionKind NodeKind = "InvokeFunctionExpression"
|
|||
|
||||
// LambdaExpression creates a lambda, a sort of "anonymous" function, that evaluates to a function type.
|
||||
type LambdaExpression struct {
|
||||
expressionNode
|
||||
functionNode
|
||||
ExpressionNode
|
||||
FunctionNode
|
||||
}
|
||||
|
||||
var _ Node = (*LambdaExpression)(nil)
|
||||
|
@ -208,7 +208,7 @@ const LambdaExpressionKind NodeKind = "LambdaExpression"
|
|||
|
||||
// UnaryOperatorExpression is the usual C-like unary operator.
|
||||
type UnaryOperatorExpression struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
Operator UnaryOperator `json:"operator"` // the operator type.
|
||||
Operand Expression `json:"operand"` // the right hand side operand.
|
||||
Postfix bool `json:"postfix"` // whether this is a postfix operator (only legal for UnaryPfixOperators).
|
||||
|
@ -239,7 +239,7 @@ const (
|
|||
|
||||
// BinaryOperatorExpression is the usual C-like binary operator (assignment, logical, operator, or relational).
|
||||
type BinaryOperatorExpression struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
Left Expression `json:"left"` // the left hand side.
|
||||
Operator BinaryOperator `json:"operator"` // the operator.
|
||||
Right Expression `json:"right"` // the right hand side.
|
||||
|
@ -300,7 +300,7 @@ const (
|
|||
|
||||
// CastExpression handles both nominal and structural casts, and will throw an exception upon failure.
|
||||
type CastExpression struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
Expression Expression `json:"expression"` // the source expression.
|
||||
Type tokens.Type `json:"type"` // the target type.
|
||||
}
|
||||
|
@ -312,7 +312,7 @@ const CastExpressionKind NodeKind = "CastExpression"
|
|||
|
||||
// IsInstExpression checks an expression for compatibility with the given type token, and evaluates to a bool.
|
||||
type IsInstExpression struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
Expression Expression `json:"expression"` // the source expression.
|
||||
Type tokens.Type `json:"type"` // the target type.
|
||||
}
|
||||
|
@ -324,7 +324,7 @@ const IsInstExpressionKind NodeKind = "IsInstExpression"
|
|||
|
||||
// TypeOfExpression gets the type token -- just a string -- of a particular type at runtime.
|
||||
type TypeOfExpression struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
Expression Expression `json:"expression"` // the source expression
|
||||
}
|
||||
|
||||
|
@ -337,7 +337,7 @@ const TypeOfExpressionKind NodeKind = "TypeOfExpression"
|
|||
|
||||
// ConditionalExpression evaluates to either a consequent or alternate based on a predicate condition.
|
||||
type ConditionalExpression struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
Condition Expression `json:"condition"` // a `bool` conditional expression.
|
||||
Consequent Expression `json:"consequent"` // the expression to evaluate to if `true`.
|
||||
Alternate Expression `json:"alternate"` // the expression to evaluate to if `false`.
|
||||
|
@ -350,7 +350,7 @@ const ConditionalExpressionKind NodeKind = "ConditionalExpression"
|
|||
|
||||
// SequenceExpression allows composition of multiple expressions into one. It evaluates to the last one.
|
||||
type SequenceExpression struct {
|
||||
expressionNode
|
||||
ExpressionNode
|
||||
Expressions []Expression `json:"expressions"`
|
||||
}
|
||||
|
||||
|
|
|
@ -29,18 +29,18 @@ var _ diag.Diagable = (Node)(nil)
|
|||
// takes its place, however (a) the kind is part of the serialized form, and (b) can be useful for debugging.
|
||||
type NodeKind string
|
||||
|
||||
type node struct {
|
||||
type NodeValue struct {
|
||||
Kind NodeKind `json:"kind"`
|
||||
Loc *Location `json:"loc,omitempty"`
|
||||
}
|
||||
|
||||
var _ diag.Diagable = (*node)(nil)
|
||||
var _ diag.Diagable = (*NodeValue)(nil)
|
||||
|
||||
func (node *node) nd() {}
|
||||
func (node *node) GetKind() NodeKind { return node.Kind }
|
||||
func (node *node) GetLoc() *Location { return node.Loc }
|
||||
func (node *NodeValue) nd() {}
|
||||
func (node *NodeValue) GetKind() NodeKind { return node.Kind }
|
||||
func (node *NodeValue) GetLoc() *Location { return node.Loc }
|
||||
|
||||
func (node *node) Where() (*diag.Document, *diag.Location) {
|
||||
func (node *NodeValue) Where() (*diag.Document, *diag.Location) {
|
||||
// TODO: consider caching Document objects; allocating one per Node is wasteful.
|
||||
// TODO: for development scenarios, it would be really great to recover the original source file text for purposes
|
||||
// of the diag.Document part. Doing so would give nice error messages tied back to the original source code
|
||||
|
@ -64,7 +64,7 @@ func (node *node) Where() (*diag.Document, *diag.Location) {
|
|||
|
||||
// Identifier represents a simple string token associated with its source location context.
|
||||
type Identifier struct {
|
||||
node
|
||||
NodeValue
|
||||
Ident tokens.Name `json:"ident"` // a valid identifier: (letter | "_") (letter | digit | "_")*
|
||||
}
|
||||
|
||||
|
|
|
@ -8,16 +8,16 @@ type Statement interface {
|
|||
statement()
|
||||
}
|
||||
|
||||
type statementNode struct {
|
||||
node
|
||||
type StatementNode struct {
|
||||
NodeValue
|
||||
}
|
||||
|
||||
func (node *statementNode) statement() {}
|
||||
func (node *StatementNode) statement() {}
|
||||
|
||||
/* Blocks */
|
||||
|
||||
type Block struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Statements []Statement `json:"statements"`
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ const BlockKind NodeKind = "Block"
|
|||
/* Local Variables */
|
||||
|
||||
type LocalVariableDeclaration struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Local *LocalVariable `json:"local"`
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ const LocalVariableDeclarationKind NodeKind = "LocalVariableDeclaration"
|
|||
/* Try/Catch/Finally */
|
||||
|
||||
type TryCatchFinally struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
TryBlock *Block `json:"tryBlock"`
|
||||
CatchBlocks *[]*TryCatchBlock `json:"catchBlocks,omitempty"`
|
||||
FinallyBlock *Block `json:"finallyBlock"`
|
||||
|
@ -53,7 +53,7 @@ var _ Statement = (*TryCatchFinally)(nil)
|
|||
const TryCatchFinallyKind NodeKind = "TryCatchFinally"
|
||||
|
||||
type TryCatchBlock struct {
|
||||
node
|
||||
NodeValue
|
||||
Block *Block `json:"block"`
|
||||
Exception *LocalVariable `json:"exception,omitempty"`
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ var _ Node = (*TryCatchBlock)(nil)
|
|||
|
||||
// BreakStatement is the usual C-style `break` (only valid within loops).
|
||||
type BreakStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Label *Identifier `json:"identifier,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ const BreakStatementKind NodeKind = "BreakStatement"
|
|||
|
||||
// ContinueStatement is the usual C-style `continue` (only valid within loops).
|
||||
type ContinueStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Label *Identifier `json:"identifier,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ const ContinueStatementKind NodeKind = "ContinueStatement"
|
|||
// IfStatement is the usual C-style `if`. To simplify the MuIL AST, this is the only conditional statement available.
|
||||
// All higher-level conditional constructs such as `switch`, if`/`else if`/..., etc., must be desugared into it.
|
||||
type IfStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Condition Expression `json:"condition"` // a `bool` conditional expression.
|
||||
Consequent Statement `json:"consequent"` // the statement to execute if `true`.
|
||||
Alternate *Statement `json:"alternative,omitempty"` // the optional statement to execute if `false`.
|
||||
|
@ -100,7 +100,7 @@ const IfStatementKind NodeKind = "IfStatement"
|
|||
|
||||
// LabeledStatement associates an identifier with a statement for purposes of labeled jumps.
|
||||
type LabeledStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Label *Identifier `json:"label"`
|
||||
Statement Statement `json:"statement"`
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ const LabeledStatementKind NodeKind = "LabeledStatement"
|
|||
|
||||
// ReturnStatement is the usual C-style `return`, to exit a function.
|
||||
type ReturnStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Expression *Expression `json:"expression,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ const ReturnStatementKind NodeKind = "ReturnStatement"
|
|||
|
||||
// ThrowStatement maps to raising an exception, usually `throw`, in the source language.
|
||||
type ThrowStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Expression *Expression `json:"expression,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ const ThrowStatementKind NodeKind = "ThrowStatement"
|
|||
// WhileStatement is the usual C-style `while`. To simplify the MuIL AST, this is the only looping statement available.
|
||||
// All higher-level looping constructs such as `for`, `foreach`, `do`/`while`, etc. must be desugared into it.
|
||||
type WhileStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Test Expression `json:"test"` // a `bool` statement indicating whether to condition.
|
||||
Body *Block `json:"block"` // the body to execute provided the test remains `true`.
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ const WhileStatementKind NodeKind = "WhileStatement"
|
|||
|
||||
// EmptyStatement is a statement with no effect.
|
||||
type EmptyStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
}
|
||||
|
||||
var _ Node = (*EmptyStatement)(nil)
|
||||
|
@ -159,7 +159,7 @@ const EmptyStatementKind NodeKind = "EmptyStatement"
|
|||
|
||||
// MultiStatement groups multiple statements into one; unlike a block, it doesn't introduce a new lexical scope.
|
||||
type MultiStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Statements []Statement `json:"statements"`
|
||||
}
|
||||
|
||||
|
@ -170,7 +170,7 @@ const MultiStatementKind NodeKind = "MultiStatement"
|
|||
|
||||
// ExpressionStatement performs an expression, in a statement position, and ignores its result.
|
||||
type ExpressionStatement struct {
|
||||
statementNode
|
||||
StatementNode
|
||||
Expression Expression `json:"expression"`
|
||||
}
|
||||
|
||||
|
|
247
pkg/compiler/types/convert_test.go
Normal file
247
pkg/compiler/types/convert_test.go
Normal file
|
@ -0,0 +1,247 @@
|
|||
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/marapongo/mu/pkg/compiler/ast"
|
||||
"github.com/marapongo/mu/pkg/compiler/symbols"
|
||||
"github.com/marapongo/mu/pkg/pack"
|
||||
"github.com/marapongo/mu/pkg/tokens"
|
||||
)
|
||||
|
||||
func newTestClass(name tokens.Name, extends symbols.Type, implements symbols.Types) *symbols.Class {
|
||||
pkg := symbols.NewPackageSym(&pack.Package{Name: "test"})
|
||||
mod := symbols.NewModuleSym(&ast.Module{
|
||||
DefinitionNode: ast.DefinitionNode{
|
||||
Name: &ast.Identifier{Ident: "test"},
|
||||
},
|
||||
}, pkg)
|
||||
return symbols.NewClassSym(&ast.Class{
|
||||
ModuleMemberNode: ast.ModuleMemberNode{
|
||||
DefinitionNode: ast.DefinitionNode{
|
||||
Name: &ast.Identifier{Ident: name},
|
||||
},
|
||||
},
|
||||
}, mod, extends, implements)
|
||||
}
|
||||
|
||||
func assertCanConvert(t *testing.T, from symbols.Type, to symbols.Type) {
|
||||
assert.True(t, CanConvert(from, to), fmt.Sprintf("convert(%v,%v)", from, to))
|
||||
}
|
||||
|
||||
func assertCannotConvert(t *testing.T, from symbols.Type, to symbols.Type) {
|
||||
assert.False(t, CanConvert(from, to), fmt.Sprintf("convert(%v,%v)", from, to))
|
||||
}
|
||||
|
||||
// TestIdentityConversions tests converting types to themselves.
|
||||
func TestIdentityConversions(t *testing.T) {
|
||||
for _, prim := range Primitives {
|
||||
assertCanConvert(t, prim, prim)
|
||||
}
|
||||
|
||||
assertCanConvert(t, AnyArray, AnyArray)
|
||||
assertCanConvert(t, AnyMap, AnyMap)
|
||||
|
||||
class := newTestClass("class", nil, nil)
|
||||
assertCanConvert(t, class, class)
|
||||
}
|
||||
|
||||
// TestAnyConversions tests converting a bunch of different types to "any".
|
||||
func TestAnyConversions(t *testing.T) {
|
||||
for _, prim := range Primitives {
|
||||
assertCanConvert(t, prim, Any)
|
||||
}
|
||||
|
||||
assertCanConvert(t, AnyArray, Any)
|
||||
assertCanConvert(t, AnyMap, Any)
|
||||
|
||||
class := newTestClass("class", nil, nil)
|
||||
assertCanConvert(t, class, Any)
|
||||
}
|
||||
|
||||
// TestNullConversions tests converting to and from "null".
|
||||
func TestNullConversions(t *testing.T) {
|
||||
for _, prim := range Primitives {
|
||||
assertCanConvert(t, prim, Null)
|
||||
assertCanConvert(t, Null, prim)
|
||||
}
|
||||
|
||||
assertCanConvert(t, AnyArray, Null)
|
||||
assertCanConvert(t, Null, AnyArray)
|
||||
assertCanConvert(t, AnyMap, Null)
|
||||
assertCanConvert(t, Null, AnyMap)
|
||||
|
||||
class := newTestClass("class", nil, nil)
|
||||
assertCanConvert(t, class, Null)
|
||||
assertCanConvert(t, Null, class)
|
||||
}
|
||||
|
||||
// TestClassConversions tests converting classes to their base types.
|
||||
func TestClassConversions(t *testing.T) {
|
||||
base := newTestClass("base", nil, nil)
|
||||
|
||||
// A simple extends case.
|
||||
{
|
||||
derived := newTestClass("derived", base, nil)
|
||||
assertCanConvert(t, derived, Any)
|
||||
assertCanConvert(t, derived, base)
|
||||
}
|
||||
|
||||
// An implements case.
|
||||
{
|
||||
derived := newTestClass("derived", nil, symbols.Types{base})
|
||||
assertCanConvert(t, derived, Any)
|
||||
assertCanConvert(t, derived, base)
|
||||
}
|
||||
|
||||
// A case where the base is different, but an implement exists.
|
||||
{
|
||||
base2 := newTestClass("base2", nil, nil)
|
||||
base3 := newTestClass("base3", nil, nil)
|
||||
base4 := newTestClass("base4", nil, nil)
|
||||
derived := newTestClass("derived", base2, symbols.Types{base3, base, base4})
|
||||
assertCanConvert(t, derived, Any)
|
||||
assertCanConvert(t, derived, base)
|
||||
}
|
||||
|
||||
// Negative test; cannot convert to primitives or incorrect bases.
|
||||
{
|
||||
base2 := newTestClass("base2", nil, nil)
|
||||
derived := newTestClass("derived", base2, nil)
|
||||
for _, prim := range Primitives {
|
||||
if prim != Any && prim != Null {
|
||||
assertCannotConvert(t, derived, prim)
|
||||
}
|
||||
}
|
||||
assertCannotConvert(t, derived, base)
|
||||
}
|
||||
}
|
||||
|
||||
// TestArrayConversions tests converting between structurally identical array types.
|
||||
func TestArrayConversions(t *testing.T) {
|
||||
// Simple primitive cases:
|
||||
for _, prim := range Primitives {
|
||||
arr1 := symbols.NewArrayType(prim)
|
||||
assertCanConvert(t, arr1, Any)
|
||||
arr2 := symbols.NewArrayType(prim)
|
||||
assertCanConvert(t, arr1, arr2)
|
||||
}
|
||||
|
||||
// Check that classes work for identity, but not conversions (arrays are not covariant):
|
||||
base := newTestClass("base", nil, nil)
|
||||
derived := newTestClass("derived", base, nil)
|
||||
arr1 := symbols.NewArrayType(base)
|
||||
arr2 := symbols.NewArrayType(base)
|
||||
assertCanConvert(t, arr1, arr2)
|
||||
arr3 := symbols.NewArrayType(derived)
|
||||
assertCannotConvert(t, arr2, arr3)
|
||||
assertCannotConvert(t, arr3, arr2)
|
||||
|
||||
// And also ensure that covariant conversions for primitive "any" isn't allowed either:
|
||||
arr4 := symbols.NewArrayType(Any)
|
||||
assertCannotConvert(t, arr3, arr4)
|
||||
assertCannotConvert(t, arr4, arr3)
|
||||
}
|
||||
|
||||
// TestMapConversions tests converting between structurally identical map types.
|
||||
func TestMapConversions(t *testing.T) {
|
||||
// Map types with the same key and element types can convert.
|
||||
for _, prim := range Primitives {
|
||||
map1 := symbols.NewMapType(String, prim)
|
||||
assertCanConvert(t, map1, Any)
|
||||
map2 := symbols.NewMapType(String, prim)
|
||||
assertCanConvert(t, map1, map2)
|
||||
}
|
||||
|
||||
// Check that classes work for identity, but not conversions (maps are not covariant):
|
||||
base := newTestClass("base", nil, nil)
|
||||
derived := newTestClass("derived", base, nil)
|
||||
map1 := symbols.NewMapType(String, base)
|
||||
map2 := symbols.NewMapType(String, base)
|
||||
assertCanConvert(t, map1, map2)
|
||||
map3 := symbols.NewMapType(String, derived)
|
||||
assertCannotConvert(t, map2, map3)
|
||||
assertCannotConvert(t, map3, map2)
|
||||
|
||||
// And also ensure that covariant conversions for primitive "any" isn't allowed either:
|
||||
map4 := symbols.NewMapType(String, Any)
|
||||
assertCannotConvert(t, map3, map4)
|
||||
assertCannotConvert(t, map4, map3)
|
||||
}
|
||||
|
||||
// TestFuncConversions tests converting between structurally identical or safely variant function types.
|
||||
func TestFuncConversions(t *testing.T) {
|
||||
// Empty functions convert to each other.
|
||||
{
|
||||
func1 := symbols.NewFunctionType(nil, nil)
|
||||
assertCanConvert(t, func1, Any)
|
||||
func2 := symbols.NewFunctionType(nil, nil)
|
||||
assertCanConvert(t, func1, func2)
|
||||
assertCanConvert(t, func2, func1)
|
||||
}
|
||||
|
||||
// Simple equivalent functions convert to each other.
|
||||
for _, param1 := range Primitives {
|
||||
for _, param2 := range Primitives {
|
||||
func1 := symbols.NewFunctionType([]symbols.Type{param1, param2}, nil)
|
||||
assertCanConvert(t, func1, Any)
|
||||
func2 := symbols.NewFunctionType([]symbols.Type{param1, param2}, nil)
|
||||
assertCanConvert(t, func1, func2)
|
||||
assertCanConvert(t, func2, func1)
|
||||
|
||||
for _, ret := range Primitives {
|
||||
func3 := symbols.NewFunctionType([]symbols.Type{param1, param2}, ret)
|
||||
assertCanConvert(t, func3, Any)
|
||||
func4 := symbols.NewFunctionType([]symbols.Type{param1, param2}, ret)
|
||||
assertCanConvert(t, func3, func4)
|
||||
assertCanConvert(t, func4, func3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base := newTestClass("base", nil, nil)
|
||||
derived := newTestClass("derived", base, nil)
|
||||
|
||||
// Parameter types are contravariant (source may be weaker).
|
||||
{
|
||||
// Simple primitive case.
|
||||
strong1 := symbols.NewFunctionType([]symbols.Type{String, Number}, String)
|
||||
weak1 := symbols.NewFunctionType([]symbols.Type{Any, Any}, String)
|
||||
assertCanConvert(t, weak1, strong1)
|
||||
assertCannotConvert(t, strong1, weak1)
|
||||
|
||||
// More complex subtyping case.
|
||||
strong2 := symbols.NewFunctionType([]symbols.Type{derived, derived}, String)
|
||||
weak2 := symbols.NewFunctionType([]symbols.Type{base, base}, String)
|
||||
assertCanConvert(t, weak2, strong2)
|
||||
assertCannotConvert(t, strong2, weak2)
|
||||
}
|
||||
|
||||
// Return types are covariant (source may be strengthened).
|
||||
{
|
||||
// Simple primitive case.
|
||||
strong1 := symbols.NewFunctionType([]symbols.Type{String, Number}, String)
|
||||
weak1 := symbols.NewFunctionType([]symbols.Type{String, Number}, Any)
|
||||
assertCanConvert(t, strong1, weak1)
|
||||
assertCannotConvert(t, weak1, strong1)
|
||||
|
||||
// More complex subtyping case.
|
||||
strong2 := symbols.NewFunctionType([]symbols.Type{String, Number}, derived)
|
||||
weak2 := symbols.NewFunctionType([]symbols.Type{String, Number}, base)
|
||||
assertCanConvert(t, strong2, weak2)
|
||||
assertCannotConvert(t, weak2, strong2)
|
||||
}
|
||||
|
||||
// Both can happen at once.
|
||||
{
|
||||
from := symbols.NewFunctionType([]symbols.Type{Any, base, Any}, derived)
|
||||
to := symbols.NewFunctionType([]symbols.Type{String, derived, Number}, base)
|
||||
assertCanConvert(t, from, to)
|
||||
assertCannotConvert(t, to, from)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue