add support for displaying method signatures

This commit is contained in:
Dave Holoway
2020-06-26 17:02:24 +01:00
parent f9a2665364
commit 563142661a
4 changed files with 187 additions and 40 deletions

View File

@@ -853,7 +853,7 @@ function enumValueList(type, tokens, imports, typemap) {
let ctr_args = [];
if (tokens.isValue('(')) {
if (!tokens.isValue(')')) {
ctr_args = expressionList(tokens, new MethodDeclarations(), type, imports, typemap);
({ expressions: ctr_args } = expressionList(tokens, new MethodDeclarations(), type, imports, typemap));
tokens.expectValue(')');
}
}
@@ -1073,7 +1073,7 @@ function forStatement(s, tokens, mdecls, method, imports, typemap) {
}
// for-updated
if (!tokens.isValue(')')) {
s.update = expressionList(tokens, mdecls, method, imports, typemap);
({ expressions: s.update } = expressionList(tokens, mdecls, method, imports, typemap));
tokens.expectValue(')');
}
s.statement = statement(tokens, mdecls, method, imports, typemap);
@@ -1574,7 +1574,7 @@ function rootTerm(tokens, mdecls, scope, imports, typemap) {
let elements = [], open = tokens.current;
tokens.expectValue('{');
if (!tokens.isValue('}')) {
elements = expressionList(tokens, mdecls, scope, imports, typemap, { isArrayLiteral:true });
({ expressions: elements } = expressionList(tokens, mdecls, scope, imports, typemap, { isArrayLiteral:true }));
tokens.expectValue('}');
}
const ident = `{${elements.map(e => e.source).join(',')}}`;
@@ -1614,7 +1614,7 @@ function newTerm(tokens, mdecls, scope, imports, typemap) {
case '(':
tokens.inc();
if (!tokens.isValue(')')) {
ctr_args = expressionList(tokens, mdecls, scope, imports, typemap);
({ expressions: ctr_args } = expressionList(tokens, mdecls, scope, imports, typemap));
tokens.expectValue(')');
}
newtokens = tokens.markEnd();
@@ -1643,9 +1643,12 @@ function newTerm(tokens, mdecls, scope, imports, typemap) {
function expressionList(tokens, mdecls, scope, imports, typemap, opts) {
let e = expression(tokens, mdecls, scope, imports, typemap);
const expressions = [e];
while (tokens.isValue(',')) {
const commas = [];
while (tokens.current.value === ',') {
commas.push(tokens.consume());
if (opts && opts.isArrayLiteral) {
// array literals are allowed a single trailing comma
// @ts-ignore
if (tokens.current.value === '}') {
break;
}
@@ -1653,7 +1656,7 @@ function expressionList(tokens, mdecls, scope, imports, typemap, opts) {
e = expression(tokens, mdecls, scope, imports, typemap);
expressions.push(e);
}
return expressions;
return { expressions, commas };
}
/**
@@ -1771,14 +1774,14 @@ function arrayQualifiers(matches, tokens, mdecls, scope, imports, typemap) {
* @param {Map<string,CEIType>} typemap
*/
function methodCallQualifier(matches, tokens, mdecls, scope, imports, typemap) {
let args = [];
let args = [], commas = [];
tokens.mark();
tokens.expectValue('(');
const open_bracket = tokens.consume();
if (!tokens.isValue(')')) {
args = expressionList(tokens, mdecls, scope, imports, typemap);
({ expressions: args, commas } = expressionList(tokens, mdecls, scope, imports, typemap));
tokens.expectValue(')');
}
return new ResolvedIdent(`${matches.source}(${args.map(a => a.source).join(', ')})`, [new MethodCallExpression(matches, args)], [], [], '', [...matches.tokens, ...tokens.markEnd()]);
return new ResolvedIdent(`${matches.source}(${args.map(a => a.source).join(', ')})`, [new MethodCallExpression(matches, open_bracket, args, commas)], [], [], '', [...matches.tokens, ...tokens.markEnd()]);
}
/**

View File

@@ -16,12 +16,16 @@ const { SourceConstructor } = require('../source-types');
class MethodCallExpression extends Expression {
/**
* @param {ResolvedIdent} instance
* @param {Token} open_bracket
* @param {ResolvedIdent[]} args
* @param {Token[]} commas
*/
constructor(instance, args) {
constructor(instance, open_bracket, args, commas) {
super();
this.instance = instance;
this.open_bracket = open_bracket;
this.args = args;
this.commas = commas;
}
/**
@@ -44,14 +48,14 @@ class MethodCallExpression extends Expression {
is_ctr = ri.method instanceof SourceConstructor;
}
if (is_ctr) {
resolveConstructorCall(ri, type.constructors, this.args, () => this.instance.tokens);
resolveConstructorCall(ri, type.constructors, this.open_bracket, this.args, this.commas, () => 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);
return resolveMethodCall(ri, type.methods, this.open_bracket, this.args, this.commas, () => this.instance.tokens);
}
tokens() {
@@ -62,11 +66,13 @@ class MethodCallExpression extends Expression {
/**
* @param {ResolveInfo} ri
* @param {Method[]} methods
* @param {Token} open_bracket
* @param {ResolvedIdent[]} args
* @param {Token[]} commas
* @param {() => Token[]} tokens
*/
function resolveMethodCall(ri, methods, args, tokens) {
const resolved_args = args.map(arg => arg.resolveExpression(ri));
function resolveMethodCall(ri, methods, open_bracket, args, commas, tokens) {
const resolved_args = args.map((arg,idx) => arg.resolveExpression(ri));
// all the arguments must be typed expressions, number literals or lambdas
/** @type {(JavaType|NumberLiteral|LambdaType|MultiValueType)[]} */
@@ -100,6 +106,32 @@ function resolveMethodCall(ri, methods, args, tokens) {
const compatible_methods = reified_methods.filter(m => isCallCompatible(m, arg_types));
const return_types = new Set(compatible_methods.map(m => m.returnType));
// store the methods and argument position for signature help
const methodIdx = Math.max(reified_methods.indexOf(compatible_methods[0]), 0);
open_bracket.methodCallInfo = {
methods: reified_methods,
methodIdx,
argIdx: 0,
}
args.forEach((arg, idx) => {
const methodCallInfo = {
methods: reified_methods,
methodIdx,
argIdx: idx,
}
// add the info to the previous comma
const c = commas[idx-1];
if (c) {
c.methodCallInfo = methodCallInfo;
}
// set the info on all the tokens used in the argument
arg.tokens.forEach(tok => {
if (tok.methodCallInfo === null) {
tok.methodCallInfo = methodCallInfo;
}
})
})
if (!compatible_methods[0]) {
// if any of the arguments is AnyType, just return AnyType
if (arg_java_types.find(t => t instanceof AnyType)) {
@@ -142,10 +174,12 @@ function resolveMethodCall(ri, methods, args, tokens) {
/**
* @param {ResolveInfo} ri
* @param {Constructor[]} constructors
* @param {Token} open_bracket
* @param {ResolvedIdent[]} args
* @param {Token[]} commas
* @param {() => Token[]} tokens
*/
function resolveConstructorCall(ri, constructors, args, tokens) {
function resolveConstructorCall(ri, constructors, open_bracket, args, commas, tokens) {
const resolved_args = args.map(arg => arg.resolveExpression(ri));
// all the arguments must be typed expressions, number literals or lambdas
@@ -177,6 +211,32 @@ function resolveConstructorCall(ri, constructors, args, tokens) {
// work out which methods are compatible with the call arguments
const compatible_ctrs = reifed_ctrs.filter(m => isCallCompatible(m, arg_types));
// store the methods and argument position for signature help
const methodIdx = reifed_ctrs.indexOf(compatible_ctrs[0]);
open_bracket.methodCallInfo = {
methods: reifed_ctrs,
methodIdx,
argIdx: 0,
}
args.forEach((arg, idx) => {
const methodCallInfo = {
methods: reifed_ctrs,
methodIdx,
argIdx: idx,
}
// add the info to the previous comma
const c = commas[idx-1];
if (c) {
c.methodCallInfo = methodCallInfo;
}
// set the info on all the tokens used in the argument
arg.tokens.forEach(tok => {
if (tok.methodCallInfo === null) {
tok.methodCallInfo = methodCallInfo;
}
})
})
if (!compatible_ctrs[0]) {
// if any of the arguments is AnyType, just ignore the call
if (arg_java_types.find(t => t instanceof AnyType)) {

View File

@@ -1,3 +1,7 @@
/**
* @typedef {import('java-mti').Method} Method
* @typedef {import('java-mti').Constructor} Constructor
*/
const { TextBlock, BlockRange } = require('./parsetypes/textblock');
/**
@@ -51,6 +55,13 @@ class Token extends TextBlock {
this.kind = kind;
/** @type {{key:string}} */
this.loc = null;
/**
* Stores information about the resolved methods/constructors this token is an argument for.
* This is used to provide method signature info to vscode
* @type {{methods:(Method|Constructor)[], methodIdx:number, argIdx:number}}
*/
this.methodCallInfo = null;
}
get value() {