add type cast checking

This commit is contained in:
Dave Holoway
2020-06-18 12:24:16 +01:00
parent 2da127edc0
commit 0f296379c6
3 changed files with 104 additions and 12 deletions

View File

@@ -1488,28 +1488,28 @@ function rootTerm(tokens, mdecls, scope, imports, typemap) {
matches = resolveIdentifier(tokens, mdecls, scope, imports, typemap); matches = resolveIdentifier(tokens, mdecls, scope, imports, typemap);
break; break;
case 'primitive-type': 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; break;
case 'string-literal': 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; break;
case 'char-literal': case 'char-literal':
matches = new ResolvedIdent(tokens.current.value, [new CharacterLiteral(tokens.current)]); matches = new ResolvedIdent(tokens.current, [new CharacterLiteral(tokens.current)]);
break; break;
case 'boolean-literal': case 'boolean-literal':
matches = new ResolvedIdent(tokens.current.value, [new BooleanLiteral(tokens.current)]); matches = new ResolvedIdent(tokens.current, [new BooleanLiteral(tokens.current)]);
break; break;
case 'object-literal': case 'object-literal':
// this, super or null // this, super or null
const scoped_type = scope instanceof SourceType ? scope : scope.owner; const scoped_type = scope instanceof SourceType ? scope : scope.owner;
if (tokens.current.value === 'this' || tokens.current.value === 'super') { 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 { } else {
matches = new ResolvedIdent(tokens.current.value, [new NullLiteral(tokens.current)]); matches = new ResolvedIdent(tokens.current, [new NullLiteral(tokens.current)]);
} }
break; break;
case /number-literal/.test(tokens.current.kind) && tokens.current.kind: 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; break;
case 'inc-operator': case 'inc-operator':
let incop = tokens.getIfKind('inc-operator'); let incop = tokens.getIfKind('inc-operator');

View File

@@ -9,7 +9,7 @@ const { AnyType, MethodType, TypeIdentType } = require('./anys');
class ResolvedIdent { class ResolvedIdent {
/** /**
* @param {string} ident * @param {string|Token} ident
* @param {Expression[]} variables * @param {Expression[]} variables
* @param {Method[]} methods * @param {Method[]} methods
* @param {JavaType[]} types * @param {JavaType[]} types
@@ -17,13 +17,12 @@ class ResolvedIdent {
* @param {Token[]} tokens * @param {Token[]} tokens
*/ */
constructor(ident, variables = [], methods = [], types = [], package_name = '', tokens = []) { constructor(ident, variables = [], methods = [], types = [], package_name = '', tokens = []) {
this.source = ident; this.source = ident instanceof Token ? ident.value : ident;
this.variables = variables; this.variables = variables;
this.methods = methods; this.methods = methods;
this.types = types; this.types = types;
this.package_name = package_name; this.package_name = package_name;
/** @type {Token[]} */ this.tokens = ident instanceof Token ? [ident] : tokens;
this.tokens = tokens;
} }
/** /**
@@ -34,7 +33,7 @@ class ResolvedIdent {
if (this.variables[0]) { if (this.variables[0]) {
return this.variables[0].resolveExpression(ri); return this.variables[0].resolveExpression(ri);
} }
if (this.methods) { if (this.methods[0]) {
return new MethodType(this.methods); return new MethodType(this.methods);
} }
if (this.types[0]) { if (this.types[0]) {

View File

@@ -1,9 +1,13 @@
/** /**
* @typedef {import('../body-types').ResolvedIdent} ResolvedIdent * @typedef {import('../body-types').ResolvedIdent} ResolvedIdent
* @typedef {import('../body-types').ResolveInfo} ResolveInfo * @typedef {import('../body-types').ResolveInfo} ResolveInfo
* @typedef {import('../anys').ResolvedType} ResolvedType
*/ */
const { Expression } = require("./Expression"); const { Expression } = require("./Expression");
const { AnyType, TypeIdentType } = require('../anys'); 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 { class CastExpression extends Expression {
/** /**
@@ -22,8 +26,14 @@ class CastExpression extends Expression {
resolveExpression(ri) { resolveExpression(ri) {
const cast_type = this.castType.resolveExpression(ri); const cast_type = this.castType.resolveExpression(ri);
if (cast_type instanceof TypeIdentType) { 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; 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; 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; exports.CastExpression = CastExpression;