Add type conversion tests

This commit is contained in:
joeduffy 2017-01-21 10:20:47 -08:00
parent a58cd7e2a0
commit 02d7ba2417
5 changed files with 330 additions and 83 deletions

View file

@ -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"`
}

View file

@ -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"`
}

View file

@ -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 | "_")*
}

View file

@ -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"`
}

View 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)
}
}