diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index 24b2639..b4e0647 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -1488,28 +1488,28 @@ function rootTerm(tokens, mdecls, scope, imports, typemap) { matches = resolveIdentifier(tokens, mdecls, scope, imports, typemap); break; case 'primitive-type': - matches = new ResolvedIdent(tokens.current.value, [], [], [PrimitiveType.fromName(tokens.current.value)]); + matches = new ResolvedIdent(tokens.current, [], [], [PrimitiveType.fromName(tokens.current.value)]); break; case 'string-literal': - matches = new ResolvedIdent(tokens.current.value, [new StringLiteral(tokens.current, typemap.get('java/lang/String'))]); + matches = new ResolvedIdent(tokens.current, [new StringLiteral(tokens.current, typemap.get('java/lang/String'))]); break; case 'char-literal': - matches = new ResolvedIdent(tokens.current.value, [new CharacterLiteral(tokens.current)]); + matches = new ResolvedIdent(tokens.current, [new CharacterLiteral(tokens.current)]); break; case 'boolean-literal': - matches = new ResolvedIdent(tokens.current.value, [new BooleanLiteral(tokens.current)]); + matches = new ResolvedIdent(tokens.current, [new BooleanLiteral(tokens.current)]); break; case 'object-literal': // this, super or null const scoped_type = scope instanceof SourceType ? scope : scope.owner; if (tokens.current.value === 'this' || tokens.current.value === 'super') { - matches = new ResolvedIdent(tokens.current.value, [new InstanceLiteral(tokens.current, scoped_type)]); + matches = new ResolvedIdent(tokens.current, [new InstanceLiteral(tokens.current, scoped_type)]); } else { - matches = new ResolvedIdent(tokens.current.value, [new NullLiteral(tokens.current)]); + matches = new ResolvedIdent(tokens.current, [new NullLiteral(tokens.current)]); } break; case /number-literal/.test(tokens.current.kind) && tokens.current.kind: - matches = new ResolvedIdent(tokens.current.value, [NumberLiteral.from(tokens.current)]); + matches = new ResolvedIdent(tokens.current, [NumberLiteral.from(tokens.current)]); break; case 'inc-operator': let incop = tokens.getIfKind('inc-operator'); diff --git a/langserver/java/body-types.js b/langserver/java/body-types.js index e59b220..4266c1e 100644 --- a/langserver/java/body-types.js +++ b/langserver/java/body-types.js @@ -9,7 +9,7 @@ const { AnyType, MethodType, TypeIdentType } = require('./anys'); class ResolvedIdent { /** - * @param {string} ident + * @param {string|Token} ident * @param {Expression[]} variables * @param {Method[]} methods * @param {JavaType[]} types @@ -17,13 +17,12 @@ class ResolvedIdent { * @param {Token[]} tokens */ constructor(ident, variables = [], methods = [], types = [], package_name = '', tokens = []) { - this.source = ident; + this.source = ident instanceof Token ? ident.value : ident; this.variables = variables; this.methods = methods; this.types = types; this.package_name = package_name; - /** @type {Token[]} */ - this.tokens = tokens; + this.tokens = ident instanceof Token ? [ident] : tokens; } /** @@ -34,7 +33,7 @@ class ResolvedIdent { if (this.variables[0]) { return this.variables[0].resolveExpression(ri); } - if (this.methods) { + if (this.methods[0]) { return new MethodType(this.methods); } if (this.types[0]) { diff --git a/langserver/java/expressiontypes/CastExpression.js b/langserver/java/expressiontypes/CastExpression.js index bd8d735..e01cdfe 100644 --- a/langserver/java/expressiontypes/CastExpression.js +++ b/langserver/java/expressiontypes/CastExpression.js @@ -1,9 +1,13 @@ /** * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolveInfo} ResolveInfo + * @typedef {import('../anys').ResolvedType} ResolvedType */ const { Expression } = require("./Expression"); const { AnyType, TypeIdentType } = require('../anys'); +const ParseProblem = require('../parsetypes/parse-problem'); +const { JavaType, PrimitiveType, NullType, CEIType, ArrayType } = require('java-mti'); +const { getTypeInheritanceList } = require('../expression-resolver'); class CastExpression extends Expression { /** @@ -22,8 +26,14 @@ class CastExpression extends Expression { resolveExpression(ri) { const cast_type = this.castType.resolveExpression(ri); if (cast_type instanceof TypeIdentType) { + const expr_type = this.expression.resolveExpression(ri); + checkCastable(this, cast_type.type, expr_type, ri.problems); return cast_type.type; } + if (cast_type instanceof AnyType) { + return cast_type; + } + ri.problems.push(ParseProblem.Error(this.castType.tokens, 'Type expected')) return AnyType.Instance; } @@ -32,4 +42,87 @@ class CastExpression extends Expression { } } +/** + * @param {CastExpression} cast + * @param {JavaType} cast_type + * @param {ResolvedType} expr_type + * @param {ParseProblem[]} problems + */ +function checkCastable(cast, cast_type, expr_type, problems) { + if (expr_type instanceof JavaType) { + if (!isTypeCastable(expr_type, cast_type)) { + problems.push(ParseProblem.Error(cast.expression.tokens, `Invalid cast: An expression of type '${expr_type.fullyDottedTypeName}' cannot be cast to type '${cast_type.fullyDottedTypeName}'`)); + } + return; + } + problems.push(ParseProblem.Error(cast.expression.tokens, `Invalid cast: expression is not a value or variable`)); +} + +/** + * @param {JavaType} source_type + * @param {JavaType} cast_type + */ +function isTypeCastable(source_type, cast_type) { + if (source_type.typeSignature === 'Ljava/lang/Object;') { + // everything is castable from Object + return true; + } + if (cast_type.typeSignature === 'Ljava/lang/Object;') { + // everything is castable to Object + return true; + } + if (source_type instanceof NullType) { + // null is castable to any non-primitive + return !(cast_type instanceof PrimitiveType); + } + if (source_type instanceof CEIType && cast_type instanceof CEIType) { + if (source_type.typeKind === 'interface') { + // interfaces are castable to any non-final class type (derived types might implement the interface) + if (cast_type.typeKind === 'class' && !cast_type.modifiers.includes('final')) { + return true; + } + } + // for other class casts, one type must be in the inheritence tree of the other + if (getTypeInheritanceList(source_type).includes(cast_type)) { + return true; + } + if (getTypeInheritanceList(cast_type).includes(source_type)) { + return true; + } + return false; + } + if (cast_type instanceof PrimitiveType) { + // source type must be a compatible primitive or class + switch (cast_type.typeSignature) { + case 'B': + case 'S': + case 'I': + case 'J': + case 'C': + case 'F': + case 'D': + return /^([BSIJCFD]|Ljava\/lang\/(Byte|Short|Integer|Long|Character|Float|Double);)$/.test(source_type.typeSignature); + case 'Z': + return /^([Z]|Ljava\/lang\/(Boolean);)$/.test(source_type.typeSignature); + } + return false; + } + if (cast_type instanceof ArrayType) { + // the source type must have the same array dimensionality and have a castable base type + if (source_type instanceof ArrayType) { + if (source_type.arrdims === cast_type.arrdims) { + if (isTypeCastable(source_type.base, cast_type.base)) { + return true; + } + } + } + } + + if (source_type instanceof AnyType || cast_type instanceof AnyType) { + return true; + } + + return false; +} + exports.CastExpression = CastExpression;