From a4be37d872a1f18ab16ec02fd9a63967ea7a7e56 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Sun, 7 Jun 2020 21:31:11 +0100 Subject: [PATCH] initial support for wildcard type arguments --- langserver/java/body-parser3.js | 199 +++++++++++++++++++++++++++----- 1 file changed, 172 insertions(+), 27 deletions(-) diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index 0cbeedf..f4a0399 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -4,7 +4,7 @@ * * Each token also contains detailed state information used for completion suggestions. */ -const { JavaType, CEIType, PrimitiveType, ArrayType, UnresolvedType, Field, Method, Parameter, Constructor, signatureToType } = require('java-mti'); +const { JavaType, CEIType, PrimitiveType, ArrayType, UnresolvedType, WildcardType, TypeVariable, Field, Method, Parameter, Constructor, signatureToType } = require('java-mti'); const { SourceMethod, SourceConstructor } = require('./source-type'); const ResolvedImport = require('./parsetypes/resolved-import'); const ParseProblem = require('./parsetypes/parse-problem'); @@ -1203,11 +1203,41 @@ function isTypeAssignable(dest_type, value_type) { // generic types are also assignable to their raw counterparts const valid_raw_types = valid_types.map(t => t.getRawType()); is_assignable = valid_raw_types.includes(dest_type); + if (!is_assignable) { + // generic types are also assignable to compatible wildcard type bounds + if (dest_type.rawTypeSignature === value_type.rawTypeSignature) { + is_assignable = dest_type.typevars.every((dest_tv, idx) => isTypeArgumentCompatible(dest_tv, value_type.typevars[idx].type)); + } + } } } return is_assignable; } +/** + * @param {TypeVariable} dest_typevar + * @param {JavaType} value_typevar_type + */ +function isTypeArgumentCompatible(dest_typevar, value_typevar_type) { + if (dest_typevar.type instanceof WildcardType) { + if (!dest_typevar.type.bound) { + // unbounded wildcard types are compatible with everything + return true; + } + if (dest_typevar.type.bound.type === value_typevar_type) { + return true; + } + switch (dest_typevar.type.bound.kind) { + case 'extends': + return isTypeAssignable(dest_typevar.type.bound.type, value_typevar_type); + case 'super':; + return isTypeAssignable(value_typevar_type, dest_typevar.type.bound.type); + } + return false; + } + return dest_typevar.type === value_typevar_type; +} + /** * @param {string} ident * @param {ResolvedIdent} lhs @@ -1221,6 +1251,119 @@ function resolveEquality(tokens, ident, lhs, op, rhs) { return new ResolvedIdent(ident, [Value.build(ident, lhs, rhs, PrimitiveType.map.Z)]); } +/** + * @param {JavaType} lhs_type + * @param {JavaType} rhs_type + */ +function isTypeComparable(lhs_type, rhs_type) { + let is_comparable; + if (lhs_type.typeSignature === rhs_type.typeSignature) { + is_comparable = true; + } else if (lhs_type instanceof AnyType || rhs_type instanceof AnyType) { + is_comparable = true; + } else if (lhs_type instanceof PrimitiveType) { + const valid_rhs_type = { + Z: /^Z$/, + V: /^$/, + }[lhs_type.typeSignature] || /^[BSIJFDC]$/; + is_comparable = valid_rhs_type.test(rhs_type.typeSignature); + } else if (lhs_type instanceof NullType || rhs_type instanceof NullType) { + is_comparable = !(rhs_type instanceof PrimitiveType); + } else if (lhs_type instanceof ArrayType) { + const base_type = lhs_type.base; + const valid_array_types = base_type instanceof CEIType ? getTypeInheritanceList(base_type) : [base_type]; + is_comparable = rhs_type.typeSignature === 'Ljava/lang/Object;' + || (rhs_type instanceof ArrayType + && rhs_type.arrdims === rhs_type.arrdims + && valid_array_types.includes(rhs_type)); + } else if (lhs_type instanceof CEIType && rhs_type instanceof CEIType) { + const lhs_types = getTypeInheritanceList(lhs_type); + const rhs_types = getTypeInheritanceList(rhs_type); + is_comparable = lhs_types.includes(rhs_type) || rhs_types.includes(lhs_type); + if (!is_comparable) { + if (lhs_type.rawTypeSignature === rhs_type.rawTypeSignature) { + is_comparable = lhs_type.typevars.every((tv, idx) => isTypeArgumentComparable(tv, rhs_type.typevars[idx])); + } + } + } + return is_comparable; +} + +/** + * @param {TypeVariable} a + * @param {TypeVariable} b + */ +function isTypeArgumentComparable(a, b) { + let a_type = a.type, b_type = b.type; + if (a_type === b_type) { + return true; + } + if (a_type instanceof WildcardType) { + if (!a_type.bound) + return true; // unbounded wildcard types are comparable with everything + if (a_type.bound.type.typeKind === 'interface') + return true; // interface bounds are comparable with everything + } + if (b_type instanceof WildcardType) { + if (!b_type.bound) + return true; // unbounded wildcard types are comparable with everything + if (b_type.bound.type.typeKind === 'interface') + return true; // interface bounds are comparable with everything + } + /** + * + * @param {JavaType} type + * @param {JavaType} list_type + */ + function extendsFrom(type, list_type) { + if (!(list_type instanceof CEIType)) { + return false; + } + return list_type === type || getTypeInheritanceList(list_type).includes(type); + } + // each type argument can have 3 possible states + // - a extends, a super, a (exact) + // - b extends, b super, b (exact) + // we need to cover all combinations of a and b... + if (a_type instanceof WildcardType && a_type.bound.kind === 'extends') { + if (b_type instanceof WildcardType && b_type.bound.kind === 'extends') { + // both are extends - one must extend from the other + return extendsFrom(a_type.bound.type, b_type.bound.type) || extendsFrom(b_type.bound.type, a_type.bound.type); + } + else if (b_type instanceof WildcardType && b_type.bound.kind === 'super') { + // a extends, b super - b must extend from a + return extendsFrom(a_type.bound.type, b_type.bound.type); + } else { + // b is an exact type - b must extend from a + return extendsFrom(a_type.bound.type, b_type); + } + } + else if (a_type instanceof WildcardType && a_type.bound.kind === 'super') { + if (b_type instanceof WildcardType && b_type.bound.kind === 'super') { + // both are super - one must extend from the other + return extendsFrom(a_type.bound.type, b_type.bound.type) || extendsFrom(b_type.bound.type, a_type.bound.type); + } + else if (b_type instanceof WildcardType && b_type.bound.kind === 'extends') { + // a super, b extends - a must extend from b + return extendsFrom(b_type.bound.type, a_type.bound.type); + } else { + // b is an exact type - a must extend from b + return extendsFrom(b_type, a_type.bound.type); + } + } else { + // a is an exact type + if (b_type instanceof WildcardType && b_type.bound.kind === 'extends') { + // a exact, b extends - a must extend from b + return extendsFrom(b_type.bound.type, a_type); + } + else if (b_type instanceof WildcardType && b_type.bound.kind === 'super') { + // a exact, b super - b must extend from a + return extendsFrom(a_type, b_type.bound.type); + } + } + return false; +} + /** * @param {TokenList} tokens * @param {Local|Parameter|Field|ArrayElement|Value} lhs @@ -1228,31 +1371,7 @@ function resolveEquality(tokens, ident, lhs, op, rhs) { * @param {Local|Parameter|Field|ArrayElement|Value} rhs */ function checkEqualityComparison(tokens, lhs, op, rhs) { - let is_comparable; - if (lhs.type.typeSignature === rhs.type.typeSignature) { - is_comparable = true; - } else if (lhs.type instanceof AnyType || rhs.type instanceof AnyType) { - is_comparable = true; - } else if (lhs.type instanceof PrimitiveType) { - const valid_rhs_type = { - Z: /^Z$/, - V: /^$/, - }[lhs.type.typeSignature] || /^[BSIJFDC]$/; - is_comparable = valid_rhs_type.test(rhs.type.typeSignature); - } else if (lhs.type instanceof NullType || rhs.type instanceof NullType) { - is_comparable = !(rhs.type instanceof PrimitiveType); - } else if (lhs.type instanceof ArrayType) { - const base_type = lhs.type.base; - const valid_array_types = base_type instanceof CEIType ? getTypeInheritanceList(base_type) : [base_type]; - is_comparable = rhs.type.typeSignature === 'Ljava/lang/Object;' - || (rhs.type instanceof ArrayType - && rhs.type.arrdims === rhs.type.arrdims - && valid_array_types.includes(rhs.type)); - } else if (lhs.type instanceof CEIType && rhs.type instanceof CEIType) { - const lhs_types = getTypeInheritanceList(lhs.type); - const rhs_types = getTypeInheritanceList(rhs.type); - is_comparable = lhs_types.includes(rhs.type) || rhs_types.includes(lhs.type); - } + const is_comparable = isTypeComparable(lhs.type, rhs.type); if (!is_comparable) { addproblem(tokens, ParseProblem.Error(op, `Incomparable types: '${lhs.type.fullyDottedTypeName}' and '${rhs.type.fullyDottedTypeName}'`)); } @@ -1649,7 +1768,10 @@ function typeIdentList(tokens, method, imports, typemap) { */ function typeIdent(tokens, method, imports, typemap) { if (tokens.current.kind !== 'ident') { - return new UnresolvedType(); + if (tokens.current.value === '?') { + return wildcardTypeArgument(tokens, method, imports, typemap); + } + return AnyType.Instance; } const { types, package_name } = resolveTypeOrPackage(tokens.current.value, method._owner, imports, typemap); let matches = new ResolvedIdent(tokens.current.value, [], [], types, package_name); @@ -1669,6 +1791,29 @@ function typeIdent(tokens, method, imports, typemap) { return matches.types[0] || new UnresolvedType(matches.source); } +/** + * @param {TokenList} tokens + * @param {SourceMC} method + * @param {ResolvedImport[]} imports + * @param {Map} typemap + */ +function wildcardTypeArgument(tokens, method, imports, typemap) { + tokens.expectValue('?'); + let bound = null; + switch (tokens.current.value) { + case 'extends': + case 'super': + const kind = tokens.current.value; + tokens.inc(); + bound = { + kind, + type: typeIdent(tokens, method, imports, typemap), + } + break; + } + return new WildcardType(bound); +} + /** * @param {TokenList} tokens * @param {Local[]} locals