add support for this() and super() constructor calls

This commit is contained in:
Dave Holoway
2020-06-21 21:29:03 +01:00
parent 153a6e869b
commit 43c701adc8

View File

@@ -1,13 +1,17 @@
/** /**
* @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('../tokenizer').Token} Token
*/ */
const { Expression } = require("./Expression"); const { Expression } = require("./Expression");
const { AnyType, AnyMethod, MethodType } = require('../anys'); const { AnyType, AnyMethod, MethodType } = require('../anys');
const { ArrayType, JavaType, Method, ReifiedMethod } = 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 { isTypeAssignable } = require('../expression-resolver'); const { isTypeAssignable } = require('../expression-resolver');
const ParseProblem = require('../parsetypes/parse-problem'); const ParseProblem = require('../parsetypes/parse-problem');
const { ValidateInfo } = require('../body-types');
const { SourceConstructor } = require('../source-types');
class MethodCallExpression extends Expression { class MethodCallExpression extends Expression {
/** /**
@@ -29,10 +33,40 @@ class MethodCallExpression extends Expression {
return AnyType.Instance; return AnyType.Instance;
} }
if (!(type instanceof MethodType)) { if (!(type instanceof MethodType)) {
// check if this is an aleternate or super constructor call: this() / super()
const instance = this.instance.variables[0];
if (!(instance instanceof InstanceLiteral) || !(type instanceof JavaType)) {
ri.problems.push(ParseProblem.Error(this.instance.tokens, `Expression is not a named method'`)); ri.problems.push(ParseProblem.Error(this.instance.tokens, `Expression is not a named method'`));
return AnyType.Instance; return AnyType.Instance;
} }
const resolved_args = this.args.map(arg => arg.resolveExpression(ri)); let is_ctr = false;
if (ri instanceof ValidateInfo) {
is_ctr = ri.method instanceof SourceConstructor;
}
if (is_ctr) {
resolveConstructorCall(ri, type.constructors, this.args, () => this.instance.tokens);
} else {
ri.problems.push(ParseProblem.Error(this.instance.tokens, `'this'/'super' constructor calls can only be used as the first statement of a constructor`));
}
return PrimitiveType.map.V;
}
return resolveMethodCall(ri, type.methods, this.args, () => this.instance.tokens);
}
tokens() {
return this.instance.tokens;
}
}
/**
* @param {ResolveInfo} ri
* @param {Method[]} methods
* @param {ResolvedIdent[]} args
* @param {() => Token[]} tokens
*/
function resolveMethodCall(ri, methods, args, tokens) {
const resolved_args = args.map(arg => arg.resolveExpression(ri));
// all the arguments must be typed expressions or number literals // all the arguments must be typed expressions or number literals
/** @type {(JavaType|NumberLiteral)[]} */ /** @type {(JavaType|NumberLiteral)[]} */
@@ -42,14 +76,14 @@ class MethodCallExpression extends Expression {
arg_types.push(a); arg_types.push(a);
return; return;
} }
ri.problems.push(ParseProblem.Error(this.args[idx].tokens, `Expression expected`)) ri.problems.push(ParseProblem.Error(args[idx].tokens, `Expression expected`))
// use AnyType for this argument // use AnyType for this argument
arg_types.push(AnyType.Instance); arg_types.push(AnyType.Instance);
}); });
// reify any methods with type-variables // reify any methods with type-variables
const arg_java_types = arg_types.map(a => a instanceof NumberLiteral ? a.type : a); const arg_java_types = arg_types.map(a => a instanceof NumberLiteral ? a.type : a);
const methods = type.methods.map(m => { const reified_methods = methods.map(m => {
if (m.typeVariables.length) { if (m.typeVariables.length) {
m = ReifiedMethod.build(m, arg_java_types); m = ReifiedMethod.build(m, arg_java_types);
} }
@@ -57,7 +91,7 @@ class MethodCallExpression extends Expression {
}); });
// work out which methods are compatible with the call arguments // work out which methods are compatible with the call arguments
const compatible_methods = methods.filter(m => isCallCompatible(m, arg_types)); const compatible_methods = reified_methods.filter(m => isCallCompatible(m, arg_types));
const return_types = new Set(compatible_methods.map(m => m.returnType)); const return_types = new Set(compatible_methods.map(m => m.returnType));
if (!compatible_methods[0]) { if (!compatible_methods[0]) {
@@ -65,9 +99,9 @@ class MethodCallExpression extends Expression {
if (arg_java_types.find(t => t instanceof AnyType)) { if (arg_java_types.find(t => t instanceof AnyType)) {
return AnyType.Instance; return AnyType.Instance;
} }
const methodlist = methods.map(m => m.label).join('\n- '); const methodlist = reified_methods.map(m => m.label).join('\n- ');
const callargtypes = arg_java_types.map(t => t.fullyDottedTypeName).join(' , '); const callargtypes = arg_java_types.map(t => t.fullyDottedTypeName).join(' , ');
ri.problems.push(ParseProblem.Error(this.instance.tokens, ri.problems.push(ParseProblem.Error(tokens(),
`No compatible method found. Tried to match argument types:\n- ( ${callargtypes} ) with:\n- ${methodlist}` `No compatible method found. Tried to match argument types:\n- ( ${callargtypes} ) with:\n- ${methodlist}`
)); ));
return AnyType.Instance; return AnyType.Instance;
@@ -89,23 +123,89 @@ class MethodCallExpression extends Expression {
if (compatible_methods.length > 1) { if (compatible_methods.length > 1) {
const methodlist = compatible_methods.map(m => m.label).join('\n- '); const methodlist = compatible_methods.map(m => m.label).join('\n- ');
const callargtypes = arg_java_types.map(t => t.fullyDottedTypeName).join(' , '); const callargtypes = arg_java_types.map(t => t.fullyDottedTypeName).join(' , ');
ri.problems.push(ParseProblem.Error(this.instance.tokens, ri.problems.push(ParseProblem.Error(tokens(),
`Ambiguous method call. Matched argument types:\n- ( ${callargtypes} ) with:\n- ${methodlist}` `Ambiguous method call. Matched argument types:\n- ( ${callargtypes} ) with:\n- ${methodlist}`
)); ));
return return_types.size > 1 ? AnyType.Instance : compatible_methods[0].returnType; return return_types.size > 1 ? AnyType.Instance : compatible_methods[0].returnType;
} }
return compatible_methods[0].returnType; return compatible_methods[0].returnType;
}
/**
* @param {ResolveInfo} ri
* @param {Constructor[]} constructors
* @param {ResolvedIdent[]} args
* @param {() => Token[]} tokens
*/
function resolveConstructorCall(ri, constructors, args, tokens) {
const resolved_args = 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(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 reifed_ctrs = constructors.map(c => {
if (c.typeVariables.length) {
c = ReifiedConstructor.build(c, arg_java_types);
}
return c;
});
// work out which methods are compatible with the call arguments
const compatible_ctrs = reifed_ctrs.filter(m => isCallCompatible(m, arg_types));
if (!compatible_ctrs[0]) {
// if any of the arguments is AnyType, just ignore the call
if (arg_java_types.find(t => t instanceof AnyType)) {
return;
}
const ctrlist = reifed_ctrs.map(m => m.label).join('\n- ');
const callargtypes = arg_java_types.map(t => t.fullyDottedTypeName).join(' , ');
ri.problems.push(ParseProblem.Error(tokens(),
`No compatible constructor found. Tried to match argument types:\n- ( ${callargtypes} ) with:\n- ${ctrlist}`
));
return;
} }
tokens() { if (compatible_ctrs.length > 1) {
return this.instance.tokens; // if any of the arguments is AnyType, return the known return-type or AnyType
if (arg_java_types.find(t => t instanceof AnyType)) {
return;
}
// see if we have an exact match
const callsig = `(${arg_java_types.map(t => t.typeSignature).join('')})`;
const exact_match = compatible_ctrs.find(m => m.methodSignature.startsWith(callsig));
if (exact_match) {
compatible_ctrs.splice(0, compatible_ctrs.length, exact_match);
}
}
if (compatible_ctrs.length > 1) {
const ctrlist = compatible_ctrs.map(m => m.label).join('\n- ');
const callargtypes = arg_java_types.map(t => t.fullyDottedTypeName).join(' , ');
ri.problems.push(ParseProblem.Error(tokens(),
`Ambiguous constructor call. Matched argument types:\n- ( ${callargtypes} ) with:\n- ${ctrlist}`
));
return;
} }
} }
/** /**
* *
* @param {Method} m * @param {Method|Constructor} m
* @param {(JavaType | NumberLiteral)[]} arg_types * @param {(JavaType | NumberLiteral)[]} arg_types
*/ */
function isCallCompatible(m, arg_types) { function isCallCompatible(m, arg_types) {