Bind unary and binary operators

This commit is contained in:
joeduffy 2017-01-27 13:51:36 -08:00
parent 349e98abbd
commit df9246dfa1
2 changed files with 143 additions and 7 deletions

View file

@ -107,6 +107,25 @@ func (a *astBinder) After(node ast.Node) {
contract.Assert(!isExpr || a.b.ctx.RequireType(expr) != nil)
}
// Utility functions
// isLValue checks whether the target is a valid l-value.
func (a *astBinder) isLValue(expr ast.Expression) bool {
// If the target is the result of a load location, it is okay.
if _, isload := expr.(*ast.LoadLocationExpression); isload {
return true
}
// Otherwise, if the target is a pointer dereference, it is also okay.
if unop, isunop := expr.(*ast.UnaryOperatorExpression); isunop {
if unop.Operator == ast.OpDereference {
return true
}
}
return false
}
// Statements
func (a *astBinder) visitBlock(node *ast.Block) {
@ -291,8 +310,8 @@ func (a *astBinder) checkLoadLocationExpression(node *ast.LoadLocationExpression
}
}
// Register a pointer type so that this expression is a valid l-expr.
a.b.ctx.RegisterType(node, symbols.NewPointerType(ty))
// Register the type; not that, although this is a valid l-value, we do not create a pointer out of it implicitly.
a.b.ctx.RegisterType(node, ty)
}
func (a *astBinder) checkNewExpression(node *ast.NewExpression) {
@ -334,13 +353,123 @@ func (a *astBinder) checkLambdaExpression(node *ast.LambdaExpression) {
}
func (a *astBinder) checkUnaryOperatorExpression(node *ast.UnaryOperatorExpression) {
// TODO: check operands.
// TODO: figure this out; almost certainly a number.
// First check that prefix/postfix isn't wrong.
switch node.Operator {
case ast.OpDereference, ast.OpAddressof, ast.OpUnaryPlus,
ast.OpUnaryMinus, ast.OpLogicalNot, ast.OpBitwiseNot:
if node.Postfix {
a.b.Diag().Errorf(errors.ErrorUnaryOperatorMustBePrefix.At(node), node.Operator)
}
case ast.OpPlusPlus, ast.OpMinusMinus:
// both prefix/post are fine.
default:
contract.Failf("Unrecognized unary operator: %v", node.Operator)
}
// Now check the types and assign a type to this expression.
opty := a.b.ctx.RequireType(node.Operand)
switch node.Operator {
case ast.OpDereference:
// The right hand side must be a pointer; produce its element type:
switch ot := opty.(type) {
case *symbols.PointerType:
a.b.ctx.RegisterType(node, ot.Element)
default:
a.b.Diag().Errorf(errors.ErrorUnaryOperatorInvalidForType.At(node), node.Operator, ot, "pointer type")
a.b.ctx.RegisterType(node, ot)
}
case ast.OpAddressof:
// The target must be a legal l-value expression:
if !a.isLValue(node.Operand) {
a.b.Diag().Errorf(errors.ErrorUnaryOperatorInvalidForOperand.At(node), node.Operator,
node.Operand.GetKind(), "load location")
}
a.b.ctx.RegisterType(node, symbols.NewPointerType(opty))
case ast.OpUnaryPlus, ast.OpUnaryMinus, ast.OpBitwiseNot, ast.OpPlusPlus, ast.OpMinusMinus:
// The target must be a number:
if opty != types.Number {
a.b.Diag().Errorf(errors.ErrorUnaryOperatorInvalidForType.At(node), node.Operator, opty, types.Number)
}
a.b.ctx.RegisterType(node, types.Number)
case ast.OpLogicalNot:
// The target must be a boolean:
if opty != types.Bool {
a.b.Diag().Errorf(errors.ErrorUnaryOperatorInvalidForType.At(node), node.Operator, opty, types.Bool)
}
a.b.ctx.RegisterType(node, types.Bool)
default:
contract.Failf("Unrecognized unary operator: %v", node.Operator)
}
}
func (a *astBinder) checkBinaryOperatorExpression(node *ast.BinaryOperatorExpression) {
// TODO: check operands.
// TODO: figure this out; almost certainly a number.
// Check that the operands are of the right type, and assign a type to this node.
lhs := a.b.ctx.RequireType(node.Left)
rhs := a.b.ctx.RequireType(node.Right)
switch node.Operator {
// Arithmetic and bitwise operators:
case ast.OpAdd, ast.OpSubtract, ast.OpMultiply, ast.OpDivide, ast.OpRemainder, ast.OpExponentiate,
ast.OpBitwiseShiftLeft, ast.OpBitwiseShiftRight, ast.OpBitwiseAnd, ast.OpBitwiseOr, ast.OpBitwiseXor:
// Both lhs and rhs must be numbers:
if lhs != types.Number {
a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node),
node.Operator, "LHS", lhs, types.Number)
}
if rhs != types.Number {
a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node),
node.Operator, "RHS", rhs, types.Number)
}
a.b.ctx.RegisterType(node, types.Number)
// Assignment operators:
case ast.OpAssign:
if !a.isLValue(node.Left) {
a.b.Diag().Errorf(errors.ErrorIllegalAssignmentLValue.At(node))
} else if !types.CanConvert(rhs, lhs) {
a.b.Diag().Errorf(errors.ErrorIllegalAssignmentTypes.At(node), rhs, lhs)
}
a.b.ctx.RegisterType(node, lhs)
case ast.OpAssignSum, ast.OpAssignDifference, ast.OpAssignProduct, ast.OpAssignQuotient,
ast.OpAssignRemainder, ast.OpAssignExponentiation, ast.OpAssignBitwiseShiftLeft, ast.OpAssignBitwiseShiftRight,
ast.OpAssignBitwiseAnd, ast.OpAssignBitwiseOr, ast.OpAssignBitwiseXor:
if !a.isLValue(node.Left) {
a.b.Diag().Errorf(errors.ErrorIllegalAssignmentLValue.At(node))
} else if lhs != types.Number {
a.b.Diag().Errorf(errors.ErrorIllegalNumericAssignmentLValue.At(node), node.Operator)
} else if !types.CanConvert(rhs, lhs) {
a.b.Diag().Errorf(errors.ErrorIllegalAssignmentTypes.At(node), rhs, lhs)
}
a.b.ctx.RegisterType(node, lhs)
// Conditional operators:
case ast.OpLogicalAnd, ast.OpLogicalOr:
// Both lhs and rhs must be booleans.
if lhs != types.Bool {
a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node),
node.Operator, "LHS", lhs, types.Bool)
}
if rhs != types.Bool {
a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node),
node.Operator, "RHS", rhs, types.Bool)
}
a.b.ctx.RegisterType(node, types.Bool)
// Relational operators:
case ast.OpLt, ast.OpLtEquals, ast.OpGt, ast.OpGtEquals:
// Both lhs and rhs must be numbers, and it produces a boolean.
if lhs != types.Number {
a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node),
node.Operator, "LHS", lhs, types.Number)
}
if rhs != types.Number {
a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node),
node.Operator, "RHS", rhs, types.Number)
}
a.b.ctx.RegisterType(node, types.Bool)
case ast.OpEquals, ast.OpNotEquals:
// Equality checking is valid on any type, and it always produces a boolean.
a.b.ctx.RegisterType(node, types.Bool)
}
}
func (a *astBinder) checkCastExpression(node *ast.CastExpression) {

View file

@ -19,5 +19,12 @@ var (
ErrorCannotNewAbstractClass = newError(512, "Cannot `new` an abstract class '%v'")
ErrorIllegalObjectLiteralType = newError(513,
"The type '%v' may not be used as an object literal type; only records and interfaces are permitted")
ErrorMissingRequiredProperty = newError(514, "Missing required property '%v'")
ErrorMissingRequiredProperty = newError(514, "Missing required property '%v'")
ErrorUnaryOperatorInvalidForType = newError(515, "The operator %v is invalid on operand type %v; expected %v")
ErrorUnaryOperatorInvalidForOperand = newError(516, "The operator %v is invalid on operand %v; expected %v")
ErrorUnaryOperatorMustBePrefix = newError(517, "The operator %v must be in a prefix position (not postfix)")
ErrorBinaryOperatorInvalidForType = newError(518, "The operator %v is invalid on %v operand type %v; expected %v")
ErrorIllegalAssignmentLValue = newError(519, "Cannot assign to the target LHS expression")
ErrorIllegalNumericAssignmentLValue = newError(520, "Cannot perform numeric assignment %v on a non-numeric LHS")
ErrorIllegalAssignmentTypes = newError(521, "Cannot assign a value of type %v to target of type %v")
)