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);
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');

View File

@@ -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]) {

View File

@@ -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;