improve support for ternary operators in assignments and method invocations

This commit is contained in:
Dave Holoway
2020-06-23 11:26:14 +01:00
parent 3073dfcd25
commit ce345965e5
3 changed files with 74 additions and 37 deletions

View File

@@ -244,7 +244,7 @@ const valid_primitive_types = {
/** /**
* Returns true if a value of value_type is assignable to a variable of dest_type * Returns true if a value of value_type is assignable to a variable of dest_type
* @param {JavaType} 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) { function isTypeAssignable(dest_type, value_type) {
@@ -256,6 +256,14 @@ function isTypeAssignable(dest_type, value_type) {
return isLambdaAssignable(dest_type, value_type) === true; 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; let is_assignable = false;
if (dest_type.typeSignature === value_type.typeSignature) { if (dest_type.typeSignature === value_type.typeSignature) {
// exact signature match // exact signature match

View File

@@ -1,12 +1,13 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../body-types').ResolveInfo} ResolveInfo
* @typedef {import('../body-types').ResolvedValue} ResolvedValue
* @typedef {import('../tokenizer').Token} Token * @typedef {import('../tokenizer').Token} Token
*/ */
const { Expression } = require("./Expression"); const { Expression } = require("./Expression");
const { JavaType, PrimitiveType } = require('java-mti'); const { JavaType, PrimitiveType } = require('java-mti');
const ParseProblem = require('../parsetypes/parse-problem'); const ParseProblem = require('../parsetypes/parse-problem');
const { AnyType, TypeIdentType } = require('../anys'); const { AnyType, MultiValueType, TypeIdentType } = require('../anys');
const { NumberLiteral } = require('./literals/Number'); const { NumberLiteral } = require('./literals/Number');
const { checkTypeAssignable } = require('../expression-resolver'); 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 (operator === 'instanceof') {
if (!(rhsvalue instanceof TypeIdentType)) { if (!(rhsvalue instanceof TypeIdentType)) {
ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Type expected`)); ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Type expected`));
} }
if (!lhstype) { if (!(lhsvalue instanceof JavaType || lhsvalue instanceof NumberLiteral)) {
ri.problems.push(ParseProblem.Error(this.rhs.tokens, `Expression expected`));
}
return PrimitiveType.map.Z;
}
if (!lhstype || !rhstype) {
if (!lhstype) {
ri.problems.push(ParseProblem.Error(this.lhs.tokens, `Expression expected`)); ri.problems.push(ParseProblem.Error(this.lhs.tokens, `Expression expected`));
} }
if (!rhstype) { return PrimitiveType.map.Z;
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');
} }
if (/^([*/%&|^+-]?=|<<=|>>>?=)$/.test(operator)) { if (/^([*/%&|^+-]?=|<<=|>>>?=)$/.test(operator)) {
let src_type = rhsvalue; let src_type = rhsvalue;
if (operator.length > 1) { 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); if (lhsvalue instanceof JavaType) {
// result of assignments are lhs checkTypeAssignable(lhsvalue, src_type, () => this.rhs.tokens, ri.problems);
return lhstype; // 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() { tokens() {
@@ -100,11 +87,51 @@ class BinaryOpExpression extends Expression {
* @param {string} operator * @param {string} operator
* @param {ResolveInfo} ri * @param {ResolveInfo} ri
* @param {Token} operator_token * @param {Token} operator_token
* @param {string} typekey * @param {ResolvedValue} lhstype
* @param {JavaType} lhstype * @param {ResolvedValue} rhstype
* @param {JavaType} 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)) { if (operator === '+' && /(^|#)Ljava\/lang\/String;/.test(typekey)) {
// string appending is compatible with all types // string appending is compatible with all types

View File

@@ -4,7 +4,7 @@
* @typedef {import('../tokenizer').Token} Token * @typedef {import('../tokenizer').Token} Token
*/ */
const { Expression } = require("./Expression"); 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 { ArrayType, JavaType, Method,PrimitiveType, ReifiedConstructor, ReifiedMethod, Constructor } = require('java-mti');
const { NumberLiteral } = require('./literals/Number'); const { NumberLiteral } = require('./literals/Number');
const { InstanceLiteral } = require('./literals/Instance') const { InstanceLiteral } = require('./literals/Instance')
@@ -69,10 +69,10 @@ function resolveMethodCall(ri, methods, args, tokens) {
const resolved_args = args.map(arg => arg.resolveExpression(ri)); const resolved_args = args.map(arg => arg.resolveExpression(ri));
// all the arguments must be typed expressions, number literals or lambdas // all the arguments must be typed expressions, number literals or lambdas
/** @type {(JavaType|NumberLiteral|LambdaType)[]} */ /** @type {(JavaType|NumberLiteral|LambdaType|MultiValueType)[]} */
const arg_types = []; const arg_types = [];
resolved_args.forEach((a, idx) => { 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); arg_types.push(a);
return; return;
} }
@@ -83,9 +83,11 @@ function resolveMethodCall(ri, methods, args, tokens) {
// reify any methods with type-variables // reify any methods with type-variables
// - lambda expressions can't be used as type arguments so just pass them as void // - 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 => const arg_java_types = arg_types.map(a =>
a instanceof NumberLiteral ? a.type a instanceof NumberLiteral ? a.type
: a instanceof LambdaType ? PrimitiveType.map.V : a instanceof LambdaType ? PrimitiveType.map.V
: a instanceof MultiValueType ? ri.typemap.get('java/lang/Object')
: a); : a);
const reified_methods = methods.map(m => { const reified_methods = methods.map(m => {
if (m.typeVariables.length) { if (m.typeVariables.length) {
@@ -214,7 +216,7 @@ function resolveConstructorCall(ri, constructors, args, tokens) {
/** /**
* *
* @param {Method|Constructor} m * @param {Method|Constructor} m
* @param {(JavaType | NumberLiteral | LambdaType)[]} arg_types * @param {(JavaType | NumberLiteral | LambdaType | MultiValueType)[]} arg_types
*/ */
function isCallCompatible(m, arg_types) { function isCallCompatible(m, arg_types) {
if (m instanceof AnyMethod) { if (m instanceof AnyMethod) {