mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 18:08:29 +00:00
add basic type checking of lambda expressions
This commit is contained in:
@@ -87,7 +87,15 @@ class MethodType {
|
|||||||
* eg. `() => null`
|
* eg. `() => null`
|
||||||
*/
|
*/
|
||||||
class LambdaType {
|
class LambdaType {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JavaType[]} param_types
|
||||||
|
* @param {ResolvedValue} return_type
|
||||||
|
*/
|
||||||
|
constructor(param_types, return_type) {
|
||||||
|
this.param_types = param_types;
|
||||||
|
this.return_type = return_type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
const ParseProblem = require('./parsetypes/parse-problem');
|
const ParseProblem = require('./parsetypes/parse-problem');
|
||||||
const { TypeVariable, JavaType, PrimitiveType, NullType, ArrayType, CEIType, WildcardType, TypeVariableType, InferredTypeArgument } = require('java-mti');
|
const { TypeVariable, JavaType, PrimitiveType, NullType, ArrayType, CEIType, WildcardType, TypeVariableType, InferredTypeArgument } = require('java-mti');
|
||||||
const { AnyType, ArrayValueType, MultiValueType } = require('./anys');
|
const { AnyType, ArrayValueType, LambdaType, MultiValueType } = require('./anys');
|
||||||
const { ResolveInfo } = require('./body-types');
|
const { ResolveInfo } = require('./body-types');
|
||||||
const { NumberLiteral } = require('./expressiontypes/literals/Number');
|
const { NumberLiteral } = require('./expressiontypes/literals/Number');
|
||||||
|
|
||||||
@@ -41,6 +41,10 @@ function checkTypeAssignable(variable_type, value, tokens, problems) {
|
|||||||
checkArrayLiteral(variable_type, value, tokens, problems);
|
checkArrayLiteral(variable_type, value, tokens, problems);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (value instanceof LambdaType) {
|
||||||
|
checkLambdaAssignable(variable_type, value, tokens, problems);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (value instanceof JavaType) {
|
if (value instanceof JavaType) {
|
||||||
if (!isTypeAssignable(variable_type, value)) {
|
if (!isTypeAssignable(variable_type, value)) {
|
||||||
incompatibleTypesError(variable_type, value, tokens, problems);
|
incompatibleTypesError(variable_type, value, tokens, problems);
|
||||||
@@ -61,6 +65,78 @@ function incompatibleTypesError(variable_type, value_type, tokens, problems) {
|
|||||||
problems.push(ParseProblem.Error(tokens(), `Incompatible types: Expression of type '${value_type.fullyDottedTypeName}' cannot be assigned to a variable of type '${variable_type.fullyDottedTypeName}'`));
|
problems.push(ParseProblem.Error(tokens(), `Incompatible types: Expression of type '${value_type.fullyDottedTypeName}' cannot be assigned to a variable of type '${variable_type.fullyDottedTypeName}'`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JavaType} variable_type
|
||||||
|
* @param {LambdaType} value
|
||||||
|
* @param {() => Token|Token[]} tokens
|
||||||
|
* @param {ParseProblem[]} problems
|
||||||
|
*/
|
||||||
|
function checkLambdaAssignable(variable_type, value, tokens, problems) {
|
||||||
|
const res = isLambdaAssignable(variable_type, value);
|
||||||
|
if (res === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (res[0]) {
|
||||||
|
case 'non-interface':
|
||||||
|
problems.push(ParseProblem.Error(tokens(), `Incompatible types: Cannot assign lambda expression to type '${variable_type.fullyDottedTypeName}'`));
|
||||||
|
return;
|
||||||
|
case 'no-methods':
|
||||||
|
problems.push(ParseProblem.Error(tokens(), `Incompatible types: Interface '${variable_type.fullyDottedTypeName}' contains no abstract methods compatible with the specified lambda expression`));
|
||||||
|
return;
|
||||||
|
case 'param-count':
|
||||||
|
problems.push(ParseProblem.Error(tokens(), `Incompatible types: Interface method '${variable_type.methods[0].label}' and lambda expression have different parameter counts`));
|
||||||
|
return;
|
||||||
|
case 'bad-param':
|
||||||
|
problems.push(ParseProblem.Error(tokens(), `Incompatible types: Interface method '${variable_type.methods[0].label}' and lambda expression have different parameter types`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JavaType} variable_type
|
||||||
|
* @param {LambdaType} value
|
||||||
|
*/
|
||||||
|
function isLambdaAssignable(variable_type, value) {
|
||||||
|
if (!(variable_type instanceof CEIType) || variable_type.typeKind !== 'interface') {
|
||||||
|
return ['non-interface'];
|
||||||
|
}
|
||||||
|
// the functional interface must only contain one abstract method excluding public Object methods
|
||||||
|
// and ignoring type-compatible methods from superinterfaces.
|
||||||
|
// this is quite complicated to calculate, so for now, just check against the most common case: a simple interface type with
|
||||||
|
// a single abstract method
|
||||||
|
if (variable_type.supers.length > 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (variable_type.methods.length === 0) {
|
||||||
|
return ['no-methods']
|
||||||
|
}
|
||||||
|
if (variable_type.methods.length > 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const intf_method = variable_type.methods[0];
|
||||||
|
const intf_params = intf_method.parameters;
|
||||||
|
if (intf_params.length !== value.param_types.length) {
|
||||||
|
return ['param-count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < intf_params.length; i++) {
|
||||||
|
// explicit parameter types must match exactly
|
||||||
|
if (value.param_types[i] instanceof AnyType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (intf_params[i].type instanceof AnyType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (intf_params[i].type.typeSignature !== value.param_types[i].typeSignature) {
|
||||||
|
return ['bad-param']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {JavaType} variable_type
|
* @param {JavaType} variable_type
|
||||||
@@ -168,7 +244,7 @@ const valid_primitive_types = {
|
|||||||
/**
|
/**
|
||||||
* Returns true if a value of value_type is assignable to a variable of dest_type
|
* Returns true if a value of value_type is assignable to a variable of dest_type
|
||||||
* @param {JavaType} dest_type
|
* @param {JavaType} dest_type
|
||||||
* @param {JavaType|NumberLiteral} value_type
|
* @param {JavaType|NumberLiteral|LambdaType} value_type
|
||||||
*/
|
*/
|
||||||
function isTypeAssignable(dest_type, value_type) {
|
function isTypeAssignable(dest_type, value_type) {
|
||||||
|
|
||||||
@@ -176,6 +252,10 @@ function isTypeAssignable(dest_type, value_type) {
|
|||||||
return value_type.isCompatibleWith(dest_type);
|
return value_type.isCompatibleWith(dest_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value_type instanceof LambdaType) {
|
||||||
|
return isLambdaAssignable(dest_type, value_type) === true;
|
||||||
|
}
|
||||||
|
|
||||||
let is_assignable = false;
|
let is_assignable = false;
|
||||||
if (dest_type.typeSignature === value_type.typeSignature) {
|
if (dest_type.typeSignature === value_type.typeSignature) {
|
||||||
// exact signature match
|
// exact signature match
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
const { Expression } = require("./Expression");
|
const { Expression } = require("./Expression");
|
||||||
const { Block } = require('../statementtypes/Block');
|
const { Block } = require('../statementtypes/Block');
|
||||||
const { LambdaType } = require('../anys');
|
const { AnyType, LambdaType } = require('../anys');
|
||||||
|
const { Local } = require('../body-types');
|
||||||
|
|
||||||
class LambdaExpression extends Expression {
|
class LambdaExpression extends Expression {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*[]} params
|
* @param {(Local|ResolvedIdent)[]} params
|
||||||
* @param {ResolvedIdent|Block} body
|
* @param {ResolvedIdent|Block} body
|
||||||
*/
|
*/
|
||||||
constructor(params, body) {
|
constructor(params, body) {
|
||||||
@@ -22,7 +23,21 @@ class LambdaExpression extends Expression {
|
|||||||
* @param {ResolveInfo} ri
|
* @param {ResolveInfo} ri
|
||||||
*/
|
*/
|
||||||
resolveExpression(ri) {
|
resolveExpression(ri) {
|
||||||
return new LambdaType();
|
let return_type;
|
||||||
|
if (this.body instanceof Block) {
|
||||||
|
// todo - search for return statements to work out what return value the lambda has
|
||||||
|
return_type = AnyType.Instance;
|
||||||
|
} else {
|
||||||
|
return_type = this.body.resolveExpression(ri);
|
||||||
|
}
|
||||||
|
const param_types = this.params.map(p => {
|
||||||
|
if (p instanceof Local) {
|
||||||
|
return p.type;
|
||||||
|
}
|
||||||
|
return AnyType.Instance;
|
||||||
|
})
|
||||||
|
return new LambdaType(param_types, return_type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens() {
|
tokens() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* @typedef {import('../tokenizer').Token} Token
|
* @typedef {import('../tokenizer').Token} Token
|
||||||
*/
|
*/
|
||||||
const { Expression } = require("./Expression");
|
const { Expression } = require("./Expression");
|
||||||
const { AnyType, AnyMethod, MethodType } = require('../anys');
|
const { AnyType, AnyMethod, LambdaType, MethodType } = require('../anys');
|
||||||
const { ArrayType, JavaType, Method,PrimitiveType, ReifiedConstructor, ReifiedMethod, Constructor } = require('java-mti');
|
const { ArrayType, JavaType, Method,PrimitiveType, ReifiedConstructor, ReifiedMethod, Constructor } = require('java-mti');
|
||||||
const { NumberLiteral } = require('./literals/Number');
|
const { NumberLiteral } = require('./literals/Number');
|
||||||
const { InstanceLiteral } = require('./literals/Instance')
|
const { InstanceLiteral } = require('./literals/Instance')
|
||||||
@@ -68,11 +68,11 @@ class MethodCallExpression extends Expression {
|
|||||||
function resolveMethodCall(ri, methods, args, tokens) {
|
function resolveMethodCall(ri, methods, args, tokens) {
|
||||||
const resolved_args = args.map(arg => arg.resolveExpression(ri));
|
const resolved_args = args.map(arg => arg.resolveExpression(ri));
|
||||||
|
|
||||||
// all the arguments must be typed expressions or number literals
|
// all the arguments must be typed expressions, number literals or lambdas
|
||||||
/** @type {(JavaType|NumberLiteral)[]} */
|
/** @type {(JavaType|NumberLiteral|LambdaType)[]} */
|
||||||
const arg_types = [];
|
const arg_types = [];
|
||||||
resolved_args.forEach((a, idx) => {
|
resolved_args.forEach((a, idx) => {
|
||||||
if (a instanceof JavaType || a instanceof NumberLiteral) {
|
if (a instanceof JavaType || a instanceof NumberLiteral || a instanceof LambdaType) {
|
||||||
arg_types.push(a);
|
arg_types.push(a);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,11 @@ function resolveMethodCall(ri, methods, args, tokens) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// reify any methods with type-variables
|
// reify any methods with type-variables
|
||||||
const arg_java_types = arg_types.map(a => a instanceof NumberLiteral ? a.type : a);
|
// - lambda expressions can't be used as type arguments so just pass them as void
|
||||||
|
const arg_java_types = arg_types.map(a =>
|
||||||
|
a instanceof NumberLiteral ? a.type
|
||||||
|
: a instanceof LambdaType ? PrimitiveType.map.V
|
||||||
|
: a);
|
||||||
const reified_methods = methods.map(m => {
|
const reified_methods = methods.map(m => {
|
||||||
if (m.typeVariables.length) {
|
if (m.typeVariables.length) {
|
||||||
m = ReifiedMethod.build(m, arg_java_types);
|
m = ReifiedMethod.build(m, arg_java_types);
|
||||||
@@ -142,11 +146,11 @@ function resolveMethodCall(ri, methods, args, tokens) {
|
|||||||
function resolveConstructorCall(ri, constructors, args, tokens) {
|
function resolveConstructorCall(ri, constructors, args, tokens) {
|
||||||
const resolved_args = args.map(arg => arg.resolveExpression(ri));
|
const resolved_args = args.map(arg => arg.resolveExpression(ri));
|
||||||
|
|
||||||
// all the arguments must be typed expressions or number literals
|
// all the arguments must be typed expressions, number literals or lambdas
|
||||||
/** @type {(JavaType|NumberLiteral)[]} */
|
/** @type {(JavaType|NumberLiteral|LambdaType)[]} */
|
||||||
const arg_types = [];
|
const arg_types = [];
|
||||||
resolved_args.forEach((a, idx) => {
|
resolved_args.forEach((a, idx) => {
|
||||||
if (a instanceof JavaType || a instanceof NumberLiteral) {
|
if (a instanceof JavaType || a instanceof NumberLiteral || a instanceof LambdaType) {
|
||||||
arg_types.push(a);
|
arg_types.push(a);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,7 +160,11 @@ function resolveConstructorCall(ri, constructors, args, tokens) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// reify any methods with type-variables
|
// reify any methods with type-variables
|
||||||
const arg_java_types = arg_types.map(a => a instanceof NumberLiteral ? a.type : a);
|
// - lambda expressions can't be used as type arguments so just pass them as void
|
||||||
|
const arg_java_types = arg_types.map(a =>
|
||||||
|
a instanceof NumberLiteral ? a.type
|
||||||
|
: a instanceof LambdaType ? PrimitiveType.map.V
|
||||||
|
: a);
|
||||||
const reifed_ctrs = constructors.map(c => {
|
const reifed_ctrs = constructors.map(c => {
|
||||||
if (c.typeVariables.length) {
|
if (c.typeVariables.length) {
|
||||||
c = ReifiedConstructor.build(c, arg_java_types);
|
c = ReifiedConstructor.build(c, arg_java_types);
|
||||||
@@ -206,7 +214,7 @@ function resolveConstructorCall(ri, constructors, args, tokens) {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Method|Constructor} m
|
* @param {Method|Constructor} m
|
||||||
* @param {(JavaType | NumberLiteral)[]} arg_types
|
* @param {(JavaType | NumberLiteral | LambdaType)[]} arg_types
|
||||||
*/
|
*/
|
||||||
function isCallCompatible(m, arg_types) {
|
function isCallCompatible(m, arg_types) {
|
||||||
if (m instanceof AnyMethod) {
|
if (m instanceof AnyMethod) {
|
||||||
|
|||||||
Reference in New Issue
Block a user