From 426735aa25649baa851c19b3b39dc7fc61369c23 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Mon, 8 Jun 2020 21:02:34 +0100 Subject: [PATCH] update ResolvableType to use same type resolving as method body parsing --- langserver/java/TokenList.js | 10 +++-- langserver/java/body-parser3.js | 31 ++++++---------- langserver/java/source-type.js | 14 +++---- langserver/java/typeident.js | 65 ++++++++++++++++++++++++--------- langserver/java/validater.js | 17 +++++---- 5 files changed, 82 insertions(+), 55 deletions(-) diff --git a/langserver/java/TokenList.js b/langserver/java/TokenList.js index e69eeae..342a206 100644 --- a/langserver/java/TokenList.js +++ b/langserver/java/TokenList.js @@ -30,7 +30,7 @@ class TokenList { * @param {string} value */ isValue(value) { - if (this.current.value === value) { + if (this.current && this.current.value === value) { this.inc(); return true; } @@ -45,14 +45,18 @@ class TokenList { if (this.isValue(value)) { return true; } + const token = this.current || this.tokens[this.tokens.length - 1]; const addproblem = require("./body-parser3").addproblem; - addproblem(this, ParseProblem.Error(this.current, `${value} expected`)); + addproblem(this, ParseProblem.Error(token, `${value} expected`)); return false; } get previous() { for (let idx = this.idx - 1; idx >= 0; idx--) { - if (idx === 0 || this.tokens[idx].kind !== 'wsc') { + if (idx <= 0) { + return this.tokens[0]; + } + if (this.tokens[idx].kind !== 'wsc') { return this.tokens[idx]; } } diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index a5064cf..2edd3c2 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -10,7 +10,7 @@ const ResolvedImport = require('./parsetypes/resolved-import'); const ParseProblem = require('./parsetypes/parse-problem'); const { getOperatorType, Token } = require('./tokenizer'); const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resolver'); -const { typeIdentList } = require('./typeident'); +const { genericTypeArgs } = require('./typeident'); const { TokenList } = require("./TokenList"); const { AnyMethod, AnyType, AnyValue, ArrayElement, ArrayLiteral, ConstructorCall, LiteralNumber, LiteralValue, Local, MethodCall, ResolvedIdent, TernaryValue, Value } = require("./body-types"); @@ -19,17 +19,21 @@ const { AnyMethod, AnyType, AnyValue, ArrayElement, ArrayLiteral, ConstructorCal */ -function flattenBlocks(blocks) { +/** + * @param {*[]} blocks + * @param {boolean} isMethod + */ +function flattenBlocks(blocks, isMethod) { return blocks.reduce((arr,block) => { if (block instanceof Token) { // 'default' and 'synchronised' are not modifiers inside method bodies - if (block.kind === 'modifier' && /^(default|synchronized)$/.test(block.value)) { + if (isMethod && block.kind === 'modifier' && /^(default|synchronized)$/.test(block.value)) { block.kind = 'statement-kw' block.simplified = block.value; } arr.push(block); } else { - arr = [...arr, ...flattenBlocks(block.blockArray().blocks)]; + arr = [...arr, ...flattenBlocks(block.blockArray().blocks, isMethod)]; } return arr; }, []) @@ -45,7 +49,7 @@ function parseBody(method, imports, typemap) { if (!body || body.blocks[0].value !== '{') { return null; } - const tokenlist = new TokenList(flattenBlocks(body.blocks)); + const tokenlist = new TokenList(flattenBlocks(body.blocks, true)); let block = null; try { block = statementBlock(tokenlist, [], method, imports, typemap); @@ -1913,21 +1917,7 @@ function qualifiers(matches, tokens, locals, method, imports, typemap) { return matches; } tokens.inc(); - let type_arguments = []; - if (!tokens.isValue('>')) { - type_arguments = typeIdentList(tokens, method._owner, imports, typemap); - tokens.expectValue('>'); - } - matches.types = matches.types.map(t => { - if (t instanceof CEIType) { - if (t.typevars.length) { - const specialised_type = t.specialise(type_arguments); - typemap.set(specialised_type.shortSignature, specialised_type); - return specialised_type; - } - } - return t; - }); + genericTypeArgs(tokens, matches.types, method._owner, imports, typemap); break; default: return matches; @@ -2122,3 +2112,4 @@ function findIdentifier(ident, locals, method, imports, typemap) { exports.addproblem = addproblem; exports.parseBody = parseBody; +exports.flattenBlocks = flattenBlocks; diff --git a/langserver/java/source-type.js b/langserver/java/source-type.js index 73b0897..c289d32 100644 --- a/langserver/java/source-type.js +++ b/langserver/java/source-type.js @@ -40,15 +40,16 @@ class SourceType extends CEIType { * @param {ModuleBlock} mod * @param {TypeDeclBlock} type * @param {string} qualified_type_name qualified $-separated type name + * @param {Map} typemap */ - constructor(mod, type, qualified_type_name) { + constructor(mod, type, qualified_type_name, typemap) { super(type.shortSignature, type.kind(), mapmods(type), type.docs); this._decl = type; 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.implicit_extend = !this.extends_types.length && !this.implements_types.length ? [new ResolvableType({type: 'java.lang.Object', typeTokens:[]})] : []; + 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)); @@ -81,7 +82,7 @@ class SourceType extends CEIType { get supers() { return [ - ...this.implicit_extend.map(t => t.resolved), + ...this.implicit_extend, ...this.extends_types.map(t => t.resolved), ...this.implements_types.map(t => t.resolved) ]; @@ -90,7 +91,6 @@ class SourceType extends CEIType { getAllResolvableTypes() { /** @type {ResolvableType[]} */ const res = [ - ...this.implicit_extend, ...this.extends_types, ...this.implements_types, ]; @@ -247,7 +247,7 @@ class ResolvableType extends UnresolvedType { constructor(decl) { super(decl.type); this._decl = decl; - /** @type {import('../java/parsetypes/resolved-type')} */ + /** @type {JavaType} */ this._resolved = null; } @@ -255,9 +255,7 @@ class ResolvableType extends UnresolvedType { * @returns {JavaType} */ get resolved() { - if (!this._resolved) return this; - if (!this._resolved.mtis.length) return this; - return this._resolved.mtis[0]; + return this._resolved || this; } get typeTokens() { diff --git a/langserver/java/typeident.js b/langserver/java/typeident.js index 4ea1b08..a33e465 100644 --- a/langserver/java/typeident.js +++ b/langserver/java/typeident.js @@ -1,4 +1,4 @@ -const { ArrayType, CEIType, JavaType, WildcardType } = require('java-mti'); +const { ArrayType, CEIType, JavaType, PrimitiveType, WildcardType } = require('java-mti'); const { SourceMethod, SourceConstructor, SourceInitialiser } = require('./source-type'); const ResolvedImport = require('./parsetypes/resolved-import'); const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resolver'); @@ -34,30 +34,29 @@ function typeIdentList(tokens, scoped_type, imports, typemap) { * @param {Map} typemap */ function typeIdent(tokens, scoped_type, imports, typemap) { - if (tokens.current.kind !== 'ident') { - if (tokens.current.value === '?') { - return wildcardTypeArgument(tokens, scoped_type, imports, typemap); - } - return AnyType.Instance; + let types = [], package_name = ''; + switch(tokens.current.kind) { + case 'ident': + ({ types, package_name } = resolveTypeOrPackage(tokens.current.value, scoped_type, 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) + : AnyType.Instance; } - let { types, package_name } = resolveTypeOrPackage(tokens.current.value, scoped_type, imports, typemap); tokens.inc(); for (;;) { if (tokens.isValue('.')) { if (tokens.current.kind !== 'ident') { break; } - resolveNextTypeOrPackage(tokens.current.value, types, package_name, typemap); + ({ types, package_name } = resolveNextTypeOrPackage(tokens.current.value, types, package_name, typemap)); + tokens.inc(); } else if (tokens.isValue('<')) { - if (!tokens.isValue('>')) { - typeIdentList(tokens, scoped_type, imports, typemap); - if (/>>>?/.test(tokens.current.value)) { - // we need to split >> and >>> into separate > tokens to handle things like List> - const new_tokens = tokens.current.value.split('').map((gt,i) => new Token(tokens.current.range.source, tokens.current.range.start + i, 1, 'comparison-operator')); - tokens.splice(tokens.idx, 1, ...new_tokens); - } - tokens.expectValue('>'); - } + genericTypeArgs(tokens, types, scoped_type, imports, typemap); } else if (tokens.isValue('[')) { let arrdims = 0; for(;;) { @@ -79,6 +78,37 @@ function typeIdent(tokens, scoped_type, imports, typemap) { return types[0] || AnyType.Instance; } +/** + * + * @param {TokenList} tokens + * @param {JavaType[]} types + * @param {CEIType} scoped_type + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function genericTypeArgs(tokens, types, scoped_type, imports, typemap) { + if (!tokens.isValue('>')) { + const type_arguments = typeIdentList(tokens, scoped_type, imports, typemap); + types.forEach((t,i,arr) => { + if (t instanceof CEIType) { + let specialised = t.specialise(type_arguments); + if (typemap.has(specialised.shortSignature)) { + arr[i] = typemap.get(specialised.shortSignature); + return; + } + typemap.set(specialised.shortSignature, specialised); + arr[i] = specialised; + } + }); + if (/>>>?/.test(tokens.current.value)) { + // we need to split >> and >>> into separate > tokens to handle things like List> + const new_tokens = tokens.current.value.split('').map((gt,i) => new Token(tokens.current.range.source, tokens.current.range.start + i, 1, 'comparison-operator')); + tokens.splice(tokens.idx, 1, ...new_tokens); + } + tokens.expectValue('>'); + } +} + /** * @param {TokenList} tokens * @param {CEIType} scoped_type @@ -105,3 +135,4 @@ function wildcardTypeArgument(tokens, scoped_type, imports, typemap) { exports.typeIdent = typeIdent; exports.typeIdentList = typeIdentList; +exports.genericTypeArgs = genericTypeArgs; diff --git a/langserver/java/validater.js b/langserver/java/validater.js index 59083e1..5953f3f 100644 --- a/langserver/java/validater.js +++ b/langserver/java/validater.js @@ -4,7 +4,9 @@ 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 { parseBody } = require('./body-parser3'); +const { parseBody, flattenBlocks } = require('./body-parser3'); +const { TokenList } = require('./TokenList'); +const { typeIdent } = require('./typeident'); /** @@ -12,16 +14,17 @@ const { parseBody } = require('./body-parser3'); * @param {string} owner_typename * @param {ModuleBlock|TypeDeclBlock} parent * @param {SourceType[]} source_types + * @param {Map} typemap */ -function getSourceTypes(mod, owner_typename, parent, source_types) { +function getSourceTypes(mod, owner_typename, parent, source_types, typemap) { parent.types.forEach(type => { const qualifiedTypeName = `${owner_typename}${type.simpleName}`; // we add the names of type variables here, but we resolve any bounds later //const typevar_names = type.typevars.map(tv => tv.name); //const mti = new MTI().addType(package_name, '', mods, type.kind(), qualifiedTypeName, typevar_names); - const t = new SourceType(mod, type, qualifiedTypeName); + const t = new SourceType(mod, type, qualifiedTypeName, typemap); source_types.push(t); - getSourceTypes(mod, `${qualifiedTypeName}$`, type, source_types); + getSourceTypes(mod, `${qualifiedTypeName}$`, type, source_types, typemap); }); } @@ -32,10 +35,10 @@ function getSourceTypes(mod, owner_typename, parent, source_types) { * @param {Map} typemap */ function resolveResolvableTypes(source_type, resolved_imports, typemap) { - const fully_qualified_scope = source_type.shortSignature; const resolvableTypes = source_type.getAllResolvableTypes(); resolvableTypes.forEach(rt => { - rt._resolved = resolveType(rt.label, fully_qualified_scope, resolved_imports, typemap); + const tokens = new TokenList(flattenBlocks(rt.typeTokens, false)); + rt._resolved = typeIdent(tokens, source_type, resolved_imports, typemap); }) } @@ -48,7 +51,7 @@ function validate(mod, androidLibrary) { /** @type {SourceType[]} */ const source_types = []; - getSourceTypes(mod, '', mod, source_types); + getSourceTypes(mod, '', mod, source_types, androidLibrary); const imports = resolveImports(androidLibrary, source_types, mod.imports, mod.packageName);