diff --git a/langserver/java/anys.js b/langserver/java/anys.js index ec7a13f..f2b4218 100644 --- a/langserver/java/anys.js +++ b/langserver/java/anys.js @@ -2,9 +2,9 @@ const { JavaType, Method } = require('java-mti'); const { Expression } = require('./expressiontypes/Expression'); /** - * AnyType is a special type that's used to fill in types that are missing. - * To prevent cascading errors, AnyType should be fully assign/cast/type-compatible - * with any other type + * Custom type designed to be used where a type is missing or unresolved. + * + * AnyType should be fully assign/cast/type-compatible with any other type */ class AnyType extends JavaType { /** @@ -27,6 +27,10 @@ class AnyType extends JavaType { } } +/** + * Custom method designed to be compatible with + * any arguments in method call + */ class AnyMethod extends Method { /** * @param {string} name @@ -40,6 +44,10 @@ class AnyMethod extends Method { } } +/** + * Custom expression designed to be compatiable with + * any variable or operator + */ class AnyValue extends Expression { /** * @@ -52,6 +60,81 @@ class AnyValue extends Expression { } } -exports.AnyMethod = AnyMethod; +/** + * Custom type used to represent a method identifier + * + * e.g `"".length` + */ +class MethodType { + /** + * @param {Method[]} methods + */ + constructor(methods) { + this.methods = methods; + } +} + +/** + * Custom type used to represent a lambda expression + * + * eg. `() => null` + */ +class LambdaType { + +} + +/** + * Custom type used to represent type name expressions + * + * eg. `x instanceof String` + */ +class TypeIdentType { + /** + * @param {JavaType} type + */ + constructor(type) { + this.type = type; + } +} + +/** + * Custom type used to represent an array literal + * + * eg. `new int[] { 1,2,3 }` + */ +class ArrayValueType { + /** + * @param {(ResolvedType)[]} element_types + */ + constructor(element_types) { + this.element_types = element_types; + } +} + +/** + * Custom type used to represent the types of a + * expression that can return multiple distinct types + * + * eg. `x == null ? 0 : 'c'` + */ +class MultiValueType { + /** + * @param {ResolvedType[]} types + */ + constructor(...types) { + this.types = types; + } +} + +/** + * @typedef {JavaType|MethodType|LambdaType|ArrayValueType|TypeIdentType|MultiValueType} ResolvedType + **/ + + exports.AnyMethod = AnyMethod; exports.AnyType = AnyType; exports.AnyValue = AnyValue; +exports.ArrayValueType = ArrayValueType; +exports.LambdaType = LambdaType; +exports.MethodType = MethodType; +exports.MultiValueType = MultiValueType; +exports.TypeIdentType = TypeIdentType; diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index a6efffc..24b2639 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -16,7 +16,7 @@ const { TokenList } = require("./TokenList"); const { AnyMethod, AnyType, AnyValue } = require("./anys"); const { Label, Local, MethodDeclarations, ResolvedIdent } = require("./body-types"); const { resolveImports, resolveSingleImport } = require('../java/import-resolver'); -const { getTypeInheritanceList } = require('./expression-resolver'); +const { checkAssignment, getTypeInheritanceList } = require('./expression-resolver'); const { ArrayIndexExpression } = require("./expressiontypes/ArrayIndexExpression"); const { ArrayValueExpression } = require("./expressiontypes/ArrayValueExpression"); @@ -212,9 +212,15 @@ function parse(source, typemap) { time('parse'); parseUnit(tokens, unit, typemap); timeEnd('parse'); + + // once all the types have been parsed, resolve any field initialisers + unit.types.forEach(t => { + t.fields.filter(f => f.init).forEach(f => checkAssignment(f.init, f.type, typemap, tokens.problems)); + }); + } catch(err) { timers.forEach(timeEnd); - if (tokens) { + if (tokens && tokens.current) { addproblem(tokens, ParseProblem.Error(tokens.current, `Parse failed: ${err.message}`)); } else { console.log(`Parse failed: ${err.message}`); @@ -1566,26 +1572,29 @@ function rootTerm(tokens, mdecls, scope, imports, typemap) { * @param {Map} typemap */ function newTerm(tokens, mdecls, scope, imports, typemap) { + tokens.mark(); const new_token = tokens.current; tokens.expectValue('new'); const ctr_type = typeIdent(tokens, scope, imports, typemap, {no_array_qualifiers:true, type_vars:[]}); let match = new ResolvedIdent(`new ${ctr_type.resolved.simpleTypeName}`, [], [], [ctr_type.resolved]); - let ctr_args = [], type_body = null; + let ctr_args = [], type_body = null, newtokens; switch(tokens.current.value) { case '[': match = arrayQualifiers(match, tokens, mdecls, scope, imports, typemap); + newtokens = tokens.markEnd(); // @ts-ignore if (tokens.current.value === '{') { // array init rootTerm(tokens, mdecls, scope, imports, typemap); } - return new ResolvedIdent(match.source, [new NewArray(new_token, ctr_type, match)]); + return new ResolvedIdent(match.source, [new NewArray(new_token, ctr_type, match)], [], [], '', newtokens); case '(': tokens.inc(); if (!tokens.isValue(')')) { ctr_args = expressionList(tokens, mdecls, scope, imports, typemap); tokens.expectValue(')'); } + newtokens = tokens.markEnd(); // @ts-ignore if (tokens.current.value === '{') { // anonymous type - just skip for now @@ -1593,10 +1602,11 @@ function newTerm(tokens, mdecls, scope, imports, typemap) { } break; default: + newtokens = tokens.markEnd(); addproblem(tokens, ParseProblem.Error(tokens.current, 'Constructor expression expected')); break; } - return new ResolvedIdent(match.source, [new NewObject(new_token, ctr_type, ctr_args, type_body)]); + return new ResolvedIdent(match.source, [new NewObject(new_token, ctr_type, ctr_args, type_body)], [], [], '', newtokens); } /** @@ -1679,6 +1689,7 @@ function qualifiers(matches, tokens, mdecls, scope, imports, typemap) { * @param {Map} typemap */ function memberQualifier(matches, tokens, mdecls, scope, imports, typemap) { + tokens.mark(); tokens.expectValue('.'); let expr, label = `${matches.source}.${tokens.current.value}`; let types = [], package_name = ''; @@ -1704,7 +1715,7 @@ function memberQualifier(matches, tokens, mdecls, scope, imports, typemap) { break; } tokens.inc(); - return new ResolvedIdent(label, [expr], [], types, package_name); + return new ResolvedIdent(label, [expr], [], types, package_name, tokens.markEnd()); } /** diff --git a/langserver/java/body-types.js b/langserver/java/body-types.js index c704195..e59b220 100644 --- a/langserver/java/body-types.js +++ b/langserver/java/body-types.js @@ -1,8 +1,11 @@ /** * @typedef {import('./expressiontypes/Expression').Expression} Expression + * @typedef {import('./anys').ResolvedType} ResolvedType */ -const { JavaType, ArrayType, Method, Parameter, Field } = require('java-mti'); +const { JavaType, CEIType, ArrayType, Method } = require('java-mti'); const { Token } = require('./tokenizer'); +const { AnyType, MethodType, TypeIdentType } = require('./anys'); + class ResolvedIdent { /** @@ -11,16 +14,35 @@ class ResolvedIdent { * @param {Method[]} methods * @param {JavaType[]} types * @param {string} package_name + * @param {Token[]} tokens */ - constructor(ident, variables = [], methods = [], types = [], package_name = '') { + constructor(ident, variables = [], methods = [], types = [], package_name = '', tokens = []) { this.source = ident; this.variables = variables; this.methods = methods; this.types = types; this.package_name = package_name; /** @type {Token[]} */ - this.tokens = []; + this.tokens = tokens; } + + /** + * @param {ResolveInfo} ri + * @returns {ResolvedType} + */ + resolveExpression(ri) { + if (this.variables[0]) { + return this.variables[0].resolveExpression(ri); + } + if (this.methods) { + return new MethodType(this.methods); + } + if (this.types[0]) { + return new TypeIdentType(this.types[0]); + } + return AnyType.Instance; + } + } class Local { @@ -79,7 +101,19 @@ class MethodDeclarations { } } +class ResolveInfo { + /** + * @param {Map} typemap + * @param {*[]} problems + */ + constructor(typemap, problems) { + this.typemap = typemap; + this.problems = problems; + } +} + exports.Label = Label; exports.Local = Local; exports.MethodDeclarations = MethodDeclarations; exports.ResolvedIdent = ResolvedIdent; +exports.ResolveInfo = ResolveInfo; diff --git a/langserver/java/expression-resolver.js b/langserver/java/expression-resolver.js index 64e746e..524f56c 100644 --- a/langserver/java/expression-resolver.js +++ b/langserver/java/expression-resolver.js @@ -3,8 +3,10 @@ */ const ParseProblem = require('./parsetypes/parse-problem'); const { TypeVariable, JavaType, PrimitiveType, NullType, ArrayType, CEIType, WildcardType, TypeVariableType, InferredTypeArgument } = require('java-mti'); -const { AnyType } = require('./anys'); -const { Local } = require('./body-types'); +const { AnyType, MultiValueType } = require('./anys'); +const { ResolveInfo } = require('./body-types'); +const { LiteralValue } = require('./expressiontypes/literals/LiteralValue'); +const { NumberLiteral } = require('./expressiontypes/literals/Number'); const { Expression } = require('./expressiontypes/Expression'); const { Variable } = require('./expressiontypes/Variable'); @@ -20,9 +22,19 @@ function checkAssignment(e, assign_type, typemap, problems) { checkTypeAssignable(assign_type, value.type, () => value.name_token, problems); return; } + if (value instanceof NumberLiteral) { + if (!value.isCompatibleWith(assign_type)) { + problems.push(ParseProblem.Error(value.token, `Incompatible types: Expression of type '${value.type.fullyDottedTypeName}' cannot be assigned to a variable of type '${assign_type.fullyDottedTypeName}'`)); + } + return; + } + if (value instanceof LiteralValue) { + checkTypeAssignable(assign_type, value.type, () => value.token, problems); + return; + } if (value instanceof Expression) { - const expression_result_type = null;//value.resolveType(typemap); - checkTypeAssignable(assign_type, expression_result_type, value.tokens, problems); + const expression_result_type = value.resolveExpression(new ResolveInfo(typemap, problems)); + checkTypeAssignable(assign_type, expression_result_type, () => value.tokens(), problems); return; } } @@ -30,13 +42,21 @@ function checkAssignment(e, assign_type, typemap, problems) { /** * * @param {JavaType} variable_type - * @param {JavaType} value_type + * @param {import('./anys').ResolvedType} value_type * @param {() => Token|Token[]} tokens */ function checkTypeAssignable(variable_type, value_type, tokens, problems) { + if (value_type instanceof MultiValueType) { + value_type.types.forEach(t => checkTypeAssignable(variable_type, t, tokens, problems)); + return; + } + if (!(value_type instanceof JavaType)) { + return; + } if (!isTypeAssignable(variable_type, value_type)) { const t = tokens(); - problems.push(ParseProblem.Error(t, `Incompatible types: Expression of type '${value_type.fullyDottedTypeName}' cannot be assigned to a variable of type '${variable_type.fullyDottedTypeName}'`)); + if (t.length || (t && !Array.isArray(t))) + problems.push(ParseProblem.Error(t, `Incompatible types: Expression of type '${value_type.fullyDottedTypeName}' cannot be assigned to a variable of type '${variable_type.fullyDottedTypeName}'`)); } } diff --git a/langserver/java/expressiontypes/ArrayIndexExpression.js b/langserver/java/expressiontypes/ArrayIndexExpression.js index 8002cd4..e06a62e 100644 --- a/langserver/java/expressiontypes/ArrayIndexExpression.js +++ b/langserver/java/expressiontypes/ArrayIndexExpression.js @@ -1,7 +1,10 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo */ const { Expression } = require("./Expression"); +const { ArrayType } = require('java-mti'); +const { AnyType } = require('../anys'); class ArrayIndexExpression extends Expression { /** @@ -17,6 +20,17 @@ class ArrayIndexExpression extends Expression { tokens() { return [...this.instance.tokens, ...this.index.tokens]; } + + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const instance_type = this.instance.resolveExpression(ri); + if (instance_type instanceof ArrayType) { + return instance_type.elementType; + } + return AnyType.Instance; + } } exports.ArrayIndexExpression = ArrayIndexExpression; diff --git a/langserver/java/expressiontypes/ArrayValueExpression.js b/langserver/java/expressiontypes/ArrayValueExpression.js index af1f5de..34344be 100644 --- a/langserver/java/expressiontypes/ArrayValueExpression.js +++ b/langserver/java/expressiontypes/ArrayValueExpression.js @@ -1,8 +1,10 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../tokenizer').Token} Token */ const { Expression } = require("./Expression"); +const { ArrayValueType } = require('../anys'); class ArrayValueExpression extends Expression { /** @@ -18,6 +20,13 @@ class ArrayValueExpression extends Expression { tokens() { return this.open; } + + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + return new ArrayValueType(this.elements.map(e => e.resolveExpression(ri))); + } } exports.ArrayValueExpression = ArrayValueExpression; diff --git a/langserver/java/expressiontypes/BinaryOpExpression.js b/langserver/java/expressiontypes/BinaryOpExpression.js index d263f42..a0518b2 100644 --- a/langserver/java/expressiontypes/BinaryOpExpression.js +++ b/langserver/java/expressiontypes/BinaryOpExpression.js @@ -1,8 +1,10 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../tokenizer').Token} Token */ const { Expression } = require("./Expression"); +const { JavaType, PrimitiveType } = require('java-mti'); class BinaryOpExpression extends Expression { /** @@ -17,6 +19,52 @@ class BinaryOpExpression extends Expression { this.rhs = rhs; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const operator = this.op.value; + const lhstype = this.lhs.resolveExpression(ri); + const rhstype = this.rhs.resolveExpression(ri); + if (operator === '+') { + const lhstypesig = lhstype instanceof JavaType && lhstype.typeSignature, + rhstypesig = rhstype instanceof JavaType && rhstype.typeSignature; + if (lhstypesig === 'Ljava/lang/String;') { + return lhstype; + } + if (lhstypesig === 'D' || rhstypesig === 'D') { + return PrimitiveType.map.D; + } + if (lhstypesig === 'F' || rhstypesig === 'F') { + return PrimitiveType.map.F; + } + if (lhstypesig === 'J' || rhstypesig === 'J') { + return PrimitiveType.map.J; + } + return PrimitiveType.map.I; + } + if (/^([*/%&|^+-]?=|<<=|>>>?=)$/.test(operator)) { + // result of assignments are lhs + return lhstype; + } + if (/^[*/%-]$/.test(operator)) { + // math operators + return PrimitiveType.map.I; + } + if (/^(<<|>>>?)$/.test(operator)) { + // shift operators + return PrimitiveType.map.I; + } + if (/^[&|^]$/.test(operator)) { + // bitwise or logical operators + return lhstype === PrimitiveType.map.Z ? lhstype : PrimitiveType.map.I; + } + if (operator === 'instanceof') { + } + // logical/comparison operators + return PrimitiveType.map.Z; + } + tokens() { return [...this.lhs.tokens, this.op, ...this.rhs.tokens]; } diff --git a/langserver/java/expressiontypes/BracketedExpression.js b/langserver/java/expressiontypes/BracketedExpression.js index e90caeb..7b25a66 100644 --- a/langserver/java/expressiontypes/BracketedExpression.js +++ b/langserver/java/expressiontypes/BracketedExpression.js @@ -1,5 +1,6 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo */ const { Expression } = require("./Expression"); @@ -12,6 +13,13 @@ class BracketedExpression extends Expression { this.expression = expression; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + return this.expression.resolveExpression(ri); + } + tokens() { return this.expression.tokens; } diff --git a/langserver/java/expressiontypes/CastExpression.js b/langserver/java/expressiontypes/CastExpression.js index 9431f0a..bd8d735 100644 --- a/langserver/java/expressiontypes/CastExpression.js +++ b/langserver/java/expressiontypes/CastExpression.js @@ -1,7 +1,9 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo */ const { Expression } = require("./Expression"); +const { AnyType, TypeIdentType } = require('../anys'); class CastExpression extends Expression { /** @@ -14,6 +16,17 @@ class CastExpression extends Expression { this.expression = expression; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const cast_type = this.castType.resolveExpression(ri); + if (cast_type instanceof TypeIdentType) { + return cast_type.type; + } + return AnyType.Instance; + } + tokens() { return [...this.castType.tokens, ...this.expression.tokens]; } diff --git a/langserver/java/expressiontypes/ClassMemberExpression.js b/langserver/java/expressiontypes/ClassMemberExpression.js index 5c0edb3..8ccd5f2 100644 --- a/langserver/java/expressiontypes/ClassMemberExpression.js +++ b/langserver/java/expressiontypes/ClassMemberExpression.js @@ -1,8 +1,10 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../tokenizer').Token} Token + * @typedef {import('../body-types').ResolveInfo} ResolveInfo */ const { Expression } = require("./Expression"); +const { AnyType } = require('../anys'); class ClassMemberExpression extends Expression { /** @@ -15,6 +17,15 @@ class ClassMemberExpression extends Expression { this.classToken = class_token; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const classType = ri.typemap.get('java/lang/Class'); + const type = this.instance.types[0]; + return classType.specialise([type || AnyType.Instance]); + } + tokens() { return this.classToken; } diff --git a/langserver/java/expressiontypes/Expression.js b/langserver/java/expressiontypes/Expression.js index e19eb48..b58a570 100644 --- a/langserver/java/expressiontypes/Expression.js +++ b/langserver/java/expressiontypes/Expression.js @@ -1,8 +1,21 @@ /** + * @typedef {import('java-mti').JavaType} JavaType + * @typedef {import('java-mti').CEIType} CEIType * @typedef {import('../tokenizer').Token} Token - */ + * @typedef {import('../body-types').ResolveInfo} ResolveInfo + * @typedef {import('../anys').ResolvedType} ResolvedType + */ class Expression { + + /** + * @param {ResolveInfo} ri + * @returns {ResolvedType} + */ + resolveExpression(ri) { + throw new Error('Expression.resolveType'); + } + /** @returns {Token|Token[]} */ tokens() { throw new Error('Expression.tokens'); diff --git a/langserver/java/expressiontypes/IncDecExpression.js b/langserver/java/expressiontypes/IncDecExpression.js index 0965847..a97d624 100644 --- a/langserver/java/expressiontypes/IncDecExpression.js +++ b/langserver/java/expressiontypes/IncDecExpression.js @@ -1,8 +1,11 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../tokenizer').Token} Token */ const { Expression } = require("./Expression"); +const { PrimitiveType } = require('java-mti'); +const { AnyType } = require('../anys'); class IncDecExpression extends Expression { /** @@ -17,6 +20,19 @@ class IncDecExpression extends Expression { this.which = which; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const type = this.expr.resolveExpression(ri); + if (type instanceof PrimitiveType) { + if (/^[BSIJFD]$/.test(type.typeSignature)) { + return type; + } + } + return AnyType.Instance; + } + tokens() { return this.operator; } diff --git a/langserver/java/expressiontypes/LambdaExpression.js b/langserver/java/expressiontypes/LambdaExpression.js index d60ceb8..c77e337 100644 --- a/langserver/java/expressiontypes/LambdaExpression.js +++ b/langserver/java/expressiontypes/LambdaExpression.js @@ -1,8 +1,10 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo */ const { Expression } = require("./Expression"); const { Block } = require('../statementtypes/Block'); +const { LambdaType } = require('../anys'); class LambdaExpression extends Expression { /** @@ -16,6 +18,13 @@ class LambdaExpression extends Expression { this.body = body; } + /** + * @param {ResolveInfo} ri + */ + resolveType(ri) { + return new LambdaType(); + } + tokens() { if (this.body instanceof Block) { return this.body.open; diff --git a/langserver/java/expressiontypes/MemberExpression.js b/langserver/java/expressiontypes/MemberExpression.js index b1b2a2b..5ec0b80 100644 --- a/langserver/java/expressiontypes/MemberExpression.js +++ b/langserver/java/expressiontypes/MemberExpression.js @@ -1,8 +1,12 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../tokenizer').Token} Token */ const { Expression } = require("./Expression"); +const { CEIType } = require('java-mti'); +const { AnyType, MethodType } = require('../anys'); +const { getTypeInheritanceList } = require('../expression-resolver'); class MemberExpression extends Expression { /** @@ -16,6 +20,34 @@ class MemberExpression extends Expression { this.member = member; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const type = this.instance.resolveExpression(ri); + if (!(type instanceof CEIType)) { + return AnyType.Instance; + } + const ident = this.member.value; + const field = type.fields.find(f => f.name === ident); + if (field) { + return field.type; + } + let methods = new Map(); + getTypeInheritanceList(type).forEach(type => { + type.methods.forEach(m => { + let msig; + if (m.name === ident && !methods.has(msig = m.methodSignature)) { + methods.set(msig, m); + } + }) + }); + if (methods.size > 0) { + return new MethodType([...methods.values()]); + } + return AnyType.Instance; + } + tokens() { return this.member; } diff --git a/langserver/java/expressiontypes/MethodCallExpression.js b/langserver/java/expressiontypes/MethodCallExpression.js index 24ee887..28c6bcc 100644 --- a/langserver/java/expressiontypes/MethodCallExpression.js +++ b/langserver/java/expressiontypes/MethodCallExpression.js @@ -1,7 +1,9 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo */ const { Expression } = require("./Expression"); +const { AnyType, MethodType } = require('../anys'); class MethodCallExpression extends Expression { /** @@ -14,6 +16,18 @@ class MethodCallExpression extends Expression { this.args = args; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const type = this.instance.resolveExpression(ri); + if (!(type instanceof MethodType)) { + return AnyType.Instance; + } + const arg_types = this.args.map(arg => arg.resolveExpression(ri)); + return type.methods[0].returnType; + } + tokens() { return this.instance.tokens; } diff --git a/langserver/java/expressiontypes/NewExpression.js b/langserver/java/expressiontypes/NewExpression.js index fc31cee..242212e 100644 --- a/langserver/java/expressiontypes/NewExpression.js +++ b/langserver/java/expressiontypes/NewExpression.js @@ -1,10 +1,12 @@ /** * @typedef {import('../tokenizer').Token} Token * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../source-types').SourceTypeIdent} SourceTypeIdent * @typedef {import('java-mti').JavaType} JavaType */ const { Expression } = require("./Expression"); +const { ArrayType } = require('java-mti'); class NewArray extends Expression { /** @@ -17,6 +19,14 @@ class NewArray extends Expression { this.new_token = new_token; this.element_type = element_type; this.dimensions = dimensions; + this.array_type = new ArrayType(element_type.resolved, 1); + } + + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + return this.array_type; } tokens() { @@ -39,6 +49,13 @@ class NewObject extends Expression { this.type_body = type_body; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + return this.object_type.resolved; + } + tokens() { return [this.new_token, ...this.object_type.tokens]; } diff --git a/langserver/java/expressiontypes/TernaryOpExpression.js b/langserver/java/expressiontypes/TernaryOpExpression.js index 69795c1..5347f0b 100644 --- a/langserver/java/expressiontypes/TernaryOpExpression.js +++ b/langserver/java/expressiontypes/TernaryOpExpression.js @@ -1,7 +1,9 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent - */ + * @typedef {import('../body-types').ResolveInfo} ResolveInfo +*/ const { Expression } = require("./Expression"); +const { MultiValueType } = require('../anys'); class TernaryOpExpression extends Expression { /** @@ -16,6 +18,15 @@ class TernaryOpExpression extends Expression { this.falseExpression = falseExpression; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + const ttype = this.truthExpression.resolveExpression(ri); + const ftype = this.falseExpression.resolveExpression(ri); + return new MultiValueType(ttype, ftype); + } + tokens() { return [...this.test.tokens, ...this.truthExpression.tokens, ...this.falseExpression.tokens]; } diff --git a/langserver/java/expressiontypes/ThisMemberExpression.js b/langserver/java/expressiontypes/ThisMemberExpression.js index 36cf204..d2e09be 100644 --- a/langserver/java/expressiontypes/ThisMemberExpression.js +++ b/langserver/java/expressiontypes/ThisMemberExpression.js @@ -1,8 +1,10 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent + * @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../tokenizer').Token} Token */ const { Expression } = require("./Expression"); +const { AnyType, TypeIdentType } = require('../anys'); class ThisMemberExpression extends Expression { /** @@ -15,6 +17,18 @@ class ThisMemberExpression extends Expression { this.thisToken = this_token; } + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + // instance should be a type identifier + const typeident = this.instance.resolveExpression(ri); + if (typeident instanceof TypeIdentType) { + return typeident.type; + } + return AnyType.Instance; + } + tokens() { return this.thisToken; } diff --git a/langserver/java/expressiontypes/Variable.js b/langserver/java/expressiontypes/Variable.js index 2a9a1f8..1ab6823 100644 --- a/langserver/java/expressiontypes/Variable.js +++ b/langserver/java/expressiontypes/Variable.js @@ -1,6 +1,7 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').Local} Local + * @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../tokenizer').Token} Token * @typedef {import('java-mti').Field} Field * @typedef {import('java-mti').Parameter} Parameter @@ -20,6 +21,13 @@ class Variable extends Expression { this.type = this.variable.type; } + /** + * @param {ResolveInfo} ri + */ + resolveType(ri) { + return this.type; + } + tokens() { return this.name_token; } diff --git a/langserver/java/expressiontypes/literals/Boolean.js b/langserver/java/expressiontypes/literals/Boolean.js index 61691a4..13d975a 100644 --- a/langserver/java/expressiontypes/literals/Boolean.js +++ b/langserver/java/expressiontypes/literals/Boolean.js @@ -10,8 +10,7 @@ class BooleanLiteral extends LiteralValue { * @param {Token} token */ constructor(token) { - super(token); - this.type = PrimitiveType.map.Z; + super(token, PrimitiveType.map.Z); } } diff --git a/langserver/java/expressiontypes/literals/Character.js b/langserver/java/expressiontypes/literals/Character.js index 03b4c4c..f404b7e 100644 --- a/langserver/java/expressiontypes/literals/Character.js +++ b/langserver/java/expressiontypes/literals/Character.js @@ -10,8 +10,7 @@ class CharacterLiteral extends LiteralValue { * @param {Token} token */ constructor(token) { - super(token); - this.type = PrimitiveType.map.C; + super(token, PrimitiveType.map.C); } } diff --git a/langserver/java/expressiontypes/literals/Instance.js b/langserver/java/expressiontypes/literals/Instance.js index d970730..48ee091 100644 --- a/langserver/java/expressiontypes/literals/Instance.js +++ b/langserver/java/expressiontypes/literals/Instance.js @@ -1,4 +1,5 @@ /** + * @typedef {import('../../body-types').ResolveInfo} ResolveInfo * @typedef {import('../../tokenizer').Token} Token * @typedef {import('java-mti').CEIType} CEIType */ @@ -11,9 +12,19 @@ class InstanceLiteral extends LiteralValue { * @param {CEIType} scoped_type */ constructor(token, scoped_type) { - super(token); + super(token, null); this.scoped_type = scoped_type; } + + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + if (this.token.value === 'this') { + return this.scoped_type; + } + return this.scoped_type.supers.find(t => t.typeKind === 'class') || ri.typemap.get('java/lang/Object'); + } } exports.InstanceLiteral = InstanceLiteral; diff --git a/langserver/java/expressiontypes/literals/LiteralValue.js b/langserver/java/expressiontypes/literals/LiteralValue.js index c94acd8..aef6421 100644 --- a/langserver/java/expressiontypes/literals/LiteralValue.js +++ b/langserver/java/expressiontypes/literals/LiteralValue.js @@ -1,4 +1,6 @@ /** + * @typedef {import('java-mti').JavaType} JavaType + * @typedef {import('../../body-types').ResolveInfo} ResolveInfo * @typedef {import('../../tokenizer').Token} Token */ const { Expression } = require('../Expression'); @@ -6,10 +8,19 @@ const { Expression } = require('../Expression'); class LiteralValue extends Expression { /** * @param {Token} token + * @param {JavaType} known_type */ - constructor(token) { + constructor(token, known_type) { super(); this.token = token; + this.type = known_type; + } + + /** + * @param {ResolveInfo} ri + */ + resolveExpression(ri) { + return this.type; } tokens() { diff --git a/langserver/java/expressiontypes/literals/Null.js b/langserver/java/expressiontypes/literals/Null.js index 0f8a79a..0940545 100644 --- a/langserver/java/expressiontypes/literals/Null.js +++ b/langserver/java/expressiontypes/literals/Null.js @@ -10,8 +10,7 @@ class NullLiteral extends LiteralValue { * @param {Token} token */ constructor(token) { - super(token); - this.type = new NullType(); + super(token, new NullType()); } } diff --git a/langserver/java/expressiontypes/literals/Number.js b/langserver/java/expressiontypes/literals/Number.js index 3eb3938..f7c2bf3 100644 --- a/langserver/java/expressiontypes/literals/Number.js +++ b/langserver/java/expressiontypes/literals/Number.js @@ -18,9 +18,8 @@ class NumberLiteral extends LiteralValue { * @param {PrimitiveType} default_type */ constructor(value, kind, default_type) { - super(value); + super(value, default_type); this.numberKind = kind; - this.type = default_type; } static shift(a, b, op) { diff --git a/langserver/java/expressiontypes/literals/String.js b/langserver/java/expressiontypes/literals/String.js index 4c25437..bd94286 100644 --- a/langserver/java/expressiontypes/literals/String.js +++ b/langserver/java/expressiontypes/literals/String.js @@ -11,8 +11,7 @@ class StringLiteral extends LiteralValue { * @param {CEIType} string_type */ constructor(token, string_type) { - super(token); - this.type = string_type; + super(token, string_type); } }