From 41adfbe53e7db321737b0c5bfdb41f1d49c149e2 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Tue, 9 Jun 2020 15:01:58 +0100 Subject: [PATCH] initial attempt to support type variable arguments in methods --- langserver/java/body-parser3.js | 16 ++-- langserver/java/source-type.js | 88 ++++++++++--------- langserver/java/type-resolver.js | 27 ++++-- langserver/java/typeident.js | 32 +++---- langserver/java/validater.js | 39 +++++--- .../java/validation/unresolved-types.js | 2 +- 6 files changed, 121 insertions(+), 83 deletions(-) diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index 2edd3c2..e85bd52 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -1183,8 +1183,8 @@ function isTypeAssignable(dest_type, value_type) { if (!is_assignable) { // generic types are also assignable to compatible wildcard type bounds const raw_type = valid_raw_types.find(rt => rt.rawTypeSignature === dest_type.rawTypeSignature); - if (raw_type instanceof CEIType && raw_type.typevars.length === value_type.typevars.length) { - is_assignable = dest_type.typevars.every((dest_tv, idx) => isTypeArgumentCompatible(dest_tv, value_type.typevars[idx].type)); + if (raw_type instanceof CEIType && raw_type.typeVariables.length === value_type.typeVariables.length) { + is_assignable = dest_type.typeVariables.every((dest_tv, idx) => isTypeArgumentCompatible(dest_tv, value_type.typeVariables[idx].type)); } } } @@ -1260,7 +1260,7 @@ function isTypeComparable(lhs_type, rhs_type) { is_comparable = lhs_types.includes(rhs_type) || rhs_types.includes(lhs_type); if (!is_comparable) { if (lhs_type.rawTypeSignature === rhs_type.rawTypeSignature) { - is_comparable = lhs_type.typevars.every((tv, idx) => isTypeArgumentComparable(tv, rhs_type.typevars[idx])); + is_comparable = lhs_type.typeVariables.every((tv, idx) => isTypeArgumentComparable(tv, rhs_type.typeVariables[idx])); } } } @@ -1596,9 +1596,9 @@ function rootTerm(tokens, locals, method, imports, typemap) { case 'object-literal': // this, super or null if (tokens.current.value === 'this') { - matches = new ResolvedIdent(tokens.current.value, [new Value(tokens.current.value, method._owner)]); + matches = new ResolvedIdent(tokens.current.value, [new Value(tokens.current.value, method.owner)]); } else if (tokens.current.value === 'super') { - const supertype = method._owner.supers.find(s => s.typeKind === 'class') || typemap.get('java/lang/Object'); + const supertype = method.owner.supers.find(s => s.typeKind === 'class') || typemap.get('java/lang/Object'); matches = new ResolvedIdent(tokens.current.value, [new Value(tokens.current.value, supertype)]); } else { matches = new ResolvedIdent(tokens.current.value, [new LiteralValue(tokens.current.value, new NullType())]); @@ -1917,7 +1917,7 @@ function qualifiers(matches, tokens, locals, method, imports, typemap) { return matches; } tokens.inc(); - genericTypeArgs(tokens, matches.types, method._owner, imports, typemap); + genericTypeArgs(tokens, matches.types, method, imports, typemap); break; default: return matches; @@ -2081,7 +2081,7 @@ function findIdentifier(ident, locals, method, imports, typemap) { matches.variables = [local || param]; } else { // is it a field or method in the current type (or any of the superclasses) - const types = getTypeInheritanceList(method._owner); + const types = getTypeInheritanceList(method.owner); const method_sigs = new Set(); types.forEach(type => { if (!matches.variables[0]) { @@ -2102,7 +2102,7 @@ function findIdentifier(ident, locals, method, imports, typemap) { }); } - const { types, package_name } = resolveTypeOrPackage(ident, method._owner, imports, typemap); + const { types, package_name } = resolveTypeOrPackage(ident, method, imports, typemap); matches.types = types; matches.package_name = package_name; diff --git a/langserver/java/source-type.js b/langserver/java/source-type.js index 0e71d76..d6f515e 100644 --- a/langserver/java/source-type.js +++ b/langserver/java/source-type.js @@ -1,4 +1,4 @@ -const { JavaType, CEIType, PrimitiveType, Constructor, Method, MethodBase, Field, Parameter, TypeVariable, UnresolvedType } = require('java-mti'); +const { JavaType, CEIType, PrimitiveType, Constructor, Method, MethodBase, Field, Parameter, TypeVariable, UnresolvedType, signatureToType } = require('java-mti'); const { ModuleBlock, TypeDeclBlock, FieldBlock, ConstructorBlock, MethodBlock, InitialiserBlock, ParameterBlock, TextBlock } = require('./parser9'); /** @@ -38,29 +38,45 @@ function extractTypeList(decl) { class SourceType extends CEIType { /** * @param {ModuleBlock} mod - * @param {TypeDeclBlock} type + * @param {TypeDeclBlock} decl * @param {string} qualified_type_name qualified $-separated type name * @param {Map} typemap */ - constructor(mod, type, qualified_type_name, typemap) { - super(type.shortSignature, type.kind(), mapmods(type), type.docs); - this._decl = type; + constructor(mod, decl, qualified_type_name, typemap) { + super(decl.shortSignature, decl.kind(), mapmods(decl), decl.docs); + this._typemap = typemap; + this._decl = decl; this._dottedTypeName = qualified_type_name.replace(/\$/g, '.'); - this.extends_types = type.extends_decl ? extractTypeList(type.extends_decl) : []; - this.implements_types = type.implements_decl ? extractTypeList(type.implements_decl) : []; + this.extends_types = decl.extends_decl ? extractTypeList(decl.extends_decl) : []; + this.implements_types = decl.implements_decl ? extractTypeList(decl.implements_decl) : []; this.implicit_extend = !this.extends_types.length && !this.implements_types.length ? [typemap.get('java/lang/Object')] : []; - this.fields = type.fields.map(f => new SourceField(this, f)); - this.methods = type.methods.map(m => new SourceMethod(this, m)); - /** @type {Constructor[]} */ - this.constructors = type.constructors.map(c => new SourceConstructor(this, c)); - if (!type.constructors[0] && type.kind() === 'class') { + this.fields = decl.fields.map(f => new SourceField(this, f)); + this.methods = decl.methods.map(m => new SourceMethod(this, m)); + + /** + * constructors coded in the source + */ + this.declaredConstructors = decl.constructors.map(c => new SourceConstructor(this, c)); + + /** + * Callable constructors for the type - if the type does not explicitly declare + * any constructors, an implicit default constructor is included + * @type {Constructor[]} + * */ + this.constructors = this.declaredConstructors; + if (!decl.constructors[0] && decl.kind() === 'class') { // add a default public constructor if this is a class with no explicit constructors - this.constructors.push(new DefaultConstructor(this)); + this.constructors = [new DefaultConstructor(this)]; } - this.initers = type.initialisers.map(i => new SourceInitialiser(this, i)); - super.typevars = type.typevars.map(tv => { + + /** + * The class initialisers + */ + this.initers = decl.initialisers.map(i => new SourceInitialiser(this, i)); + + super.typeVariables = decl.typevars.map(tv => { const typevar = new TypeVariable(this, tv.name); // automatically add the Object bound typevar.bounds.push(new TypeVariable.Bound(this, 'Ljava/lang/Object;', false)); @@ -88,23 +104,13 @@ class SourceType extends CEIType { ]; } - getAllResolvableTypes() { - /** @type {ResolvableType[]} */ - const res = [ - ...this.extends_types, - ...this.implements_types, - ]; - this.fields.forEach(f => res.push(f._type)); - this.methods.forEach(m => { - res.push(m._returnType); - m.parameters.forEach(p => res.push(p._paramType)); - }); - this.constructors.forEach(c => { - if (c instanceof SourceConstructor) { - c.parameters.forEach(p => res.push(p._paramType)); - } - }); - return res; + /** + * @param {string} signature + * @param {TypeVariable[]} [typevars] + * @returns {JavaType} + */ + resolveType(signature, typevars = []) { + return signatureToType(signature, this._typemap, [...typevars, ...this.typeVariables]); } } @@ -115,7 +121,6 @@ class SourceField extends Field { */ constructor(owner, decl) { super(mapmods(decl), decl.docs); - this._owner = owner; this._decl = decl; this._type = new ResolvableType(decl); } @@ -135,7 +140,7 @@ class SourceConstructor extends Constructor { * @param {ConstructorBlock} decl */ constructor(owner, decl) { - super(mapmods(decl), decl.docs); + super(owner, mapmods(decl), decl.docs); this._owner = owner; this._decl = decl; this._parameters = decl.parameters.map((p,i) => new SourceParameter(p)); @@ -165,8 +170,8 @@ class DefaultConstructor extends Constructor { * @param {SourceType} owner */ constructor(owner) { - super(['public']); - this._owner = owner; + super(owner, ['public'], ''); + this.owner = owner; } get methodSignature() { @@ -177,7 +182,7 @@ class DefaultConstructor extends Constructor { * @returns {SourceType} */ get returnType() { - return this._owner; + return this.owner; } } @@ -188,8 +193,8 @@ class SourceInitialiser extends MethodBase { * @param {InitialiserBlock} decl */ constructor(owner, decl) { - super(mapmods(decl), decl.docs); - this._owner = owner; + super(owner, mapmods(decl), decl.docs); + this.owner = owner; this._decl = decl; } @@ -204,8 +209,8 @@ class SourceMethod extends Method { * @param {MethodBlock} decl */ constructor(owner, decl) { - super(decl.name, mapmods(decl), decl.docs); - this._owner = owner; + super(owner, decl.name, mapmods(decl), decl.docs); + this.owner = owner; this._decl = decl; this._parameters = decl.parameters.map((p,i) => new SourceParameter(p)); this._returnType = new ResolvableType(decl); @@ -280,3 +285,4 @@ exports.SourceParameter = SourceParameter; exports.SourceConstructor = SourceConstructor; exports.DefaultConstructor = DefaultConstructor; exports.SourceInitialiser = SourceInitialiser; +exports.ResolvableType = ResolvableType; diff --git a/langserver/java/type-resolver.js b/langserver/java/type-resolver.js index d6ca474..f50917d 100644 --- a/langserver/java/type-resolver.js +++ b/langserver/java/type-resolver.js @@ -1,7 +1,7 @@ /** * @typedef {Map} TypeMap */ -const { JavaType, PrimitiveType, ArrayType, CEIType } = require('java-mti'); +const { JavaType, PrimitiveType, ArrayType, CEIType, MethodBase } = require('java-mti'); const { ResolvedImport } = require('./import-resolver'); const ResolvedType = require('./parsetypes/resolved-type'); @@ -260,16 +260,25 @@ function resolveTypeIdents(types, fully_qualified_scope, resolved_imports, typem /** * * @param {string} ident - * @param {CEIType} scoped_type + * @param {CEIType|MethodBase} scope * @param {ResolvedImport[]} imports * @param {Map} typemap */ -function resolveTypeOrPackage(ident, scoped_type, imports, typemap) { +function resolveTypeOrPackage(ident, scope, imports, typemap) { const types = []; let package_name = ''; - // is it an enclosed type of the currently scoped type or any outer type - if (scoped_type) { + if (scope instanceof MethodBase) { + // is it a type variable in the current scope + const tv = scope.typeVariables.find(tv => tv.name === ident); + if (tv) { + types.push(tv.type); + } + } + + if (scope) { + // is it an enclosed type of the currently scoped type or any outer type + const scoped_type = scope instanceof CEIType ? scope : scope.owner; const scopes = scoped_type.shortSignature.split('$'); while (scopes.length) { const enc_type = typemap.get(`${scopes.join('$')}$${ident}`); @@ -281,6 +290,14 @@ function resolveTypeOrPackage(ident, scoped_type, imports, typemap) { } } + if (scope instanceof CEIType) { + // is it a type variable of the currently scoped type + const tv = scope.typeVariables.find(tv => tv.name === ident); + if (tv) { + types.push(tv.type); + } + } + if (!types[0]) { // is it a top-level type from the imports const top_level_type = '/' + ident; diff --git a/langserver/java/typeident.js b/langserver/java/typeident.js index a33e465..f268db2 100644 --- a/langserver/java/typeident.js +++ b/langserver/java/typeident.js @@ -1,4 +1,4 @@ -const { ArrayType, CEIType, JavaType, PrimitiveType, WildcardType } = require('java-mti'); +const { ArrayType, CEIType, JavaType, PrimitiveType, MethodBase, WildcardType } = require('java-mti'); const { SourceMethod, SourceConstructor, SourceInitialiser } = require('./source-type'); const ResolvedImport = require('./parsetypes/resolved-import'); const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resolver'); @@ -12,16 +12,16 @@ const { AnyType } = require("./body-types"); /** * @param {TokenList} tokens - * @param {CEIType} scoped_type + * @param {CEIType|MethodBase} scope * @param {ResolvedImport[]} imports * @param {Map} typemap */ -function typeIdentList(tokens, scoped_type, imports, typemap) { - let type = typeIdent(tokens, scoped_type, imports, typemap); +function typeIdentList(tokens, scope, imports, typemap) { + let type = typeIdent(tokens, scope, imports, typemap); const types = [type]; while (tokens.current.value === ',') { tokens.inc(); - type = typeIdent(tokens, scoped_type, imports, typemap); + type = typeIdent(tokens, scope, imports, typemap); types.push(type); } return types; @@ -29,22 +29,22 @@ function typeIdentList(tokens, scoped_type, imports, typemap) { /** * @param {TokenList} tokens - * @param {CEIType} scoped_type + * @param {CEIType|MethodBase} scope * @param {ResolvedImport[]} imports * @param {Map} typemap */ -function typeIdent(tokens, scoped_type, imports, typemap) { +function typeIdent(tokens, scope, imports, typemap) { let types = [], package_name = ''; switch(tokens.current.kind) { case 'ident': - ({ types, package_name } = resolveTypeOrPackage(tokens.current.value, scoped_type, imports, typemap)); + ({ types, package_name } = resolveTypeOrPackage(tokens.current.value, scope, imports, typemap)); break; case 'primitive-type': types.push(PrimitiveType.fromName(tokens.current.value)); break; default: return tokens.current.value === '?' - ? wildcardTypeArgument(tokens, scoped_type, imports, typemap) + ? wildcardTypeArgument(tokens, scope, imports, typemap) : AnyType.Instance; } tokens.inc(); @@ -56,7 +56,7 @@ function typeIdent(tokens, scoped_type, imports, typemap) { ({ types, package_name } = resolveNextTypeOrPackage(tokens.current.value, types, package_name, typemap)); tokens.inc(); } else if (tokens.isValue('<')) { - genericTypeArgs(tokens, types, scoped_type, imports, typemap); + genericTypeArgs(tokens, types, scope, imports, typemap); } else if (tokens.isValue('[')) { let arrdims = 0; for(;;) { @@ -82,13 +82,13 @@ function typeIdent(tokens, scoped_type, imports, typemap) { * * @param {TokenList} tokens * @param {JavaType[]} types - * @param {CEIType} scoped_type + * @param {CEIType|MethodBase} scope * @param {ResolvedImport[]} imports * @param {Map} typemap */ -function genericTypeArgs(tokens, types, scoped_type, imports, typemap) { +function genericTypeArgs(tokens, types, scope, imports, typemap) { if (!tokens.isValue('>')) { - const type_arguments = typeIdentList(tokens, scoped_type, imports, typemap); + const type_arguments = typeIdentList(tokens, scope, imports, typemap); types.forEach((t,i,arr) => { if (t instanceof CEIType) { let specialised = t.specialise(type_arguments); @@ -111,12 +111,12 @@ function genericTypeArgs(tokens, types, scoped_type, imports, typemap) { /** * @param {TokenList} tokens - * @param {CEIType} scoped_type + * @param {CEIType|MethodBase} scope * @param {ResolvedImport[]} imports * @param {Map} typemap * @returns {WildcardType} */ -function wildcardTypeArgument(tokens, scoped_type, imports, typemap) { +function wildcardTypeArgument(tokens, scope, imports, typemap) { tokens.expectValue('?'); let bound = null; switch (tokens.current.value) { @@ -126,7 +126,7 @@ function wildcardTypeArgument(tokens, scoped_type, imports, typemap) { tokens.inc(); bound = { kind, - type: typeIdent(tokens, scoped_type, imports, typemap), + type: typeIdent(tokens, scope, imports, typemap), } break; } diff --git a/langserver/java/validater.js b/langserver/java/validater.js index 5953f3f..a65bf4c 100644 --- a/langserver/java/validater.js +++ b/langserver/java/validater.js @@ -2,8 +2,7 @@ const { JavaType } = require('java-mti'); const { ModuleBlock, TypeDeclBlock } = require('./parser9'); const { resolveImports } = require('../java/import-resolver'); const ResolvedImport = require('../java/parsetypes/resolved-import'); -const { resolveType } = require('../java/type-resolver'); -const { SourceType, SourceConstructor } = require('./source-type'); +const { SourceType, SourceMethod, SourceConstructor, ResolvableType } = require('./source-type'); const { parseBody, flattenBlocks } = require('./body-parser3'); const { TokenList } = require('./TokenList'); const { typeIdent } = require('./typeident'); @@ -28,6 +27,17 @@ function getSourceTypes(mod, owner_typename, parent, source_types, typemap) { }); } +/** + * @param {ResolvableType} rt + * @param {SourceType|SourceMethod|SourceConstructor} scope + * @param {ResolvedImport[]} resolved_imports + * @param {Map} typemap + */ +function resolveResolvableType(rt, scope, resolved_imports, typemap) { + const tokens = new TokenList(flattenBlocks(rt.typeTokens, false)); + rt._resolved = typeIdent(tokens, scope, resolved_imports, typemap); +} + /** * * @param {SourceType} source_type @@ -35,11 +45,20 @@ function getSourceTypes(mod, owner_typename, parent, source_types, typemap) { * @param {Map} typemap */ function resolveResolvableTypes(source_type, resolved_imports, typemap) { - const resolvableTypes = source_type.getAllResolvableTypes(); - resolvableTypes.forEach(rt => { - const tokens = new TokenList(flattenBlocks(rt.typeTokens, false)); - rt._resolved = typeIdent(tokens, source_type, resolved_imports, typemap); - }) + source_type.extends_types.forEach(rt => resolveResolvableType(rt, source_type, resolved_imports, typemap)); + source_type.implements_types.forEach(rt => resolveResolvableType(rt, source_type, resolved_imports, typemap)); + + source_type.fields.forEach(f => resolveResolvableType(f._type, source_type, resolved_imports, typemap)); + + // methods and constructors can have parameterized types + source_type.methods.forEach(m => { + resolveResolvableType(m._returnType, m, resolved_imports, typemap); + m.parameters.forEach(p => resolveResolvableType(p._paramType, m, resolved_imports, typemap)); + }); + + source_type.declaredConstructors.forEach(c => { + c.parameters.forEach(p => resolveResolvableType(p._paramType, c, resolved_imports, typemap)); + }); } /** @@ -67,11 +86,7 @@ function validate(mod, androidLibrary) { if (parsed) probs = probs.concat(parsed.problems) }) - t.constructors.forEach(c => { - // ignore any default constructors - if (!(c instanceof SourceConstructor)) { - return; - } + t.declaredConstructors.forEach(c => { console.log(c.label); const parsed = parseBody(c, imports.resolved, imports.typemap); if (parsed) diff --git a/langserver/java/validation/unresolved-types.js b/langserver/java/validation/unresolved-types.js index ce1ef3a..8165402 100644 --- a/langserver/java/validation/unresolved-types.js +++ b/langserver/java/validation/unresolved-types.js @@ -12,7 +12,7 @@ function checkType(type, typeTokens, probs) { return; } if (type instanceof CEIType) { - type.typevars.forEach(tv => { + type.typeVariables.forEach(tv => { if (tv instanceof TypeArgument) { checkType(tv.type, typeTokens, probs); }