improve method call resolving

This commit is contained in:
Dave Holoway
2020-06-21 18:52:41 +01:00
parent 6badc9fdb6
commit 153a6e869b

View File

@@ -3,7 +3,11 @@
* @typedef {import('../body-types').ResolveInfo} ResolveInfo
*/
const { Expression } = require("./Expression");
const { AnyType, MethodType } = require('../anys');
const { AnyType, AnyMethod, MethodType } = require('../anys');
const { ArrayType, JavaType, Method, ReifiedMethod } = require('java-mti');
const { NumberLiteral } = require('./literals/Number');
const { isTypeAssignable } = require('../expression-resolver');
const ParseProblem = require('../parsetypes/parse-problem');
class MethodCallExpression extends Expression {
/**
@@ -21,11 +25,77 @@ class MethodCallExpression extends Expression {
*/
resolveExpression(ri) {
const type = this.instance.resolveExpression(ri);
if (!(type instanceof MethodType)) {
if (type instanceof AnyType) {
return AnyType.Instance;
}
const arg_types = this.args.map(arg => arg.resolveExpression(ri));
return type.methods[0].returnType;
if (!(type instanceof MethodType)) {
ri.problems.push(ParseProblem.Error(this.instance.tokens, `Expression is not a named method'`));
return AnyType.Instance;
}
const resolved_args = this.args.map(arg => arg.resolveExpression(ri));
// all the arguments must be typed expressions or number literals
/** @type {(JavaType|NumberLiteral)[]} */
const arg_types = [];
resolved_args.forEach((a, idx) => {
if (a instanceof JavaType || a instanceof NumberLiteral) {
arg_types.push(a);
return;
}
ri.problems.push(ParseProblem.Error(this.args[idx].tokens, `Expression expected`))
// use AnyType for this argument
arg_types.push(AnyType.Instance);
});
// reify any methods with type-variables
const arg_java_types = arg_types.map(a => a instanceof NumberLiteral ? a.type : a);
const methods = type.methods.map(m => {
if (m.typeVariables.length) {
m = ReifiedMethod.build(m, arg_java_types);
}
return m;
});
// work out which methods are compatible with the call arguments
const compatible_methods = methods.filter(m => isCallCompatible(m, arg_types));
const return_types = new Set(compatible_methods.map(m => m.returnType));
if (!compatible_methods[0]) {
// if any of the arguments is AnyType, just return AnyType
if (arg_java_types.find(t => t instanceof AnyType)) {
return AnyType.Instance;
}
const methodlist = methods.map(m => m.label).join('\n- ');
const callargtypes = arg_java_types.map(t => t.fullyDottedTypeName).join(' , ');
ri.problems.push(ParseProblem.Error(this.instance.tokens,
`No compatible method found. Tried to match argument types:\n- ( ${callargtypes} ) with:\n- ${methodlist}`
));
return AnyType.Instance;
}
if (compatible_methods.length > 1) {
// if any of the arguments is AnyType, return the known return-type or AnyType
if (arg_java_types.find(t => t instanceof AnyType)) {
return return_types.size > 1 ? AnyType.Instance : compatible_methods[0].returnType;
}
// see if we have an exact match
const callsig = `(${arg_java_types.map(t => t.typeSignature).join('')})`;
const exact_match = compatible_methods.find(m => m.methodSignature.startsWith(callsig));
if (exact_match) {
compatible_methods.splice(0, compatible_methods.length, exact_match);
}
}
if (compatible_methods.length > 1) {
const methodlist = compatible_methods.map(m => m.label).join('\n- ');
const callargtypes = arg_java_types.map(t => t.fullyDottedTypeName).join(' , ');
ri.problems.push(ParseProblem.Error(this.instance.tokens,
`Ambiguous method call. Matched argument types:\n- ( ${callargtypes} ) with:\n- ${methodlist}`
));
return return_types.size > 1 ? AnyType.Instance : compatible_methods[0].returnType;
}
return compatible_methods[0].returnType;
}
tokens() {
@@ -33,4 +103,47 @@ class MethodCallExpression extends Expression {
}
}
/**
*
* @param {Method} m
* @param {(JavaType | NumberLiteral)[]} arg_types
*/
function isCallCompatible(m, arg_types) {
if (m instanceof AnyMethod) {
return true;
}
const param_count = m.parameterCount;
if (param_count !== arg_types.length) {
// for variable arity methods, we must have at least n-1 formal parameters
if (!m.isVariableArity || arg_types.length < param_count - 1) {
// wrong parameter count
return false;
}
}
const formal_params = m.parameters.slice();
const last_param = formal_params.pop();
for (let i = 0; i < arg_types.length; i++) {
const param = formal_params[i] || last_param;
let param_type = param.type;
if (param.varargs && param_type instanceof ArrayType) {
// last varargs parameter
// - if the argument count matches the parameter count, the final argument can match the array or non-array version
// e.g void v(int... x) will match with v(), v(1) and v(new int[3]);
if (arg_types.length === param_count) {
if (isTypeAssignable(param_type, arg_types[i])) {
continue;
}
}
param_type = param_type.elementType;
}
// is the argument assignable to the parameter
if (isTypeAssignable(param_type, arg_types[i])) {
continue;
}
// mismatch parameter type
return false;
}
return true;
}
exports.MethodCallExpression = MethodCallExpression;