diff --git a/langserver/java/anys.js b/langserver/java/anys.js index ca40fd3..7b23800 100644 --- a/langserver/java/anys.js +++ b/langserver/java/anys.js @@ -1,5 +1,8 @@ const { JavaType, Method } = require('java-mti'); const { Expression } = require('./expressiontypes/Expression'); +/** + * @typedef {import('./tokenizer').Token} Token + */ /** * Custom type designed to be used where a type is missing or unresolved. @@ -104,10 +107,10 @@ class TypeIdentType { */ class ArrayValueType { /** - * @param {(ResolvedType)[]} element_types + * @param {{tokens:Token[], value: ResolvedType}[]} elements */ - constructor(element_types) { - this.element_types = element_types; + constructor(elements) { + this.elements = elements; } } diff --git a/langserver/java/expression-resolver.js b/langserver/java/expression-resolver.js index 7721181..5e17b18 100644 --- a/langserver/java/expression-resolver.js +++ b/langserver/java/expression-resolver.js @@ -1,9 +1,10 @@ /** * @typedef {import('./tokenizer').Token} Token + * @typedef {import('./anys').ResolvedType} ResolvedType */ const ParseProblem = require('./parsetypes/parse-problem'); const { TypeVariable, JavaType, PrimitiveType, NullType, ArrayType, CEIType, WildcardType, TypeVariableType, InferredTypeArgument } = require('java-mti'); -const { AnyType, MultiValueType } = require('./anys'); +const { AnyType, ArrayValueType, MultiValueType } = require('./anys'); const { ResolveInfo } = require('./body-types'); const { LiteralValue } = require('./expressiontypes/literals/LiteralValue'); const { NumberLiteral } = require('./expressiontypes/literals/Number'); @@ -24,7 +25,7 @@ function checkAssignment(e, assign_type, typemap, problems) { } 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}'`)); + incompatibleTypesError(assign_type, value.type, () => value.token, problems); } return; } @@ -44,21 +45,79 @@ function checkAssignment(e, assign_type, typemap, problems) { * @param {JavaType} variable_type * @param {import('./anys').ResolvedType} value_type * @param {() => Token|Token[]} tokens + * @param {ParseProblem[]} problems */ 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 ArrayValueType) { + checkArrayLiteral(variable_type, value_type, tokens, problems); + return; + } if (value_type instanceof JavaType) { if (!isTypeAssignable(variable_type, value_type)) { - problems.push(ParseProblem.Error(tokens(), `Incompatible types: Expression of type '${value_type.fullyDottedTypeName}' cannot be assigned to a variable of type '${variable_type.fullyDottedTypeName}'`)); + incompatibleTypesError(variable_type, value_type, tokens, problems); } return; } problems.push(ParseProblem.Error(tokens(), `Field, variable or method call expected`)); } +/** + * + * @param {JavaType} variable_type + * @param {JavaType} value_type + * @param {() => Token|Token[]} tokens + * @param {ParseProblem[]} problems + */ +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}'`)); +} + +/** + * + * @param {JavaType} variable_type + * @param {ArrayValueType} value_type + * @param {() => Token|Token[]} tokens + * @param {ParseProblem[]} problems + */ +function checkArrayLiteral(variable_type, value_type, tokens, problems) { + if (!(variable_type instanceof ArrayType)) { + problems.push(ParseProblem.Error(tokens(), `Array expression cannot be assigned to a variable of type '${variable_type.fullyDottedTypeName}'`)); + return; + } + if (value_type.elements.length === 0) { + // empty arrays are compatible with all array types + return; + } + const element_type = variable_type.elementType; + value_type.elements.forEach(element => { + checkArrayElement(element_type, element.value, element.tokens); + }); + + /** + * @param {JavaType} element_type + * @param {ResolvedType} value_type + * @param {Token[]} tokens + */ + function checkArrayElement(element_type, value_type, tokens) { + if (value_type instanceof JavaType) { + if (!isTypeAssignable(element_type, value_type)) { + incompatibleTypesError(element_type, value_type, () => tokens, problems); + } + return; + } + if (value_type instanceof ArrayValueType) { + checkArrayLiteral(element_type, value_type, () => tokens, problems); + return; + } + problems.push(ParseProblem.Error(tokens, `Expression expected`)); + } +} + + /** * Set of regexes to map source primitives to their destination types. * eg, long (J) is type-assignable to long, float and double (and their boxed counterparts) diff --git a/langserver/java/expressiontypes/ArrayValueExpression.js b/langserver/java/expressiontypes/ArrayValueExpression.js index 34344be..26ac74d 100644 --- a/langserver/java/expressiontypes/ArrayValueExpression.js +++ b/langserver/java/expressiontypes/ArrayValueExpression.js @@ -25,7 +25,10 @@ class ArrayValueExpression extends Expression { * @param {ResolveInfo} ri */ resolveExpression(ri) { - return new ArrayValueType(this.elements.map(e => e.resolveExpression(ri))); + return new ArrayValueType(this.elements.map(e => ({ + tokens: e.tokens, + value: e.resolveExpression(ri), + }))); } }