diff --git a/langserver/java/TokenList.js b/langserver/java/TokenList.js index b4f7aef..c1f5159 100644 --- a/langserver/java/TokenList.js +++ b/langserver/java/TokenList.js @@ -37,7 +37,11 @@ class TokenList { markEnd() { let i = this.idx; while (this.tokens[--i].kind === 'wsc') { } - return this.tokens.slice(this.marks.shift(), i + 1); + const range = [this.marks.shift(), i + 1]; + if (range[1] <= range[0]) { + range[1] = range[0] + 1; + } + return this.tokens.slice(range[0], range[1]); } /** diff --git a/langserver/java/anys.js b/langserver/java/anys.js index a2720da..322b1d3 100644 --- a/langserver/java/anys.js +++ b/langserver/java/anys.js @@ -61,6 +61,10 @@ class AnyValue extends Expression { this.label = label; this.type = AnyType.Instance; } + + resolveExpression() { + return this.type; + } } /** @@ -100,6 +104,21 @@ class TypeIdentType { } } +/** + * Custom type used to represent package name expressions + * + * eg. `java` + */ +class PackageNameType { + /** + * @param {string} package_name + */ + constructor(package_name) { + this.package_name = package_name; + } +} + + /** * Custom type used to represent an array literal * @@ -131,7 +150,7 @@ class MultiValueType { /** * @typedef {import('./expressiontypes/literals/Number').NumberLiteral} NumberLiteral - * @typedef {JavaType|MethodType|LambdaType|ArrayValueType|TypeIdentType|MultiValueType|NumberLiteral} ResolvedValue + * @typedef {JavaType|MethodType|LambdaType|ArrayValueType|TypeIdentType|PackageNameType|MultiValueType|NumberLiteral} ResolvedValue **/ exports.AnyMethod = AnyMethod; @@ -141,4 +160,5 @@ exports.ArrayValueType = ArrayValueType; exports.LambdaType = LambdaType; exports.MethodType = MethodType; exports.MultiValueType = MultiValueType; +exports.PackageNameType = PackageNameType; exports.TypeIdentType = TypeIdentType; diff --git a/langserver/java/body-types.js b/langserver/java/body-types.js index f6a3272..386b429 100644 --- a/langserver/java/body-types.js +++ b/langserver/java/body-types.js @@ -4,7 +4,7 @@ */ const { JavaType, CEIType, ArrayType, Method } = require('java-mti'); const { Token } = require('./tokenizer'); -const { AnyType, MethodType, TypeIdentType } = require('./anys'); +const { AnyType, MethodType, PackageNameType, TypeIdentType } = require('./anys'); const ParseProblem = require('./parsetypes/parse-problem'); @@ -40,10 +40,12 @@ class ResolvedIdent { if (this.types[0]) { return new TypeIdentType(this.types[0]); } + if (this.package_name) { + return new PackageNameType(this.package_name); + } ri.problems.push(ParseProblem.Error(this.tokens, `Unresolved identifier: ${this.source}`)); return AnyType.Instance; } - } class Local { @@ -98,7 +100,13 @@ class MethodDeclarations { } popScope() { - [this.locals, this.labels, this.types] = this._scopeStack.pop(); + const prev = { + locals: this.locals, + labels: this.labels, + types: this.types, + }; + ([this.locals, this.labels, this.types] = this._scopeStack.pop()); + return prev; } } diff --git a/langserver/java/expressiontypes/Expression.js b/langserver/java/expressiontypes/Expression.js index 5e879fb..c48d82a 100644 --- a/langserver/java/expressiontypes/Expression.js +++ b/langserver/java/expressiontypes/Expression.js @@ -13,7 +13,7 @@ class Expression { * @returns {ResolvedValue} */ resolveExpression(ri) { - throw new Error('Expression.resolveType'); + throw new Error('Expression.resolveExpression'); } /** @returns {Token|Token[]} */ diff --git a/langserver/java/expressiontypes/LambdaExpression.js b/langserver/java/expressiontypes/LambdaExpression.js index c77e337..08fad3b 100644 --- a/langserver/java/expressiontypes/LambdaExpression.js +++ b/langserver/java/expressiontypes/LambdaExpression.js @@ -21,7 +21,7 @@ class LambdaExpression extends Expression { /** * @param {ResolveInfo} ri */ - resolveType(ri) { + resolveExpression(ri) { return new LambdaType(); } diff --git a/langserver/java/expressiontypes/MemberExpression.js b/langserver/java/expressiontypes/MemberExpression.js index 5e8c9c4..7869101 100644 --- a/langserver/java/expressiontypes/MemberExpression.js +++ b/langserver/java/expressiontypes/MemberExpression.js @@ -5,8 +5,9 @@ */ const { Expression } = require("./Expression"); const { CEIType } = require('java-mti'); -const { AnyType, MethodType, TypeIdentType } = require('../anys'); +const { AnyType, MethodType, PackageNameType, TypeIdentType } = require('../anys'); const { getTypeInheritanceList } = require('../expression-resolver'); +const { resolveNextPackage } = require('../type-resolver'); const ParseProblem = require('../parsetypes/parse-problem'); class MemberExpression extends Expression { @@ -25,25 +26,36 @@ class MemberExpression extends Expression { * @param {ResolveInfo} ri */ resolveExpression(ri) { - let type = this.instance.resolveExpression(ri); - if (type instanceof TypeIdentType) { + let instance = this.instance.resolveExpression(ri); + if (instance instanceof TypeIdentType) { // static member - type = type.type; + instance = instance.type; } - if (type instanceof AnyType) { - return type; + if (instance instanceof AnyType) { + return instance; } const ident = this.member.value; - if (!(type instanceof CEIType)) { + + if (instance instanceof PackageNameType) { + const { sub_package_name, type } = resolveNextPackage(instance.package_name, ident, ri.typemap); + if (!type && !sub_package_name) { + ri.problems.push(ParseProblem.Error(this.member, `Unresolved identifier: '${ident}'`)); + } + return type ? new TypeIdentType(type) + : sub_package_name ? new PackageNameType(sub_package_name) + : AnyType.Instance; + } + + if (!(instance instanceof CEIType)) { ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}'`)); return AnyType.Instance; } - const field = type.fields.find(f => f.name === ident); + const field = instance.fields.find(f => f.name === ident); if (field) { return field.type; } let methods = new Map(); - getTypeInheritanceList(type).forEach(type => { + getTypeInheritanceList(instance).forEach(type => { type.methods.forEach(m => { let msig; if (m.name === ident && !methods.has(msig = m.methodSignature)) { @@ -54,7 +66,7 @@ class MemberExpression extends Expression { if (methods.size > 0) { return new MethodType([...methods.values()]); } - ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}' in type '${type.fullyDottedRawName}'`)); + ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}' in type '${instance.fullyDottedRawName}'`)); return AnyType.Instance; } diff --git a/langserver/java/expressiontypes/Variable.js b/langserver/java/expressiontypes/Variable.js index 1ab6823..2b70116 100644 --- a/langserver/java/expressiontypes/Variable.js +++ b/langserver/java/expressiontypes/Variable.js @@ -24,7 +24,7 @@ class Variable extends Expression { /** * @param {ResolveInfo} ri */ - resolveType(ri) { + resolveExpression(ri) { return this.type; } diff --git a/langserver/java/expressiontypes/literals/Instance.js b/langserver/java/expressiontypes/literals/Instance.js index 48ee091..8077a81 100644 --- a/langserver/java/expressiontypes/literals/Instance.js +++ b/langserver/java/expressiontypes/literals/Instance.js @@ -13,6 +13,7 @@ class InstanceLiteral extends LiteralValue { */ constructor(token, scoped_type) { super(token, null); + this.token = token; this.scoped_type = scoped_type; } diff --git a/langserver/java/type-resolver.js b/langserver/java/type-resolver.js index 31eddf5..bc33568 100644 --- a/langserver/java/type-resolver.js +++ b/langserver/java/type-resolver.js @@ -364,16 +364,11 @@ function resolveNextTypeOrPackage(ident, outer_types, outer_package_name, typema }) if (outer_package_name) { - const type_match = `${outer_package_name}/${ident}`; - if (typemap.has(type_match)) { - // it matches a type - types.push(typemap.get(type_match)); - } - const package_match = type_match + '/'; - if ([...typemap.keys()].find(fqn => fqn.startsWith(package_match))) { - // it matches a sub-package - package_name = type_match; + const { type, sub_package_name } = resolveNextPackage(outer_package_name, ident, typemap); + if (type) { + types.push(type); } + package_name = sub_package_name; } return { @@ -382,6 +377,27 @@ function resolveNextTypeOrPackage(ident, outer_types, outer_package_name, typema } } +/** + * + * @param {string} package_name + * @param {string} ident + * @param {TypeMap} typemap + */ +function resolveNextPackage(package_name, ident, typemap) { + let type = null, sub_package_name = ''; + const qualified_name = `${package_name}/${ident}`; + type = typemap.get(qualified_name) || null; + const package_match = qualified_name + '/'; + if ([...typemap.keys()].find(fqn => fqn.startsWith(package_match))) { + // it matches a sub-package + sub_package_name = qualified_name; + } + return { + type, + sub_package_name + } +} + module.exports = { parse_type, resolveType, @@ -390,4 +406,5 @@ module.exports = { ResolvedType, resolveTypeOrPackage, resolveNextTypeOrPackage, + resolveNextPackage, }