From 7d1d215b79a0afcbb28fbcf5b25e0d03891156ec Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Mon, 29 Jun 2020 23:48:29 +0100 Subject: [PATCH] add support for parsing and validating anonymous types --- langserver/java/body-parser.js | 48 +++++-- .../java/expressiontypes/NewExpression.js | 3 +- langserver/java/source-types.js | 127 ++++++++++++++---- langserver/java/statement-validater.js | 3 + langserver/java/validater.js | 14 +- 5 files changed, 146 insertions(+), 49 deletions(-) diff --git a/langserver/java/body-parser.js b/langserver/java/body-parser.js index 9d5543c..637bbde 100644 --- a/langserver/java/body-parser.js +++ b/langserver/java/body-parser.js @@ -6,7 +6,7 @@ */ const { CEIType, PrimitiveType, ArrayType, UnresolvedType, TypeVariable, Field, Method } = require('java-mti'); const { SourceType, SourceTypeIdent, SourceField, SourceMethod, SourceConstructor, SourceInitialiser, SourceParameter, SourceAnnotation, - SourceUnit, SourcePackage, SourceImport, SourceArrayType, FixedLengthArrayType } = require('./source-types'); + SourceUnit, SourcePackage, SourceImport, SourceArrayType, FixedLengthArrayType, NamedSourceType, AnonymousSourceType } = require('./source-types'); const ResolvedImport = require('./parsetypes/resolved-import'); const ParseProblem = require('./parsetypes/parse-problem'); const { tokenize, Token } = require('./tokenizer'); @@ -86,6 +86,23 @@ function flattenBlocks(blocks, isMethod) { }, []) } +/** + * @param {SourceType} type + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function parseTypeMethods(type, imports, typemap) { + type.initers.forEach(i => { + i.parsed = parseBody(i, imports, typemap); + }) + type.constructors.forEach(c => { + c.parsed = parseBody(c, imports, typemap); + }) + type.sourceMethods.forEach(m => { + m.parsed = parseBody(m, imports, typemap); + }) +} + /** * @param {SourceMethod | SourceConstructor | SourceInitialiser} method * @param {ResolvedImport[]} imports @@ -97,16 +114,15 @@ function parseBody(method, imports, typemap) { return null; } const tokenlist = new TokenList(flattenBlocks(body_tokens, true)); - let block = null; let mdecls = new MethodDeclarations(); try { - block = statementBlock(tokenlist, mdecls, method, imports, typemap); - checkStatementBlock(block, method, typemap, tokenlist.problems); + method.body.block = statementBlock(tokenlist, mdecls, method, imports, typemap); + checkStatementBlock(method.body.block, method, typemap, tokenlist.problems); } catch (err) { addproblem(tokenlist, ParseProblem.Information(tokenlist.current, `Parse failed: ${err.message}`)); } return { - block, + block: method.body.block, problems: tokenlist.problems, } } @@ -168,7 +184,7 @@ function extractSourceTypes(tokens, typemap) { kind_token = findTokenAt(m.index), name_token = findTokenAt(m.index + m[0].match(/\w+$/).index), outer_type = type_stack[0] && type_stack[0].source_type, - source_type = new SourceType(package_name, outer_type, '', [], typeKind, kind_token, name_token, typemap); + source_type = new NamedSourceType(package_name, outer_type, '', [], typeKind, kind_token, name_token, typemap); type_stack.unshift({ source_type, @@ -690,17 +706,17 @@ function typeDeclaration(package_name, scope, docs, modifiers, typeKind, kind_to addproblem(tokens, ParseProblem.Error(tokens.current, `Type identifier expected`)); return; } - const type_short_sig = SourceType.getShortSignature(package_name, scope, name.value); + const type_short_sig = NamedSourceType.getShortSignature(package_name, scope, name.value); // the source type object should already exist in the type map - /** @type {SourceType} */ + /** @type {NamedSourceType} */ // @ts-ignore let type = typemap.get(type_short_sig); - if (type instanceof SourceType) { + if (type instanceof NamedSourceType) { // update the missing parts type.setModifierTokens(modifiers); type.docs = docs; } else { - type = new SourceType(package_name, scope, docs, modifiers, typeKind, kind_token, name, typemap); + type = new NamedSourceType(package_name, scope, docs, modifiers, typeKind, kind_token, name, typemap); } type.typeVariables = tokens.current.value === '<' ? typeVariableList(type, tokens, scope, imports, typemap) @@ -1608,8 +1624,14 @@ function newTerm(tokens, mdecls, scope, imports, typemap) { newtokens = tokens.markEnd(); // @ts-ignore if (tokens.current.value === '{') { - // anonymous type - just skip for now - type_body = skipBody(tokens); + // anonymous type + tokens.consume(); + type_body = new AnonymousSourceType(ctr_type, scope, typemap); + typemap.set(type_body.shortSignature, type_body); + typeBody(type_body, tokens, mdecls, imports, typemap); + tokens.expectValue('}'); + // perform an immediate parse of all the methods in the anonymous class + parseTypeMethods(type_body, imports, typemap); } return new ResolvedIdent(match.source, [new NewObject(new_token, ctr_type, open_bracket, ctr_args, commas, type_body)], [], [], '', newtokens); } @@ -1907,6 +1929,6 @@ function findIdentifier(token, mdecls, scope, imports, typemap) { exports.addproblem = addproblem; -exports.parseBody = parseBody; +exports.parseTypeMethods = parseTypeMethods; exports.parse = parse; exports.flattenBlocks = flattenBlocks; diff --git a/langserver/java/expressiontypes/NewExpression.js b/langserver/java/expressiontypes/NewExpression.js index 1786bb6..c111496 100644 --- a/langserver/java/expressiontypes/NewExpression.js +++ b/langserver/java/expressiontypes/NewExpression.js @@ -2,6 +2,7 @@ * @typedef {import('../tokenizer').Token} Token * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolveInfo} ResolveInfo + * @typedef {import('../source-types').AnonymousSourceType} AnonymousSourceType * @typedef {import('../source-types').SourceTypeIdent} SourceTypeIdent * @typedef {import('java-mti').JavaType} JavaType */ @@ -64,7 +65,7 @@ class NewObject extends Expression { * @param {Token} open_bracket * @param {ResolvedIdent[]} ctr_args * @param {Token[]} commas - * @param {Token[]} type_body + * @param {AnonymousSourceType} type_body */ constructor(new_token, object_type, open_bracket, ctr_args, commas, type_body) { super(); diff --git a/langserver/java/source-types.js b/langserver/java/source-types.js index 7d4720d..1519e03 100644 --- a/langserver/java/source-types.js +++ b/langserver/java/source-types.js @@ -1,4 +1,5 @@ const { CEIType, JavaType, PrimitiveType, ArrayType, TypeVariableType, Field, Method, MethodBase, Constructor, Parameter, TypeVariable, TypeArgument } = require('java-mti'); +const { AnyType } = require('./anys'); const { Token } = require('./tokenizer'); /** @@ -59,24 +60,14 @@ function createImplicitEnumMethods(enum_type, typemap) { class SourceType extends CEIType { /** - * @param {string} packageName - * @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope + * @param {string} rawShortSignature + * @param {'class'|'interface'|'enum'|'@interface'} typeKind + * @param {string[]|number} modifiers * @param {string} docs - * @param {Token[]} modifiers - * @param {string} typeKind - * @param {Token} kind_token - * @param {Token} name_token * @param {Map} typemap */ - constructor(packageName, outer_scope, docs, modifiers, typeKind, kind_token, name_token, typemap) { - // @ts-ignore - super(generateShortSignature(outer_scope || packageName, name_token.value), typeKind, modifiers.map(m => m.source), docs); - super.packageName = packageName; - this.modifierTokens = modifiers; - this.kind_token = kind_token; - this.nameToken = name_token; - this.scope = outer_scope; - this.typemap = typemap; + constructor(rawShortSignature, typeKind, modifiers, docs, typemap) { + super(rawShortSignature, typeKind, modifiers, docs); /** * Number of local/anonymous types declared in the scope of this type * The number is used when naming them. @@ -98,14 +89,7 @@ class SourceType extends CEIType { this.initers = []; /** @type {SourceEnumValue[]} */ this.enumValues = []; - } - - /** - * @returns {SourceMethod[]} - */ - get sourceMethods() { - // @ts-ignore - return this.methods.filter(m => m instanceof SourceMethod);// [...this.implicitMethods, ...this.sourceMethods]; + this.typemap = typemap; } /** @@ -118,6 +102,95 @@ class SourceType extends CEIType { this.enumValues.push(new SourceEnumValue(this, docs, ident, ctr_args, anonymousType)); } + /** + * @returns {SourceMethod[]} + */ + get sourceMethods() { + // @ts-ignore + return this.methods.filter(m => m instanceof SourceMethod);// [...this.implicitMethods, ...this.sourceMethods]; + } + +} + +class AnonymousSourceType extends SourceType { + + /** + * @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} scope + */ + static genSignature(scope) { + const type = scope instanceof SourceType ? scope : scope.owner; + return `${type._rawShortSignature}$${type.localTypeCount += 1}`; + } + + /** + * @param {SourceTypeIdent} typeident + * @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope + * @param {Map} typemap + */ + constructor(typeident, outer_scope, typemap) { + super(AnonymousSourceType.genSignature(outer_scope), 'class', [], '', typemap); + this.simpleTypeName = typeident.resolved.simpleTypeName; + this.typeIdent = typeident; + } + + get dottedTypeName() { + return this.typeIdent.resolved.dottedTypeName; + } + + get fullyDottedRawName() { + return this.dottedTypeName; + } + + get fullyDottedTypeName() { + return this.dottedTypeName; + } + + get label() { + return `new ${this.dottedTypeName}`; + } + + /** @type {JavaType[]} */ + get supers() { + if (this.typeIdent.resolved instanceof AnyType || this.typeIdent.resolved.typeKind !== 'class') { + return [this.typemap.get('java/lang/Object')] + } + return [this.typeIdent.resolved]; + } + + get shortSignature() { + return this._rawShortSignature; + } + + get rawTypeSignature() { + return `L${this._rawShortSignature};`; + } + + get typeSignature() { + return this.rawTypeSignature; + } +} + +class NamedSourceType extends SourceType { + /** + * @param {string} packageName + * @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope + * @param {string} docs + * @param {Token[]} modifiers + * @param {string} typeKind + * @param {Token} kind_token + * @param {Token} name_token + * @param {Map} typemap + */ + constructor(packageName, outer_scope, docs, modifiers, typeKind, kind_token, name_token, typemap) { + // @ts-ignore + super(generateShortSignature(outer_scope || packageName, name_token.value), typeKind, modifiers.map(m => m.source), docs, typemap); + super.packageName = packageName; + this.modifierTokens = modifiers; + this.kind_token = kind_token; + this.nameToken = name_token; + this.scope = outer_scope; + } + /** * @param {string} package_name * @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser} outer_scope @@ -372,6 +445,8 @@ class SourceConstructor extends Constructor { tokens: body_tokens, /** @type {import('./body-types').Local[]} */ locals: [], + /** @type {import('./statementtypes/Block').Block} */ + block: null, } this.parsed = null; } @@ -423,6 +498,8 @@ class SourceMethod extends Method { tokens: body_tokens, /** @type {import('./body-types').Local[]} */ locals: [], + /** @type {import('./statementtypes/Block').Block} */ + block: null, } this.parsed = null; } @@ -467,6 +544,8 @@ class SourceInitialiser extends MethodBase { tokens: body_tokens, /** @type {import('./body-types').Local[]} */ locals: [], + /** @type {import('./statementtypes/Block').Block} */ + block: null, } this.parsed = null; } @@ -673,3 +752,5 @@ exports.SourceImport = SourceImport; exports.SourceEnumValue = SourceEnumValue; exports.SourceArrayType = SourceArrayType; exports.FixedLengthArrayType = FixedLengthArrayType; +exports.NamedSourceType = NamedSourceType; +exports.AnonymousSourceType = AnonymousSourceType; diff --git a/langserver/java/statement-validater.js b/langserver/java/statement-validater.js index 774080c..6bb2592 100644 --- a/langserver/java/statement-validater.js +++ b/langserver/java/statement-validater.js @@ -16,6 +16,9 @@ const { ValidateInfo } = require('./body-types'); * @param {ParseProblem[]} problems */ function checkStatementBlock(block, method, typemap, problems) { + if (!block) { + return; + } block.validate(new ValidateInfo(typemap, problems, method)); } diff --git a/langserver/java/validater.js b/langserver/java/validater.js index da0f427..9a397bd 100644 --- a/langserver/java/validater.js +++ b/langserver/java/validater.js @@ -1,7 +1,7 @@ const { CEIType } = require('java-mti'); const { resolveImports } = require('../java/import-resolver'); const { SourceUnit } = require('./source-types'); -const { parseBody } = require('./body-parser'); +const { parseTypeMethods } = require('./body-parser'); /** * @param {SourceUnit} unit @@ -12,17 +12,7 @@ function parseMethodBodies(unit, typemap) { ...resolveImports(typemap, unit.packageName), ...unit.imports.filter(i => i.resolved).map(i => i.resolved), ] - unit.types.forEach(t => { - t.initers.forEach(i => { - i.parsed = parseBody(i, resolved_types, typemap); - }) - t.constructors.forEach(c => { - c.parsed = parseBody(c, resolved_types, typemap); - }) - t.sourceMethods.forEach(m => { - m.parsed = parseBody(m, resolved_types, typemap); - }) - }) + unit.types.forEach(t => parseTypeMethods(t, resolved_types, typemap)); } /**