From ce345965e581a89174fb0995c9c99aff836d3d50 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Tue, 23 Jun 2020 11:26:14 +0100 Subject: [PATCH] improve support for ternary operators in assignments and method invocations --- langserver/java/expression-resolver.js | 10 +- .../expressiontypes/BinaryOpExpression.js | 91 ++++++++++++------- .../expressiontypes/MethodCallExpression.js | 10 +- 3 files changed, 74 insertions(+), 37 deletions(-) diff --git a/langserver/java/expression-resolver.js b/langserver/java/expression-resolver.js index e857fba..349a135 100644 --- a/langserver/java/expression-resolver.js +++ b/langserver/java/expression-resolver.js @@ -244,7 +244,7 @@ const valid_primitive_types = { /** * Returns true if a value of value_type is assignable to a variable of dest_type * @param {JavaType} dest_type - * @param {JavaType|NumberLiteral|LambdaType} value_type + * @param {JavaType|NumberLiteral|LambdaType|MultiValueType} value_type */ function isTypeAssignable(dest_type, value_type) { @@ -256,6 +256,14 @@ function isTypeAssignable(dest_type, value_type) { return isLambdaAssignable(dest_type, value_type) === true; } + if (value_type instanceof MultiValueType) { + return value_type.types.every(t => { + if (t instanceof JavaType || t instanceof NumberLiteral || t instanceof LambdaType || t instanceof MultiValueType) + return isTypeAssignable(dest_type, t); + return false; + }); + } + let is_assignable = false; if (dest_type.typeSignature === value_type.typeSignature) { // exact signature match diff --git a/langserver/java/expressiontypes/BinaryOpExpression.js b/langserver/java/expressiontypes/BinaryOpExpression.js index 9ae7e36..6c97861 100644 --- a/langserver/java/expressiontypes/BinaryOpExpression.js +++ b/langserver/java/expressiontypes/BinaryOpExpression.js @@ -1,12 +1,13 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolveInfo} ResolveInfo + * @typedef {import('../body-types').ResolvedValue} ResolvedValue * @typedef {import('../tokenizer').Token} Token */ const { Expression } = require("./Expression"); const { JavaType, PrimitiveType } = require('java-mti'); const ParseProblem = require('../parsetypes/parse-problem'); -const { AnyType, TypeIdentType } = require('../anys'); +const { AnyType, MultiValueType, TypeIdentType } = require('../anys'); const { NumberLiteral } = require('./literals/Number'); const { checkTypeAssignable } = require('../expression-resolver'); @@ -47,47 +48,33 @@ class BinaryOpExpression extends Expression { } } - const lhstype = lhsvalue instanceof JavaType ? lhsvalue : lhsvalue instanceof NumberLiteral ? lhsvalue.type : null; - const rhstype = rhsvalue instanceof JavaType ? rhsvalue : rhsvalue instanceof NumberLiteral ? rhsvalue.type : null; - if (operator === 'instanceof') { if (!(rhsvalue instanceof TypeIdentType)) { ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Type expected`)); } - if (!lhstype) { - ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Expression expected`)); - } - return PrimitiveType.map.Z; - } - - if (!lhstype || !rhstype) { - if (!lhstype) { + if (!(lhsvalue instanceof JavaType || lhsvalue instanceof NumberLiteral)) { ri.problems.push(ParseProblem.Error(this.lhs.tokens, `Expression expected`)); } - if (!rhstype) { - ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Expression expected`)); - } - return AnyType.Instance; - } - - const typekey = `${lhstype.typeSignature}#${rhstype.typeSignature}`; - - if (operator === '+' && /(^|#)Ljava\/lang\/String;/.test(typekey)) { - // string appending is compatible with all types - return ri.typemap.get('java/lang/String'); + return PrimitiveType.map.Z; } if (/^([*/%&|^+-]?=|<<=|>>>?=)$/.test(operator)) { let src_type = rhsvalue; if (operator.length > 1) { - src_type = checkOperator(operator.slice(0,-1), ri, this.op, typekey, lhstype, rhstype); + const result_types = checkOperator(operator.slice(0,-1), ri, this.op, lhsvalue, rhsvalue); + src_type = Array.isArray(result_types) ? new MultiValueType(...result_types) : result_types; } - checkTypeAssignable(lhstype, src_type, () => this.rhs.tokens, ri.problems); - // result of assignments are lhs - return lhstype; + if (lhsvalue instanceof JavaType) { + checkTypeAssignable(lhsvalue, src_type, () => this.rhs.tokens, ri.problems); + // result of assignments are lhs type + return lhsvalue; + } + ri.problems.push(ParseProblem.Error(this.op, `Invalid assignment`)); + return AnyType.Instance; } - return checkOperator(operator, ri, this.op, typekey, lhstype, rhstype); + const result_types = checkOperator(operator, ri, this.op, lhsvalue, rhsvalue); + return Array.isArray(result_types) ? new MultiValueType(...result_types) : result_types; } tokens() { @@ -100,11 +87,51 @@ class BinaryOpExpression extends Expression { * @param {string} operator * @param {ResolveInfo} ri * @param {Token} operator_token - * @param {string} typekey - * @param {JavaType} lhstype - * @param {JavaType} rhstype + * @param {ResolvedValue} lhstype + * @param {ResolvedValue} rhstype + * @returns {JavaType|JavaType[]} */ -function checkOperator(operator, ri, operator_token, typekey, lhstype, rhstype) { +function checkOperator(operator, ri, operator_token, lhstype, rhstype) { + + if (lhstype instanceof MultiValueType) { + /** @type {JavaType[]} */ + let types = []; + lhstype.types.reduce((arr, type) => { + const types = checkOperator(operator, ri, operator_token, type, rhstype); + Array.isArray(types) ? arr.splice(arr.length, 0, ...types) : arr.push(types); + return arr; + }, types); + types = [...new Set(types)]; + return types.length === 1 ? types[0] : types; + } + + if (rhstype instanceof MultiValueType) { + /** @type {JavaType[]} */ + let types = []; + rhstype.types.reduce((arr, type) => { + const types = checkOperator(operator, ri, operator_token, lhstype, type); + Array.isArray(types) ? arr.splice(arr.length, 0, ...types) : arr.push(types); + return arr; + }, types); + types = [...new Set(types)]; + return types.length === 1 ? types[0] : types; + } + + if (lhstype instanceof NumberLiteral) { + lhstype = lhstype.type; + } + if (rhstype instanceof NumberLiteral) { + rhstype = rhstype.type; + } + + if (!(lhstype instanceof JavaType)) { + return AnyType.Instance; + } + if (!(rhstype instanceof JavaType)) { + return AnyType.Instance; + } + + const typekey = `${lhstype.typeSignature}#${rhstype.typeSignature}`; if (operator === '+' && /(^|#)Ljava\/lang\/String;/.test(typekey)) { // string appending is compatible with all types diff --git a/langserver/java/expressiontypes/MethodCallExpression.js b/langserver/java/expressiontypes/MethodCallExpression.js index d1b214f..1af10c9 100644 --- a/langserver/java/expressiontypes/MethodCallExpression.js +++ b/langserver/java/expressiontypes/MethodCallExpression.js @@ -4,7 +4,7 @@ * @typedef {import('../tokenizer').Token} Token */ const { Expression } = require("./Expression"); -const { AnyType, AnyMethod, LambdaType, MethodType } = require('../anys'); +const { AnyType, AnyMethod, LambdaType, MethodType, MultiValueType } = require('../anys'); const { ArrayType, JavaType, Method,PrimitiveType, ReifiedConstructor, ReifiedMethod, Constructor } = require('java-mti'); const { NumberLiteral } = require('./literals/Number'); const { InstanceLiteral } = require('./literals/Instance') @@ -69,10 +69,10 @@ function resolveMethodCall(ri, methods, args, tokens) { const resolved_args = args.map(arg => arg.resolveExpression(ri)); // all the arguments must be typed expressions, number literals or lambdas - /** @type {(JavaType|NumberLiteral|LambdaType)[]} */ + /** @type {(JavaType|NumberLiteral|LambdaType|MultiValueType)[]} */ const arg_types = []; resolved_args.forEach((a, idx) => { - if (a instanceof JavaType || a instanceof NumberLiteral || a instanceof LambdaType) { + if (a instanceof JavaType || a instanceof NumberLiteral || a instanceof LambdaType || a instanceof MultiValueType) { arg_types.push(a); return; } @@ -83,9 +83,11 @@ function resolveMethodCall(ri, methods, args, tokens) { // reify any methods with type-variables // - lambda expressions can't be used as type arguments so just pass them as void + // - multi-value types will dynamically chhose the type, but it's always a reference type (so assignable to Object) const arg_java_types = arg_types.map(a => a instanceof NumberLiteral ? a.type : a instanceof LambdaType ? PrimitiveType.map.V + : a instanceof MultiValueType ? ri.typemap.get('java/lang/Object') : a); const reified_methods = methods.map(m => { if (m.typeVariables.length) { @@ -214,7 +216,7 @@ function resolveConstructorCall(ri, constructors, args, tokens) { /** * * @param {Method|Constructor} m - * @param {(JavaType | NumberLiteral | LambdaType)[]} arg_types + * @param {(JavaType | NumberLiteral | LambdaType | MultiValueType)[]} arg_types */ function isCallCompatible(m, arg_types) { if (m instanceof AnyMethod) {