From f4a18ebcdc408cfa7acd44e2eff0d57398ba4024 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Fri, 12 Jun 2020 14:32:11 +0100 Subject: [PATCH] initial changes to support local type declarations --- langserver/java/body-parser3.js | 292 ++++++++++++++++++++++++++++++- langserver/java/source-types2.js | 177 +++++++++++++++++++ langserver/java/type-resolver.js | 3 + 3 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 langserver/java/source-types2.js diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index 8e85771..9c383c7 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -11,9 +11,10 @@ const ResolvedImport = require('./parsetypes/resolved-import'); const ParseProblem = require('./parsetypes/parse-problem'); const { getOperatorType, Token } = require('./tokenizer'); const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resolver'); -const { genericTypeArgs, typeIdent } = require('./typeident'); +const { genericTypeArgs, typeIdent, typeIdentList } = require('./typeident'); const { TokenList } = require("./TokenList"); const { AnyMethod, AnyType, AnyValue, ArrayElement, ArrayLiteral, ConstructorCall, Label, LiteralNumber, LiteralValue, Local, MethodCall, MethodDeclarations, ResolvedIdent, TernaryValue, Value } = require("./body-types"); +const { SourceType, SourceField2, SourceMethod2, SourceConstructor2, SourceParameter2 } = require('./source-types2'); /** * @typedef {SourceMethod|SourceConstructor|SourceInitialiser} SourceMC @@ -41,7 +42,7 @@ function flattenBlocks(blocks, isMethod) { } /** - * @param {SourceMC} method + * @param {SourceMethod | SourceConstructor | SourceInitialiser} method * @param {ResolvedImport[]} imports * @param {Map} typemap */ @@ -103,6 +104,9 @@ function statement(tokens, mdecls, method, imports, typemap) { modifiers.push(tokens.current); tokens.inc(); continue; + case 'type-kw': + localType(modifiers.splice(0,1e9), tokens, mdecls, method, imports, typemap); + continue; } break; } @@ -231,6 +235,290 @@ class AssertStatement extends Statement { message = null; } +/** +* @param {Token[]} modifiers +* @param {TokenList} tokens +* @param {MethodDeclarations} mdecls +* @param {SourceMC} method +* @param {ResolvedImport[]} imports +* @param {Map} typemap +*/ +function localType(modifiers, tokens, mdecls, method, imports, typemap) { + // local types are inner types with number-prefixed names, eg. Type$1Inner + const type = typeDeclaration(method, modifiers, tokens.current, tokens, imports, typemap); + mdecls.types.push(type); + if (tokens.isValue('extends')) { + const extends_types = typeIdentList(tokens, type, imports, typemap); + } + if (tokens.isValue('implements')) { + const implement_types = typeIdentList(tokens, type, imports, typemap); + } + tokens.expectValue('{'); + if (!tokens.isValue('}')) { + typeBody(type, tokens, method, imports, typemap); + } +} + +/** +* @param {SourceType} type +* @param {TokenList} tokens +* @param {SourceMC} method +* @param {ResolvedImport[]} imports +* @param {Map} typemap +*/ +function typeBody(type, tokens, method, imports, typemap) { + while (!tokens.isValue('}')) { + let modifiers = []; + while (tokens.current.kind === 'modifier') { + modifiers.push(tokens.current); + tokens.inc(); + } + switch(tokens.current.kind) { + case 'ident': + case 'primitive-type': + fmc(modifiers, [], type, tokens, imports, typemap); + continue; + case 'type-kw': + localType(modifiers, tokens, new MethodDeclarations(), method, imports, typemap); + continue; + } + switch(tokens.current.value) { + case '<': + const type_variables = typeVariableList(type, tokens, type, imports, typemap); + fmc(modifiers, type_variables, type, tokens, imports, typemap); + continue; + case '@': + tokens.inc().value === 'interface' + ? annotationTypeDeclaration(type, modifiers.splice(0,1e9), tokens, imports, typemap) + : annotation(tokens, type, imports, typemap); + continue; + case ';': + tokens.inc(); + continue; + } + if (!tokens.inc()) { + break; + } + } +} + +/** + * @param {Token[]} modifiers + * @param {TypeVariable[]} type_variables + * @param {SourceType} type + * @param {TokenList} tokens + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function fmc(modifiers, type_variables, type, tokens, imports, typemap) { + const decl_type = typeIdent(tokens, type, imports, typemap); + if (decl_type.rawTypeSignature === type.rawTypeSignature) { + if (tokens.current.value === '(') { + // constructor + const { parameters, throws, body } = methodDeclaration(type, tokens, imports, typemap); + const ctr = new SourceConstructor2(type, modifiers, parameters, throws, body); + type.constructors.push(ctr); + return; + } + } + let name = tokens.current; + if (!tokens.isKind('ident')) { + name = null; + addproblem(tokens, ParseProblem.Error(tokens.current, `Identifier expected`)) + } + if (tokens.current.value === '(') { + const { parameters, throws, body } = methodDeclaration(type, tokens, imports, typemap); + const method = new SourceMethod2(type, modifiers, decl_type, name, parameters, throws, body); + type.methods.push(method); + } else { + if (name) { + if (type_variables.length) { + addproblem(tokens, ParseProblem.Error(tokens.current, `Fields cannot declare type variables`)); + } + const locals = var_ident_list(modifiers, decl_type, name, tokens, new MethodDeclarations(), type, imports, typemap); + const fields = locals.map(l => new SourceField2(type, modifiers, l.type, l.decltoken)); + type.fields.push(...fields); + } + semicolon(tokens); + } +} + +/** + * + * @param {TokenList} tokens + */ +function annotation(tokens, scope, imports, typemap) { + if (tokens.current.kind !== 'ident') { + addproblem(tokens, ParseProblem.Error(tokens.current, `Type identifier expected`)); + return; + } + let annotation_type = typeIdent(tokens, scope, imports, typemap, false); + if (tokens.isValue('(')) { + if (!tokens.isValue(')')) { + expressionList(tokens, new MethodDeclarations(), scope, imports, typemap); + tokens.expectValue(')'); + } + } +} + +/** + * @param {SourceType | SourceMC} scope + * @param {Token[]} modifiers + * @param {TokenList} tokens + * @param {CEIType | SourceMC} scope + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function annotationTypeDeclaration(scope, modifiers, tokens, imports, typemap) { + const type = typeDeclaration(scope, modifiers, tokens.current, tokens, imports, typemap); +} + +/** + * @param {SourceType | SourceMC} scope + * @param {Token[]} modifiers + * @param {Token} kind_token + * @param {TokenList} tokens + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function typeDeclaration(scope, modifiers, kind_token, tokens, imports, typemap) { + let name = tokens.inc(); + if (!tokens.isKind('ident')) { + name = null; + addproblem(tokens, ParseProblem.Error(tokens.current, `Type identifier expected`)); + return; + } + const type = new SourceType('', scope, '', modifiers.map(m => m.source), kind_token, name); + type.typeVariables = tokens.current.value === '<' + ? typeVariableList(type, tokens, scope, imports, typemap) + : []; + + return type; +} + +/** + * @param {CEIType} owner + * @param {TokenList} tokens + * @param {CEIType | SourceMC} scope + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function typeVariableList(owner, tokens, scope, imports, typemap) { + tokens.expectValue('<'); + /** @type {TypeVariable[]} */ + const type_variables = []; + for (;;) { + let name = tokens.current, bounds = []; + if (!tokens.isKind('ident')) { + name = null; + addproblem(tokens, ParseProblem.Error(tokens.current, `Type identifier expected`)); + } + switch (tokens.current.value) { + case 'extends': + case 'super': + const type_bounds = typeIdent(tokens, scope, imports, typemap); + bounds.push(new TypeVariable.Bound(owner, type_bounds.typeSignature, type_bounds.typeKind === 'interface')); + break; + } + if (name) { + type_variables.push(new TypeVariable(owner, name.value, bounds)); + if (tokens.isValue(',')) { + continue; + } + } + if (tokens.current.kind === 'ident') { + addproblem(tokens, ParseProblem.Error(tokens.current, `Missing comma`)); + continue; + } + tokens.expectValue('>'); + break; + } + return type_variables; +} + + +/** + * @param {SourceType} owner + * @param {TokenList} tokens + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function methodDeclaration(owner, tokens, imports, typemap) { + tokens.expectValue('('); + let parameters = [], throws = [], body = null; + if (!tokens.isValue(')')) { + for(;;) { + const p = parameterDeclaration(owner, tokens, imports, typemap); + parameters.push(p); + if (tokens.isValue(',')) { + continue; + } + tokens.expectValue(')'); + break; + } + } + if (tokens.isValue('throws')) { + throws = typeIdentList(tokens, owner, imports, typemap); + } + if (!tokens.isValue(';')) { + const start_idx = tokens.idx; + if (tokens.expectValue('{')) { + // skip the method body + for (let balance=1; balance;) { + switch (tokens.current.value) { + case '{': balance++; break; + case '}': { + if (--balance === 0) { + body = tokens.tokens.slice(start_idx, tokens.idx + 1); + } + break; + } + } + tokens.inc(); + } + } + } + return { + parameters, + throws, + body, + } +} + +/** + * @param {SourceType} owner + * @param {TokenList} tokens + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function parameterDeclaration(owner, tokens, imports, typemap) { + const modifiers = []; + while (tokens.current.kind === 'modifier') { + modifiers.push(tokens.current); + tokens.inc(); + } + checkLocalModifiers(tokens, modifiers); + let type = typeIdent(tokens, owner, imports, typemap); + const varargs = tokens.isValue('...'); + let name_token = tokens.current; + if (!tokens.isKind('ident')) { + name_token = null; + addproblem(tokens, ParseProblem.Error(tokens.current, `Identifier expected`)) + } + let postnamearrdims = 0; + while (tokens.isValue('[')) { + postnamearrdims += 1; + tokens.expectValue(']'); + } + if (postnamearrdims > 0) { + type = new ArrayType(type, postnamearrdims); + } + if (varargs) { + type = new ArrayType(type, 1); + } + return new SourceParameter2(modifiers, type, varargs, name_token); +} + /** * @param {TokenList} tokens * @param {MethodDeclarations} mdecls diff --git a/langserver/java/source-types2.js b/langserver/java/source-types2.js new file mode 100644 index 0000000..e248d31 --- /dev/null +++ b/langserver/java/source-types2.js @@ -0,0 +1,177 @@ +const { CEIType, JavaType, Field, Method, Constructor, Parameter } = require('java-mti'); +const { SourceMethod, SourceConstructor, SourceInitialiser } = require('./source-type'); +const { Token } = require('./tokenizer'); + +/** + * @param {SourceType|SourceMethod|SourceConstructor|SourceInitialiser|string} scope_or_package_name + * @param {Token} name + */ +function generateShortSignature(scope_or_package_name, name) { + if (scope_or_package_name instanceof SourceType) { + const type = scope_or_package_name; + return `${type._rawShortSignature}$${name.value}`; + } + if (scope_or_package_name instanceof SourceMethod + || scope_or_package_name instanceof SourceConstructor + || scope_or_package_name instanceof SourceInitialiser) { + const method = scope_or_package_name; + return `${method.owner._rawShortSignature}$${method.owner.localTypeCount += 1}${name.value}`; + } + const pkgname = scope_or_package_name; + return pkgname ?`${pkgname.replace(/\./g, '/')}/${name.value}` : name.value; +} + +class SourceType extends CEIType { + /** + * @param {string} packageName + * @param {SourceType|SourceMethod2|SourceConstructor|SourceInitialiser} outer_scope + * @param {string} docs + * @param {string[]} modifiers + * @param {Token} kind_token + * @param {Token} name_token + */ + constructor(packageName, outer_scope, docs, modifiers, kind_token, name_token) { + // @ts-ignore + super(generateShortSignature(outer_scope || packageName, name_token), kind_token.source, modifiers, docs); + super.packageName = packageName; + this.kind_token = kind_token; + this.name_token = name_token; + this.scope = outer_scope; + /** + * Number of local/anonymous types declared in the scope of this type + * The number is used when naming them. + */ + this.localTypeCount = 0; + /** @type {SourceConstructor2[]} */ + this.constructors = []; + /** @type {SourceMethod2[]} */ + this.methods = []; + /** @type {SourceField2[]} */ + this.fields = []; + } + +} + +class SourceField2 extends Field { + /** + * @param {SourceType} owner + * @param {Token[]} modifiers + * @param {JavaType} field_type + * @param {Token} name_token + */ + constructor(owner, modifiers, field_type, name_token) { + super(modifiers.map(m => m.value), ''); + this.owner = owner; + this.fieldType = field_type; + this.nameToken = name_token; + } + + get name() { + return this.nameToken ? this.nameToken.value : ''; + } + + get type() { + return this.fieldType; + } +} + +class SourceConstructor2 extends Constructor { + /** + * @param {SourceType} owner + * @param {Token[]} modifiers + * @param {SourceParameter2[]} parameters + * @param {JavaType[]} throws + * @param {Token[]} body + */ + constructor(owner, modifiers, parameters, throws, body) { + super(owner, modifiers.map(m => m.value), ''); + this.owner = owner; + this.sourceParameters = parameters; + this.throws = throws; + this.body_tokens = body; + } + + get hasImplementation() { + return !!this.body_tokens; + } + + get parameterCount() { + return this.sourceParameters.length; + } + + /** + * @returns {SourceParameter2[]} + */ + get parameters() { + return this.sourceParameters; + } + + /** + * @returns {SourceType} + */ + get returnType() { + return this.owner; + } +} + +class SourceMethod2 extends Method { + /** + * @param {SourceType} owner + * @param {Token[]} modifiers + * @param {JavaType} method_type + * @param {Token} name_token + * @param {SourceParameter2[]} parameters + * @param {JavaType[]} throws + * @param {Token[]} body + */ + constructor(owner, modifiers, method_type, name_token, parameters, throws, body) { + super(owner, name_token ? name_token.value : '', modifiers.map(m => m.value), ''); + this.owner = owner; + this.methodType = method_type; + this.sourceParameters = parameters; + this.throws = throws; + this.body_tokens = body; + } + + get hasImplementation() { + return !!this.body_tokens; + } + + get parameterCount() { + return this.sourceParameters.length; + } + + /** + * @returns {SourceParameter2[]} + */ + get parameters() { + return this.sourceParameters; + } + + /** + * @returns {JavaType} + */ + get returnType() { + return this.methodType; + } +} + +class SourceParameter2 extends Parameter { + /** + * @param {Token[]} modifiers + * @param {JavaType} type + * @param {boolean} varargs + * @param {Token} name_token + */ + constructor(modifiers, type, varargs, name_token) { + super(name_token ? name_token.value : '', type, varargs); + this.name_token = name_token; + this.modifiers = modifiers; + } +} + +exports.SourceType = SourceType; +exports.SourceField2 = SourceField2; +exports.SourceMethod2 = SourceMethod2; +exports.SourceParameter2 = SourceParameter2; +exports.SourceConstructor2 = SourceConstructor2; diff --git a/langserver/java/type-resolver.js b/langserver/java/type-resolver.js index f50917d..9f8b46e 100644 --- a/langserver/java/type-resolver.js +++ b/langserver/java/type-resolver.js @@ -288,6 +288,9 @@ function resolveTypeOrPackage(ident, scope, imports, typemap) { } scopes.pop(); } + if (!types[0] && scoped_type.simpleTypeName === ident) { + types.push(scoped_type); + } } if (scope instanceof CEIType) {