diff --git a/langserver/java/expressiontypes/BinaryOpExpression.js b/langserver/java/expressiontypes/BinaryOpExpression.js index a0518b2..8158aa7 100644 --- a/langserver/java/expressiontypes/BinaryOpExpression.js +++ b/langserver/java/expressiontypes/BinaryOpExpression.js @@ -5,6 +5,8 @@ */ const { Expression } = require("./Expression"); const { JavaType, PrimitiveType } = require('java-mti'); +const ParseProblem = require('../parsetypes/parse-problem'); +const { AnyType, TypeIdentType } = require('../anys'); class BinaryOpExpression extends Expression { /** @@ -26,43 +28,45 @@ class BinaryOpExpression extends Expression { const operator = this.op.value; const lhstype = this.lhs.resolveExpression(ri); const rhstype = this.rhs.resolveExpression(ri); - if (operator === '+') { - const lhstypesig = lhstype instanceof JavaType && lhstype.typeSignature, - rhstypesig = rhstype instanceof JavaType && rhstype.typeSignature; - if (lhstypesig === 'Ljava/lang/String;') { - return lhstype; - } - if (lhstypesig === 'D' || rhstypesig === 'D') { - return PrimitiveType.map.D; - } - if (lhstypesig === 'F' || rhstypesig === 'F') { - return PrimitiveType.map.F; - } - if (lhstypesig === 'J' || rhstypesig === 'J') { - return PrimitiveType.map.J; - } - return PrimitiveType.map.I; + + if (lhstype instanceof AnyType || rhstype instanceof AnyType) { + return AnyType.Instance; } + + if (operator === 'instanceof') { + if (!(rhstype instanceof TypeIdentType)) { + ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Type expected`)); + } + if (!(lhstype instanceof JavaType)) { + ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Expression expected`)); + } + return PrimitiveType.map.Z; + } + + if (!(lhstype instanceof JavaType) || !(rhstype instanceof JavaType)) { + if (!(lhstype instanceof JavaType)) { + ri.problems.push(ParseProblem.Error(this.lhs.tokens, `Expression expected`)); + } + if (!(rhstype instanceof JavaType)) { + ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Expression expected`)); + } + return AnyType.Instance; + } + + const typekey = `${lhstype.typeSignature}#${rhstype.typeSignature}`; + + if (operator === '+' && typekey.startsWith('Ljava/lang/String;')) { + // string appending is compatible with all types + return lhstype; + } + if (/^([*/%&|^+-]?=|<<=|>>>?=)$/.test(operator)) { + checkOperator(operator.slice(0,-1), ri, this.op, typekey, lhstype, rhstype); // result of assignments are lhs return lhstype; } - if (/^[*/%-]$/.test(operator)) { - // math operators - return PrimitiveType.map.I; - } - if (/^(<<|>>>?)$/.test(operator)) { - // shift operators - return PrimitiveType.map.I; - } - if (/^[&|^]$/.test(operator)) { - // bitwise or logical operators - return lhstype === PrimitiveType.map.Z ? lhstype : PrimitiveType.map.I; - } - if (operator === 'instanceof') { - } - // logical/comparison operators - return PrimitiveType.map.Z; + + return checkOperator(operator, ri, this.op, typekey, lhstype, rhstype); } tokens() { @@ -70,4 +74,74 @@ class BinaryOpExpression extends Expression { } } +/** + * + * @param {string} operator + * @param {ResolveInfo} ri + * @param {Token} operator_token + * @param {string} typekey + * @param {JavaType} lhstype + * @param {JavaType} rhstype + */ +function checkOperator(operator, ri, operator_token, typekey, lhstype, rhstype) { + + if (/^[*/%+-]$/.test(operator)) { + // math operators - must be numeric + if (!/^[BSIJFD]#[BSIJFD]$/.test(typekey)) { + ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for types '${lhstype.fullyDottedTypeName}' and '${rhstype.fullyDottedTypeName}'`)); + } + if (/^(D|F#[^D]|J#[^FD]|I#[^JFD])/.test(typekey)) { + return lhstype; + } + if (/^(.#D|.#F|.#J|.#I)/.test(typekey)) { + return rhstype; + } + return PrimitiveType.map.I; + } + + if (/^(<<|>>>?)$/.test(operator)) { + // shift operators - must be integral + if (!/^[BSIJ]#[BSIJ]$/.test(typekey)) { + ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for types '${lhstype.fullyDottedTypeName}' and '${rhstype.fullyDottedTypeName}'`)); + } + if (/^J/.test(typekey)) { + return PrimitiveType.map.J; + } + return PrimitiveType.map.I; + } + + if (/^[&|^]$/.test(operator)) { + // bitwise or logical operators + if (!/^[BSIJ]#[BSIJ]$|^Z#Z$/.test(typekey)) { + ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for types '${lhstype.fullyDottedTypeName}' and '${rhstype.fullyDottedTypeName}'`)); + } + if (/^[JZ]/.test(typekey)) { + return lhstype; + } + return PrimitiveType.map.I; + } + + if (/^(&&|\|\|)$/.test(operator)) { + // logical operators + if (!/^Z#Z$/.test(typekey)) { + ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for types '${lhstype.fullyDottedTypeName}' and '${rhstype.fullyDottedTypeName}'`)); + } + return PrimitiveType.map.Z; + } + + if (/^(>=?|<=?)$/.test(operator)) { + // numeric comparison operators + if (!/^[BSIJFDC]#[BSIJFDC]$/.test(typekey)) { + ri.problems.push(ParseProblem.Error(operator_token, `Operator '${operator_token.value}' is not valid for types '${lhstype.fullyDottedTypeName}' and '${rhstype.fullyDottedTypeName}'`)); + } + return PrimitiveType.map.Z; + } + + // comparison operators + if (typekey === 'Ljava/lang/String;#Ljava/lang/String;') { + ri.problems.push(ParseProblem.Warning(operator_token, `Using equality operators '=='/'!=' to compare strings has unpredictable behaviour. Consider using String.equals(...) instead.`)); + } + return PrimitiveType.map.Z; +} + exports.BinaryOpExpression = BinaryOpExpression;