fspl/analyzer/assignment.go

559 lines
16 KiB
Go

package analyzer
import "fmt"
import "math"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/integer"
type strictness int; const (
// Structural equivalence, but named types are treated as opaque and are
// not tested. This applies to the root of the type, and to types
// enclosed as members, elements, etc. This is the assignment mode most
// often used.
strict strictness = iota
// Like strict, but the root types specifically are compared as if they
// were not named. analyzer.ReduceToBase() is used to accomplish this.
// Assignment to private/opaque types is not allowed.
weak
// Full structural equivalence, and named types are always reduced.
// Assignment to private/opaque types is not allowed.
structural
// Data of the source type must be convert-able to the destination type.
// This is used in value casts. Assignment to private/opaque types
// is not allowed.
coerce
// All assignment rules are ignored. This is only used in bit casts.
force
)
// canAssign takes in an analyzed destination type and an analyzed source type,
// and determines whether source can be assigned to destination.
func (this *Tree) canAssign (
pos errors.Position,
// assuming both are analyzed already
destination entity.Type,
mode strictness,
source entity.Type,
) error {
fail := func () error {
switch mode {
case strict:
return errors.Errorf (
pos, "expected %v",
entity.FormatType(destination))
case weak:
return errors.Errorf (
pos, "cannot use %v as %v",
entity.FormatType(source),
entity.FormatType(destination))
case structural:
return errors.Errorf (
pos, "cannot convert from %v to %v",
entity.FormatType(source),
entity.FormatType(destination))
default:
panic(fmt.Sprint (
"BUG: analyzer doesnt know about mode", mode))
}
}
// check access permissions
if destination != nil && source != nil &&
mode != strict && mode != force {
err := this.typeOpaque(pos, destination)
if err != nil { return err }
err = this.typeOpaque(pos, source)
if err != nil { return err }
}
// short circuit if force is selected
if mode == force { return nil }
// check for coerce strictness
if mode == coerce {
return this.canAssignCoerce(pos, destination, source)
}
// do structural equivalence if structural is selected
if mode == structural {
if this.areStructurallyEquivalent(destination, source) {
return nil
} else {
return fail()
}
}
// first, check if this is interface assignment
if destination, ok := this.isInterface(destination); ok {
return this.canAssignInterface(pos, destination, source)
}
// then, check for union assignment
if _, ok := this.isUnion(destination); ok {
return this.canAssignUnion(pos, destination, source)
}
// then, check for array to slice assignment
if destination, ok := ReduceToBase(destination).(*entity.TypeSlice); ok {
if source, ok := ReduceToBase(source). (*entity.TypeArray); ok {
return this.canAssignSliceArray(pos, destination, source)
}}
// if weak, only compare the first base types
if mode == weak {
destination = ReduceToBase(destination)
source = ReduceToBase(source)
}
switch destination := destination.(type) {
// no type
case nil:
return nil
// named type
case *entity.TypeNamed:
if source, ok := source.(*entity.TypeNamed); ok {
if destination.Name == source.Name {
return nil
}
}
// composite base types
case *entity.TypePointer:
if source, ok := source.(*entity.TypePointer); ok {
mode := mode
if mode > structural { mode = structural }
return this.canAssign (
pos, destination.Referenced,
mode, source.Referenced)
}
case *entity.TypeSlice:
if source, ok := source.(*entity.TypeSlice); ok {
mode := mode
if mode > structural { mode = structural }
return this.canAssign (
pos, destination.Element,
mode, source.Element)
}
case *entity.TypeArray:
if source, ok := source.(*entity.TypeArray); ok {
mode := mode
if mode > structural { mode = structural }
return this.canAssign (
pos, destination.Element,
mode, source.Element)
}
// other base type
case *entity.TypeStruct,
*entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
if destination.Equals(source) {
return nil
}
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type", destination))
}
return fail()
}
// canAssignCoerce determines if data of an analyzed source type can be
// converted into data of an analyzed destination type.
func (this *Tree) canAssignCoerce (
pos errors.Position,
destination entity.Type,
source entity.Type,
) error {
fail := func () error {
return errors.Errorf (
pos, "cannot convert from %v to %v",
entity.FormatType(source),
entity.FormatType(destination))
}
destination = ReduceToBase(destination)
source = ReduceToBase(source)
switch destination := destination.(type) {
// no type
case nil:
return nil
// base type
case *entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
switch source.(type) {
case *entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
// integers, floats, and words may all be assigned to
// eachother
return nil
default:
return fail()
}
case *entity.TypePointer:
switch source := source.(type) {
case *entity.TypeSlice:
// a slice may be assigned to a pointer if its element
// type can be assigned (structural) to the pointer's
// referenced type
err := this.canAssign (
pos, destination.Referenced,
structural, source.Element)
if err != nil {
return fail()
}
default:
// a pointer may be assigned to another pointer if they
// are structurally equivalent
if !this.areStructurallyEquivalent(destination, source) {
return fail()
}
}
case *entity.TypeSlice,
*entity.TypeArray,
*entity.TypeStruct:
// composite types may be assigned to eachother if they are
// structurally equivalent
if !this.areStructurallyEquivalent(destination, source) {
return fail()
}
default: fail()
}
return nil
}
// canAssignSliceArray takes in an analyzed slice type and an analyzed array
// type, and determines whether the array can be assigned to the slice.
func (this *Tree) canAssignSliceArray (
pos errors.Position,
destination *entity.TypeSlice,
source *entity.TypeArray,
) error {
err := this.canAssign(pos, destination.Element, strict, source.Element)
if err != nil {
return errors.Errorf (
pos, "expected %v",
entity.FormatType(destination))
} else {
return nil
}
}
// canAssignInterface takes in an analyzed interface destination type and an
// analyzed source type, and determines whether source can be assigned to
// destination.
func (this *Tree) canAssignInterface (
pos errors.Position,
destination *entity.TypeInterface,
source entity.Type,
) error {
for name, behavior := range destination.BehaviorMap {
// get method
method, err := this.analyzeMethodOrBehavior (
pos, source, name)
if err != nil { return err }
// extract signature
var signature *entity.Signature
switch method.(type) {
case *entity.Signature:
signature = method.(*entity.Signature)
case *entity.Method:
signature = method.(*entity.Method).Signature
default:
panic(fmt.Sprint (
"Tree.analyzeMethodOrBehavior returned",
method))
}
// check equivalence
if !signature.Equals(behavior) {
return errors.Errorf (
pos, "%v has wrong signature for method %v",
source, name)
}
}
return nil
}
// canAssignUnion takes in an analyzed union destination type and an
// analyzed source type, and determines whether source can be assigned to
// destination.
func (this *Tree) canAssignUnion (
pos errors.Position,
destination entity.Type,
source entity.Type,
) error {
sourceHash := source.Hash()
union, _ := this.isUnion(destination)
// check if types are identical
if entity.TypesEqual(source, destination) { return nil }
// check if type is included within the union
if _, ok := union.AllowedMap[sourceHash]; ok { return nil }
return errors.Errorf (
pos, "%v is not included within union %v",
source, destination)
}
// areStructurallyEquivalent tests whether two types are structurally equivalent
// to eachother.
func (this *Tree) areStructurallyEquivalent (left, right entity.Type) bool {
left = ReduceToBase(left)
right = ReduceToBase(right)
switch left.(type) {
case nil: return false
// composite
case *entity.TypePointer:
left := left.(*entity.TypePointer)
right, ok := right.(*entity.TypePointer)
if !ok { return false }
return this.areStructurallyEquivalent(left.Referenced, right.Referenced)
case *entity.TypeSlice:
left := left.(*entity.TypeSlice)
right, ok := right.(*entity.TypeSlice)
if !ok { return false }
return this.areStructurallyEquivalent(left.Element, right.Element)
case *entity.TypeArray:
left := left.(*entity.TypeArray)
right, ok := right.(*entity.TypeArray)
if !ok { return false }
return left.Length == right.Length &&
this.areStructurallyEquivalent(left.Element, right.Element)
case *entity.TypeStruct:
left := left.(*entity.TypeStruct)
right, ok := right.(*entity.TypeStruct)
if !ok { return false }
if len(left.MemberMap) != len(right.MemberMap) { return false }
for name, member := range left.MemberMap {
other, ok := right.MemberMap[name]
if !ok { return false }
if !this.areStructurallyEquivalent(member.Type(), other.Type()) {
return false
}
}
// terminals
case *entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
return left.Equals(right)
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type", left))
}
return false
}
// isLocationExpression returns whether or not an expression is a valid location
// expression.
func (this *Tree) isLocationExpression (expression entity.Expression) error {
// TODO: have some Name() method of Expression so we can use it in a
// default case here. this will prevent crashes when new features are
// added.
cannot := func (pos errors.Position, kind string) error {
return errors.Errorf(pos, "cannot assign to %s", kind)
}
switch expression := expression.(type) {
case *entity.Variable:
return nil
case *entity.Declaration:
return nil
case *entity.Call:
return cannot(expression.Position(), "function call")
case *entity.MethodCall:
return cannot(expression.Position(), "method call")
case *entity.Subscript:
return this.isLocationExpression(expression.Slice)
case *entity.Slice:
return cannot(expression.Position(), "slice operation")
case *entity.Dereference:
return this.isLocationExpression(expression.Pointer)
case *entity.Reference:
return cannot(expression.Position(), "reference operation")
case *entity.ValueCast:
return cannot(expression.Position(), "value cast")
case *entity.BitCast:
return cannot(expression.Position(), "bit cast")
case *entity.Operation:
return cannot(expression.Position(), fmt.Sprintf (
"cannot assign to %v operation",
expression.Operator))
case *entity.Block:
return cannot(expression.Position(), "block")
case *entity.MemberAccess:
return this.isLocationExpression (
expression.Source)
case *entity.IfElse:
return cannot(expression.Position(), "if/else")
case *entity.Match:
return cannot(expression.Position(), "match")
case *entity.Loop:
return cannot(expression.Position(), "loop")
case *entity.Break:
return cannot(expression.Position(), "break statement")
case *entity.Return:
return cannot(expression.Position(), "return statement")
case *entity.LiteralInt:
return cannot(expression.Position(), "integer literal")
case *entity.LiteralFloat:
return cannot(expression.Position(), "float literal")
case *entity.LiteralString:
return cannot(expression.Position(), "string literal")
case *entity.LiteralArray:
return cannot(expression.Position(), "array literal")
case *entity.LiteralStruct:
return cannot(expression.Position(), "struct literal")
case *entity.LiteralBoolean:
return cannot(expression.Position(), "boolean literal")
case *entity.LiteralNil:
return cannot(expression.Position(), "nil literal")
default:
panic(fmt.Sprint (
"BUG: analyzer doesnt know about expression",
expression))
}
}
// ReduceToBase takes in an analyzed type and reduces it to its first non-name
// type.
func ReduceToBase (ty entity.Type) entity.Type {
if namedty, ok := ty.(*entity.TypeNamed); ok {
return ReduceToBase(namedty.Type)
} else {
return ty
}
}
// isInterface takes in an analyzed type and returns true and an interface if
// that type refers to an interface. If not, it returns nil, false.
func (this *Tree) isInterface (ty entity.Type) (*entity.TypeInterface, bool) {
ty = ReduceToBase(ty)
switch ty.(type) {
case *entity.TypeInterface: return ty.(*entity.TypeInterface), true
default: return nil, false
}
}
// isUnion takes in an analyzed type and returns true and a union if that type
// refers to a union. If not, it returns nil, false.
func (this *Tree) isUnion (ty entity.Type) (*entity.TypeUnion, bool) {
ty = ReduceToBase(ty)
switch ty.(type) {
case *entity.TypeUnion: return ty.(*entity.TypeUnion), true
default: return nil, false
}
}
// isNumeric returns whether or not the specified type is a number.
func isNumeric (ty entity.Type) bool {
ty = ReduceToBase(ty)
switch ty.(type) {
case *entity.TypeInt, *entity.TypeFloat, *entity.TypeWord: return true
default: return false
}
}
// isInteger returns whether or not the specified type is an integer.
func isInteger (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypeInt, *entity.TypeWord: return true
default: return false
}
}
// isPointer returns whether or not the specified type is a pointer.
func isPointer (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypePointer: return true
default: return false
}
}
// isOrdered returns whether or not the values of the specified type can be
// ordered.
func isOrdered (ty entity.Type) bool {
return isNumeric(ty)
}
// isBoolean returns whether or not the specified type is a boolean.
func isBoolean (ty entity.Type) bool {
for {
named, ok := ty.(*entity.TypeNamed)
if !ok { return false }
if named.Name == "Bool" { return true }
}
}
// isFloat returns whether or not the specified type is a float.
func isFloat (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypeFloat: return true
default: return false
}
}
// IsUnsigned returns whether or not the specified type is an unsigned integer.
func IsUnsigned (ty entity.Type) bool {
switch ty := ReduceToBase(ty).(type) {
case *entity.TypeInt: return !ty.Signed
case *entity.TypeWord: return !ty.Signed
default: return false
}
}
// integerWidth returns the width of the type, if it is an integer.
func integerWidth (ty entity.Type) (int, bool) {
switch ty := ReduceToBase(ty).(type) {
case *entity.TypeInt: return ty.Width, true
default: return 0, false
}
}
// inRange returns whether the specified value can fit within the given integer
// type.
func inRange (ty entity.Type, value int64) bool {
base := ReduceToBase(ty)
switch base.(type) {
case *entity.TypeInt:
base := base.(*entity.TypeInt)
if base.Signed {
return value > integer.SignedMin(base.Width) &&
value < integer.SignedMax(base.Width)
} else {
return value >= 0 &&
uint64(value) < integer.UnsignedMax(base.Width)
}
case *entity.TypeWord:
base := base.(*entity.TypeWord)
if base.Signed {
return value > math.MinInt && value < math.MaxInt
} else {
return value >= 0 && uint64(value) < math.MaxUint
}
default: return false
}
}