mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-22 17:39:19 +00:00
improve method call resolving
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user