From 335a21659d0d057d602ce7b8ba40fcf98c63d914 Mon Sep 17 00:00:00 2001 From: mmews Date: Thu, 11 Jul 2024 10:30:46 +0200 Subject: [PATCH] migrate some --- .../typesystem/utils/SimplifyComputer.java | 286 +++++++ .../typesystem/utils/SimplifyComputer.xtend | 271 ------ .../utils/StructuralTypingComputer.java | 770 ++++++++++++++++++ .../utils/StructuralTypingComputer.xtend | 587 ------------- .../typesystem/utils/SubtypeComputer.java | 314 +++++++ .../typesystem/utils/SubtypeComputer.xtend | 309 ------- 6 files changed, 1370 insertions(+), 1167 deletions(-) create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SimplifyComputer.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SimplifyComputer.xtend create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/StructuralTypingComputer.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/StructuralTypingComputer.xtend create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SubtypeComputer.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SubtypeComputer.xtend diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SimplifyComputer.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SimplifyComputer.java new file mode 100644 index 0000000000..fc9b175d27 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SimplifyComputer.java @@ -0,0 +1,286 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.typesystem.utils; + +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.anyTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isAnyDynamic; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isBoolean; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isNumeric; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.isString; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.nullTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.objectTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.undefinedTypeRef; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.flatten; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.n4js.ts.typeRefs.ComposedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory; +import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression; +import org.eclipse.n4js.ts.typeRefs.UnknownTypeRef; +import org.eclipse.n4js.types.utils.TypeCompareHelper; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; + +import com.google.inject.Inject; + +/** + * Type System Helper Strategy for creating simplified composed types, i.e. union and intersection types. + */ +class SimplifyComputer extends TypeSystemHelperStrategy { + + private static final UnknownTypeRef UNKNOWN_TYPE_REF = TypeRefsFactory.eINSTANCE.createUnknownTypeRef(); + + @Inject + private N4JSTypeSystem ts; + @Inject + private TypeCompareHelper typeCompareHelper; + + /** + * Creates a simplified union type containing the given types; since it is simplified, the result is not necessarily + * a union type. The result may be contained in another container, so clients may have to use + * Ecore2.cloneIfNecessary(EObject). + */ + TypeRef createUnionType(RuleEnvironment G, TypeRef... elements) { + return simplify(G, TypeUtils.createNonSimplifiedUnionType(elements), true); + } + + /** + * Creates a simplified intersection type containing the given types; since it is simplified, the result is not + * necessarily an intersection type. The result may be contained in another container, so clients may have to use + * Ecore2.cloneIfNecessary(EObject). + */ + TypeRef createIntersectionType(RuleEnvironment G, TypeRef... elements) { + return simplify(G, TypeUtils.createNonSimplifiedIntersectionType(elements), true); + } + + /** + * Returns a simplified copy of a given composed type, i.e. union or intersection type. The returned type may be one + * of the elements, without cloning it. So clients need to clone the result if necessary. + * + * @apiNote [N4JS Spec], 4.13 Intersection Type + */ + TypeRef simplify(RuleEnvironment G, T composedType, boolean checkSubtypes) { + List typeRefs = getSimplifiedTypeRefs(G, composedType, checkSubtypes); + switch (typeRefs.size()) { + case 0: + return undefinedTypeRef(G); + case 1: + return typeRefs.get(0); + default: { + EClass eClass = composedType.eClass(); + ComposedTypeRef simplified = (ComposedTypeRef) EcoreUtil.create(eClass); + simplified.getTypeRefs().addAll(typeRefs); // note: typeRefs were already copied (if contained) + return simplified; + } + } + } + + private List getSimplifiedTypeRefs(RuleEnvironment G, T composedType, + boolean checkSubtypes) { + if (composedType == null) { + return null; + } + Iterable typeRefsFlattened = flattenComposedTypes(composedType.eClass(), composedType); + List typeRefsTrimmed = removeDuplicateAndTrivialTypes(G, typeRefsFlattened, composedType); + if (!checkSubtypes) { + return typeRefsTrimmed; + } + List typeRefsSimplified = simplifyBasedOnSubtypeRelations(G, typeRefsTrimmed, composedType); + return typeRefsSimplified; + } + + private Iterable flattenComposedTypes(EClass eClass, TypeRef typeRef) { + if (eClass.isInstance(typeRef)) { + EList typeRefs = ((ComposedTypeRef) typeRef).getTypeRefs(); + return flatten(map(typeRefs, it -> flattenComposedTypes(eClass, it))); + } else { + return Collections.singleton(typeRef); + } + } + + private List removeDuplicateAndTrivialTypes(RuleEnvironment G, Iterable typeRefs, + ComposedTypeRef composedType) { + // simplify cases related to the trivial types: any, Object, null, undefined + TypeRef anyTypeRef = anyTypeRef(G); + TypeRef objectTypeRef = objectTypeRef(G); + TypeRef nullTypeRef = nullTypeRef(G); + TypeRef undefinedTypeRef = undefinedTypeRef(G); + var haveAny = false; + var haveObject = false; + var haveNull = false; + var haveUndefined = false; + var haveUnknown = false; + + var haveNumeric = false; + var haveBoolean = false; + var haveString = false; + + // remove duplicates but keep original order + ArrayList noDups = new ArrayList<>(); + ArrayList noDupsWithoutObject = new ArrayList<>(); + { + Set set = new TreeSet<>(typeCompareHelper.getTypeRefComparator()); + for (TypeRef typeRef : typeRefs) { + if (!set.contains(typeRef)) { + boolean isAny = typeCompareHelper.isEqual(anyTypeRef, typeRef); + boolean isObject = typeCompareHelper.isEqual(objectTypeRef, typeRef); + boolean isNull = typeCompareHelper.isEqual(nullTypeRef, typeRef); + boolean isUndefined = typeCompareHelper.isEqual(undefinedTypeRef, typeRef); + boolean isUnknown = typeCompareHelper.isEqual(UNKNOWN_TYPE_REF, typeRef); + + boolean isNumeric = isNumeric(G, typeRef); + boolean isBoolean = isBoolean(G, typeRef); + boolean isString = isString(G, typeRef); + + if (isAny || isNull || isUndefined + || (haveNumeric && isNumeric) + || (haveBoolean && isBoolean) + || (haveString && isString)) { + // skip + } else { + set.add(typeRef); + noDups.add(typeRef); + if (!isObject) { + noDupsWithoutObject.add(typeRef); + } + } + + haveAny = haveAny || isAny; + haveObject = haveObject || isObject; + haveNull = haveNull || isNull; + haveUndefined = haveUndefined || isUndefined; + haveUnknown = haveUnknown || isUnknown; + haveNumeric = haveNumeric || isNumeric; + haveBoolean = haveBoolean || isBoolean; + haveString = haveString || isString; + } + } + } + boolean haveOthers = !noDupsWithoutObject.isEmpty(); + + if (composedType instanceof UnionTypeExpression) { + // in a union, subtypes of other elements can be thrown away + // if (haveUnknown) { + // return Collections.singletonList(UNKNOWN_TYPE_REF); + // } else + if (haveAny) { + return Collections.singletonList(anyTypeRef); + } else if (haveOthers) { + // proceed with others below ... + } else if (haveObject) { + return Collections.singletonList(objectTypeRef); + } else if (haveNull) { + return Collections.singletonList(nullTypeRef); + } else if (haveUndefined) { + return Collections.singletonList(undefinedTypeRef); + } else { + return Collections.emptyList(); + } + } else { + // in an intersection, super types of other elements can be thrown away + if (haveUndefined) { + return Collections.singletonList(undefinedTypeRef); + } else if (haveOthers) { + // proceed with others below ... + } else if (haveNull) { + return Collections.singletonList(nullTypeRef); + } else if (haveObject) { + return Collections.singletonList(objectTypeRef); + } else if (haveAny) { + return Collections.singletonList(anyTypeRef); + } else { + return Collections.emptyList(); + } + } + + List typeRefsCleaned = new ArrayList<>(noDups.size() + 2); + for (TypeRef e : noDups) { + TypeRef cpy = TypeUtils.copyIfContained(e); + typeRefsCleaned.add(cpy); + } + // NOTE: no need to add 'any' or 'null'/'undefined' here, because, if they were present, + // they have either rendered all other typeRefs obsolete (and we returned early above) OR + // they were rendered obsolete by the other typeRefs. The relation between 'null' and + // 'undefined' when not also having other type references was already handled above. + + return typeRefsCleaned; + } + + /** + * Simplifies the given typeRefs based on general subtype relations between them. For example, if class B inherits + * from class A, then union{A,B} can be simplified to A and intersection{A,B} can be simplified to B. However, for + * performance reasons, this is *ONLY DONE* if + *
    + *
  • there are only 2 typeRefs, and + *
  • there's no structural typing involved. + *
+ */ + private List simplifyBasedOnSubtypeRelations(RuleEnvironment G, List typeRefs, + ComposedTypeRef composedType) { + if (typeRefs.size() == 2) { + TypeRef fst = typeRefs.get(0); + TypeRef snd = typeRefs.get(1); + + boolean isFstStructural = fst.isUseSiteStructuralTyping() || fst.isDefSiteStructuralTyping(); + boolean isSndStructural = snd.isUseSiteStructuralTyping() || snd.isDefSiteStructuralTyping(); + boolean doSimplify = !isFstStructural && !isSndStructural; + + if (doSimplify) { + if (isAnyDynamic(G, fst)) { + if (composedType instanceof UnionTypeExpression) { + return Collections.singletonList(fst); // chose any+ + } else { + return Collections.singletonList(snd); // chose more concrete type + } + } + if (isAnyDynamic(G, snd)) { + if (composedType instanceof UnionTypeExpression) { + return Collections.singletonList(snd); // chose any+ + } else { + return Collections.singletonList(fst); // chose more concrete type + } + } + + boolean fstIsSubtype = ts.subtypeSucceeded(G, fst, snd); + boolean sndIsSubtype = ts.subtypeSucceeded(G, snd, fst); + if (fstIsSubtype && sndIsSubtype) { + // int/number, and others? + return Collections.singletonList(fst); + } + if (fstIsSubtype) { + if (composedType instanceof UnionTypeExpression) { + return Collections.singletonList(snd); // subtype can be thrown away + } else { + return Collections.singletonList(fst); // super type can be thrown away + } + } + if (sndIsSubtype) { + if (composedType instanceof UnionTypeExpression) { + return Collections.singletonList(fst); // subtype can be thrown away + } else { + return Collections.singletonList(snd); // super type can be thrown away + } + } + } + } + return typeRefs; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SimplifyComputer.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SimplifyComputer.xtend deleted file mode 100644 index 33b706973a..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SimplifyComputer.xtend +++ /dev/null @@ -1,271 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.typesystem.utils - -import com.google.inject.Inject -import java.util.ArrayList -import java.util.Collections -import java.util.List -import java.util.Set -import java.util.TreeSet -import org.eclipse.emf.ecore.EClass -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.n4js.ts.typeRefs.ComposedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRefsFactory -import org.eclipse.n4js.ts.typeRefs.UnionTypeExpression -import org.eclipse.n4js.ts.typeRefs.UnknownTypeRef -import org.eclipse.n4js.types.utils.TypeCompareHelper -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem - -import static extension java.util.Collections.* -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Type System Helper Strategy for creating simplified composed types, i.e. union - * and intersection types. - */ -package class SimplifyComputer extends TypeSystemHelperStrategy { - - private static final UnknownTypeRef UNKNOWN_TYPE_REF = TypeRefsFactory.eINSTANCE.createUnknownTypeRef; - - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeCompareHelper typeCompareHelper; - - /** - * Creates a simplified union type containing the given types; since it is simplified, the result is not necessarily a union type. - * The result may be contained in another container, so clients may have to use Ecore2.cloneIfNecessary(EObject). - */ - def TypeRef createUnionType(RuleEnvironment G, TypeRef... elements) { - simplify(G, TypeUtils.createNonSimplifiedUnionType(elements), true); - } - - /** - * Creates a simplified intersection type containing the given types; since it is simplified, the result is not necessarily an intersection type. - * The result may be contained in another container, so clients may have to use Ecore2.cloneIfNecessary(EObject). - */ - def TypeRef createIntersectionType(RuleEnvironment G, TypeRef... elements) { - simplify(G, TypeUtils.createNonSimplifiedIntersectionType(elements), true); - } - - /** - * Returns a simplified copy of a given composed type, i.e. union or intersection type. - * The returned type may be one of the elements, without cloning it. - * So clients need to clone the result if necessary. - * @see [N4JS Spec], 4.13 Intersection Type - */ - def TypeRef simplify(RuleEnvironment G, T composedType, boolean checkSubtypes) { - val List typeRefs = getSimplifiedTypeRefs(G, composedType, checkSubtypes); - switch (typeRefs.size()) { - case 0: - return G.undefinedTypeRef - case 1: - return typeRefs.head - default: { - val eClass = composedType.eClass; - val simplified = EcoreUtil.create(eClass) as ComposedTypeRef; - simplified.typeRefs.addAll(typeRefs); // note: typeRefs were already copied (if contained) - return simplified; - } - } - } - - def private List getSimplifiedTypeRefs(RuleEnvironment G, T composedType, boolean checkSubtypes) { - if (composedType === null) { - return null; - } - val typeRefsFlattened = flattenComposedTypes(composedType.eClass, composedType); - val typeRefsTrimmed = removeDuplicateAndTrivialTypes(G, typeRefsFlattened, composedType); - if (!checkSubtypes) { - return typeRefsTrimmed; - } - val typeRefsSimplified = simplifyBasedOnSubtypeRelations(G, typeRefsTrimmed, composedType); - return typeRefsSimplified; - } - - private def Iterable flattenComposedTypes(EClass eClass, TypeRef typeRef) { - if (eClass.isInstance(typeRef)) { - (typeRef as ComposedTypeRef).typeRefs.map[flattenComposedTypes(eClass, it)].flatten - } else { - typeRef.singleton() - }; - } - - private def List removeDuplicateAndTrivialTypes(RuleEnvironment G, Iterable typeRefs, ComposedTypeRef composedType) { - // simplify cases related to the trivial types: any, Object, null, undefined - val anyTypeRef = G.anyTypeRef; - val objectTypeRef = G.objectTypeRef; - val nullTypeRef = G.nullTypeRef; - val undefinedTypeRef = G.undefinedTypeRef; - var haveAny = false; - var haveObject = false; - var haveNull = false; - var haveUndefined = false; - var haveUnknown = false; - - var haveNumeric = false; - var haveBoolean = false; - var haveString = false; - - // remove duplicates but keep original order - val noDups = new ArrayList(); - val noDupsWithoutObject = new ArrayList(); - { - val Set set = new TreeSet(typeCompareHelper.getTypeRefComparator); - for (typeRef : typeRefs) { - if (!set.contains(typeRef)) { - val isAny = typeCompareHelper.isEqual(anyTypeRef, typeRef); - val isObject = typeCompareHelper.isEqual(objectTypeRef, typeRef); - val isNull = typeCompareHelper.isEqual(nullTypeRef, typeRef); - val isUndefined = typeCompareHelper.isEqual(undefinedTypeRef, typeRef); - val isUnknown = typeCompareHelper.isEqual(UNKNOWN_TYPE_REF, typeRef); - - val isNumeric = G.isNumeric(typeRef); - val isBoolean = G.isBoolean(typeRef); - val isString = G.isString(typeRef); - - if (isAny || isNull || isUndefined - || (haveNumeric && isNumeric) - || (haveBoolean && isBoolean) - || (haveString && isString) - ) { - // skip - } else { - set.add(typeRef); - noDups.add(typeRef); - if (!isObject) { - noDupsWithoutObject.add(typeRef); - } - } - - haveAny = haveAny || isAny; - haveObject = haveObject || isObject; - haveNull = haveNull || isNull; - haveUndefined = haveUndefined || isUndefined; - haveUnknown = haveUnknown || isUnknown; - haveNumeric = haveNumeric || isNumeric; - haveBoolean = haveBoolean || isBoolean; - haveString = haveString || isString; - } - } - } - val haveOthers = !noDupsWithoutObject.isEmpty; - - if (composedType instanceof UnionTypeExpression) { - // in a union, subtypes of other elements can be thrown away -// if (haveUnknown) { -// return Collections.singletonList(UNKNOWN_TYPE_REF); -// } else - if (haveAny) { - return Collections.singletonList(anyTypeRef); - } else if (haveOthers) { - // proceed with others below ... - } else if (haveObject) { - return Collections.singletonList(objectTypeRef); - } else if (haveNull) { - return Collections.singletonList(nullTypeRef); - } else if (haveUndefined) { - return Collections.singletonList(undefinedTypeRef); - } else { - return Collections.emptyList(); - } - } else { - // in an intersection, super types of other elements can be thrown away - if (haveUndefined) { - return Collections.singletonList(undefinedTypeRef); - } else if (haveOthers) { - // proceed with others below ... - } else if (haveNull) { - return Collections.singletonList(nullTypeRef); - } else if (haveObject) { - return Collections.singletonList(objectTypeRef); - } else if (haveAny) { - return Collections.singletonList(anyTypeRef); - } else { - return Collections.emptyList(); - } - } - - val List typeRefsCleaned = new ArrayList(noDups.size + 2); - for (e : noDups) { - val TypeRef cpy = TypeUtils.copyIfContained(e); - typeRefsCleaned.add(cpy); - } - // NOTE: no need to add 'any' or 'null'/'undefined' here, because, if they were present, - // they have either rendered all other typeRefs obsolete (and we returned early above) OR - // they were rendered obsolete by the other typeRefs. The relation between 'null' and - // 'undefined' when not also having other type references was already handled above. - - return typeRefsCleaned; - } - - /** - * Simplifies the given typeRefs based on general subtype relations between them. For example, - * if class B inherits from class A, then union{A,B} can be simplified to A and intersection{A,B} - * can be simplified to B. However, for performance reasons, this is *ONLY DONE* if - *
    - *
  • there are only 2 typeRefs, and - *
  • there's no structural typing involved. - *
- */ - private def List simplifyBasedOnSubtypeRelations(RuleEnvironment G, List typeRefs, ComposedTypeRef composedType) { - if (typeRefs.size === 2) { - val fst = typeRefs.get(0); - val snd = typeRefs.get(1); - - val isFstStructural = fst.isUseSiteStructuralTyping || fst.isDefSiteStructuralTyping; - val isSndStructural = snd.isUseSiteStructuralTyping || snd.isDefSiteStructuralTyping; - val doSimplify = !isFstStructural && !isSndStructural; - - if (doSimplify) { - if (G.isAnyDynamic(fst)) { - if (composedType instanceof UnionTypeExpression) { - return Collections.singletonList(fst); // chose any+ - } else { - return Collections.singletonList(snd); // chose more concrete type - } - } - if (G.isAnyDynamic(snd)) { - if (composedType instanceof UnionTypeExpression) { - return Collections.singletonList(snd); // chose any+ - } else { - return Collections.singletonList(fst); // chose more concrete type - } - } - - val fstIsSubtype = ts.subtypeSucceeded(G, fst, snd); - val sndIsSubtype = ts.subtypeSucceeded(G, snd, fst); - if (fstIsSubtype && sndIsSubtype) { - // int/number, and others? - return Collections.singletonList(fst); - } - if (fstIsSubtype) { - if (composedType instanceof UnionTypeExpression) { - return Collections.singletonList(snd); // subtype can be thrown away - } else { - return Collections.singletonList(fst); // super type can be thrown away - } - } - if (sndIsSubtype) { - if (composedType instanceof UnionTypeExpression) { - return Collections.singletonList(fst); // subtype can be thrown away - } else { - return Collections.singletonList(snd); // super type can be thrown away - } - } - } - } - return typeRefs; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/StructuralTypingComputer.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/StructuralTypingComputer.java new file mode 100644 index 0000000000..264d465782 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/StructuralTypingComputer.java @@ -0,0 +1,770 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.typesystem.utils; + +import static org.eclipse.n4js.ts.types.TypingStrategy.STRUCTURAL_FIELD_INITIALIZER; +import static org.eclipse.n4js.ts.types.TypingStrategy.STRUCTURAL_READ_ONLY_FIELDS; +import static org.eclipse.n4js.ts.types.TypingStrategy.STRUCTURAL_WRITE_ONLY_FIELDS; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.GUARD_STRUCTURAL_TYPING_COMPUTER__IN_PROGRESS_FOR_TYPE_REF; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.functionTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.numberTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.objectType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.stringTypeRef; +import static org.eclipse.n4js.typesystem.utils.StructuralTypingResult.failure; +import static org.eclipse.n4js.typesystem.utils.StructuralTypingResult.result; +import static org.eclipse.n4js.typesystem.utils.StructuralTypingResult.success; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isOptionalityLessRestrictedOrEqual; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isReadOnlyField; +import static org.eclipse.n4js.utils.N4JSLanguageUtils.isWriteableField; +import static org.eclipse.n4js.utils.StructuralMembersPredicates.GETTERS_PREDICATE; +import static org.eclipse.n4js.utils.StructuralMembersPredicates.READABLE_FIELDS_PREDICATE; +import static org.eclipse.n4js.utils.StructuralMembersPredicates.SETTERS_PREDICATE; +import static org.eclipse.n4js.utils.StructuralMembersPredicates.WRITABLE_FIELDS_PREDICATE; +import static org.eclipse.n4js.validation.IssueCodes.TYS_NO_SUBTYPE; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.util.EcoreUtil.EqualityHelper; +import org.eclipse.n4js.ts.typeRefs.OptionalFieldStrategy; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeArgument; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.FieldAccessor; +import org.eclipse.n4js.ts.types.PrimitiveType; +import org.eclipse.n4js.ts.types.TEnum; +import org.eclipse.n4js.ts.types.TField; +import org.eclipse.n4js.ts.types.TGetter; +import org.eclipse.n4js.ts.types.TMember; +import org.eclipse.n4js.ts.types.TMethod; +import org.eclipse.n4js.ts.types.TN4Classifier; +import org.eclipse.n4js.ts.types.TSetter; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypeVariable; +import org.eclipse.n4js.ts.types.TypingStrategy; +import org.eclipse.n4js.ts.types.util.Variance; +import org.eclipse.n4js.types.utils.TypeCompareUtils; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.constraints.TypeConstraint; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind; +import org.eclipse.n4js.utils.StructuralMembersTriple; +import org.eclipse.n4js.utils.StructuralMembersTripleIterator; +import org.eclipse.n4js.utils.StructuralTypesHelper; +import org.eclipse.n4js.validation.N4JSElementKeywordProvider; +import org.eclipse.xtext.xbase.lib.CollectionLiterals; +import org.eclipse.xtext.xbase.lib.Pair; +import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; + +import com.google.common.base.Optional; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + */ +@Singleton +public class StructuralTypingComputer extends TypeSystemHelperStrategy { + + @Inject + private N4JSTypeSystem ts; + @Inject + private N4JSElementKeywordProvider keywordProvider; + @Inject + private StructuralTypesHelper structuralTypesHelper; + + /***/ + public static class StructTypingInfo { + private final RuleEnvironment G; + + private final TypeRef left; + + private final TypeRef right; + + private final TypingStrategy leftStrategy; + + private final TypingStrategy rightStrategy; + + private final List missingMembers = CollectionLiterals. newArrayList(); + + private final List wrongMembers = CollectionLiterals. newArrayList(); + + /***/ + public StructTypingInfo(final RuleEnvironment G, final TypeRef left, final TypeRef right, + final TypingStrategy leftStrategy, final TypingStrategy rightStrategy) { + super(); + this.G = G; + this.left = left; + this.right = right; + this.leftStrategy = leftStrategy; + this.rightStrategy = rightStrategy; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.G == null) ? 0 : this.G.hashCode()); + result = prime * result + ((this.left == null) ? 0 : this.left.hashCode()); + result = prime * result + ((this.right == null) ? 0 : this.right.hashCode()); + result = prime * result + ((this.leftStrategy == null) ? 0 : this.leftStrategy.hashCode()); + result = prime * result + ((this.rightStrategy == null) ? 0 : this.rightStrategy.hashCode()); + result = prime * result + ((this.missingMembers == null) ? 0 : this.missingMembers.hashCode()); + return prime * result + ((this.wrongMembers == null) ? 0 : this.wrongMembers.hashCode()); + } + + @Override + + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + StructuralTypingComputer.StructTypingInfo other = (StructuralTypingComputer.StructTypingInfo) obj; + if (this.G == null) { + if (other.G != null) + return false; + } else if (!this.G.equals(other.G)) + return false; + if (this.left == null) { + if (other.left != null) + return false; + } else if (!this.left.equals(other.left)) + return false; + if (this.right == null) { + if (other.right != null) + return false; + } else if (!this.right.equals(other.right)) + return false; + if (this.leftStrategy == null) { + if (other.leftStrategy != null) + return false; + } else if (!this.leftStrategy.equals(other.leftStrategy)) + return false; + if (this.rightStrategy == null) { + if (other.rightStrategy != null) + return false; + } else if (!this.rightStrategy.equals(other.rightStrategy)) + return false; + if (this.missingMembers == null) { + if (other.missingMembers != null) + return false; + } else if (!this.missingMembers.equals(other.missingMembers)) + return false; + if (this.wrongMembers == null) { + if (other.wrongMembers != null) + return false; + } else if (!this.wrongMembers.equals(other.wrongMembers)) + return false; + return true; + } + + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + b.add("G", this.G); + b.add("left", this.left); + b.add("right", this.right); + b.add("leftStrategy", this.leftStrategy); + b.add("rightStrategy", this.rightStrategy); + b.add("missingMembers", this.missingMembers); + b.add("wrongMembers", this.wrongMembers); + return b.toString(); + } + + /***/ + public RuleEnvironment getG() { + return this.G; + } + + /***/ + public TypeRef getLeft() { + return this.left; + } + + /***/ + public TypeRef getRight() { + return this.right; + } + + /***/ + public TypingStrategy getLeftStrategy() { + return this.leftStrategy; + } + + /***/ + public TypingStrategy getRightStrategy() { + return this.rightStrategy; + } + + /***/ + public List getMissingMembers() { + return this.missingMembers; + } + + /***/ + public List getWrongMembers() { + return this.wrongMembers; + } + } + + /***/ + public StructuralTypingResult isStructuralSubtype(RuleEnvironment G, TypeRef left, TypeRef right) { + TypingStrategy leftStrategy = left.getTypingStrategy(); + TypingStrategy rightStrategy = right.getTypingStrategy(); + + // shortcut: keep in sync with Reducer#reduceStructuralTypeRef() + if (leftStrategy == rightStrategy + && left.getDeclaredType() == right.getDeclaredType() + && (left.getDeclaredType() instanceof TN4Classifier || left.getDeclaredType() instanceof TypeVariable) // <-- + // IDEBUG-838 + && left.getStructuralMembers().isEmpty() && right.getStructuralMembers().isEmpty()) { + + if (!left.isGeneric()) { + return result(left, right, Collections.emptyList(), Collections.emptyList()); + } else if (left instanceof ParameterizedTypeRef && right instanceof ParameterizedTypeRef) { + EList leftTypeArgs = left.getTypeArgsWithDefaults(); + EList rightTypeArgs = right.getTypeArgsWithDefaults(); + if (leftTypeArgs.size() == rightTypeArgs.size() + && leftTypeArgs.size() == right.getDeclaredType().getTypeVars().size()) { + var typeArgsEqual = true; + for (var i = 0; typeArgsEqual && i < leftTypeArgs.size(); i++) { + TypeArgument leftTypeArg = leftTypeArgs.get(i); + TypeArgument rightTypeArg = rightTypeArgs.get(i); + + Variance variance = right.getDeclaredType().getVarianceOfTypeVar(i); + Result tempResult = tsh.checkTypeArgumentCompatibility(G, leftTypeArg, rightTypeArg, + Optional.of(variance), false); + + if (tempResult.isFailure()) { + return failure(left.getTypeRefAsString() + " is not a structural subtype of " + + right.getTypeRefAsString() + " due to type argument incompatibility: " + + tempResult.getFailureMessage()); + } + } + return result(left, right, Collections.emptyList(), Collections.emptyList()); + } + } + } + + // check if we are dealing with structural primitive types + StructuralTypingResult primitiveSubtypingResult = isPrimitiveStructuralSubtype(G, left, right); + if (null != primitiveSubtypingResult) { + return primitiveSubtypingResult; + } + + // recursion guard (see method #isStructuralSubtypingInProgressFor() for details) + if (isStructuralSubtypingInProgressFor(G, left, right)) { + return result(left, right, Collections.emptyList(), Collections.emptyList()); + } + RuleEnvironment G2 = RuleEnvironmentExtensions.wrap(G); + rememberStructuralSubtypingInProgressFor(G2, left, right); + + StructTypingInfo info = new StructTypingInfo(G2, left, right, leftStrategy, rightStrategy); // we'll collect + // error messages in + // here + + StructuralMembersTripleIterator iter = structuralTypesHelper.getMembersTripleIterator(G2, left, right, true); + while (iter.hasNext()) { + // check if left member can fulfill the structural requirement imposed by right member + checkMembers(left, iter.next(), info); + } + + return result(left, right, info.missingMembers, info.wrongMembers); + } + + /** + * Special handling for primitive-structural types. + * + *

+ * Note that this method only returns a non-null {@link StructuralTypingResult} if primitive structural subtyping is + * applicable for the given operands {@code left} and {@code right}. + *

+ * + * @returns A {@link StructuralTypingResult} if primitive structural typing is applicable. {@code null} otherwise. + */ + StructuralTypingResult isPrimitiveStructuralSubtype(RuleEnvironment G, TypeRef leftRaw, TypeRef right) { + + // for the purpose of the rules implemented here, a number-/string-based enum behaves like type + // 'number'/'string' (lower-case) + TypeRef left = changeNumberOrStringBasedEnumToPrimitive(G, leftRaw); + + // check if we're dealing with structural primitive types + boolean rightIsPrimitive = right.getDeclaredType() instanceof PrimitiveType; + boolean leftIsPrimitive = left.getDeclaredType() instanceof PrimitiveType; + + // primitive type on the right and non-primitive on the left + if (rightIsPrimitive && !leftIsPrimitive) { + return failure(TYS_NO_SUBTYPE.getMessage(leftRaw.getTypeRefAsString(), right.getTypeRefAsString())); + } + // primitive type on the left and non-primitive on the right + else if (leftIsPrimitive && !rightIsPrimitive) { + return failure(TYS_NO_SUBTYPE.getMessage(leftRaw.getTypeRefAsString(), right.getTypeRefAsString())); + } + // primitive types on both sides + else if (leftIsPrimitive && rightIsPrimitive) { + // types must match nominally + if (left.getDeclaredType() == right.getDeclaredType()) { + return success(); + } else { + return failure(TYS_NO_SUBTYPE.getMessage(leftRaw.getTypeRefAsString(), right.getTypeRefAsString())); + } + } + // neither left nor right is primitive + else { + // shouldn't be handled by this method + return null; + } + } + + /** + * Replace type references pointing to the type of a @NumberBased / @StringBased enum by a + * reference to built-in type number / string, leaving all other types unchanged. + */ + private TypeRef changeNumberOrStringBasedEnumToPrimitive(RuleEnvironment G, TypeRef typeRef) { + Type declType = typeRef.getDeclaredType(); + if (declType instanceof TEnum) { + EnumKind enumKind = N4JSLanguageUtils.getEnumKind((TEnum) declType); + if (enumKind == EnumKind.NumberBased) { + return numberTypeRef(G); + } else if (enumKind == EnumKind.StringBased) { + return stringTypeRef(G); + } + } + return typeRef; + } + + private void checkMembers(TypeRef leftTypeRef, StructuralMembersTriple triple, StructTypingInfo info) { + TMember leftMember = triple.getLeft(); + TMember rightMember = triple.getRight(); + FieldAccessor leftOtherAccessor = triple.getLeftOtherAccessor(); + TypingStrategy leftStrategy = info.leftStrategy; + TypingStrategy rightStrategy = info.rightStrategy; + + checkMembers(leftTypeRef, leftMember, rightMember, info); + + switch (rightStrategy) { + + // For any ~r~ right members. + case STRUCTURAL_READ_ONLY_FIELDS: { + + // For any readable, non-optional right members. Initialized fields does not count as optional. + boolean handleOptionality = isOptionalityLessRestrictedOrEqual( + leftTypeRef.getASTNodeOptionalFieldStrategy(), OptionalFieldStrategy.GETTERS_OPTIONAL); + boolean memberNecessary = !rightMember.isOptional() || (rightMember.isOptional() && !handleOptionality); + if (memberNecessary && READABLE_FIELDS_PREDICATE.apply(rightMember)) { + + // For ~w~ left members requires an explicit getter. + if (STRUCTURAL_WRITE_ONLY_FIELDS == leftStrategy + && !GETTERS_PREDICATE.apply(leftMember) + && !GETTERS_PREDICATE.apply(leftOtherAccessor)) { + + info.wrongMembers + .add(rightMember.getName() + " failed: readable field requires a getter in subtype."); + + // Otherwise any readable field is enough. + } else if (!READABLE_FIELDS_PREDICATE.apply(leftMember) + && !READABLE_FIELDS_PREDICATE.apply(leftOtherAccessor)) { + info.wrongMembers.add(rightMember.getName() + + " failed: readable field requires a readable field or a getter in subtype."); + } + } + } + break; + // For any ~w~ right members. + case STRUCTURAL_WRITE_ONLY_FIELDS: { + + // For any writable right members. + if (WRITABLE_FIELDS_PREDICATE.apply(rightMember) && (leftTypeRef + .getASTNodeOptionalFieldStrategy() != OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL)) { + + // If left is either ~r~ or ~i~, then an explicit setter is required. + if ((STRUCTURAL_READ_ONLY_FIELDS == leftStrategy || STRUCTURAL_FIELD_INITIALIZER == leftStrategy) + && !SETTERS_PREDICATE.apply(leftMember) + && !SETTERS_PREDICATE.apply(leftOtherAccessor)) { + + info.wrongMembers + .add(rightMember.getName() + " failed: writable field requires a setter in subtype."); + + // Otherwise any writable field (data field or setter) is sufficient. + } else if (!WRITABLE_FIELDS_PREDICATE.apply(leftMember) + && !WRITABLE_FIELDS_PREDICATE.apply(leftOtherAccessor)) { + info.wrongMembers.add(rightMember.getName() + + " failed: writable field requires a writable field or a setter in subtype."); + } + } + } + break; + + // For any ~i~ right members. + case STRUCTURAL_FIELD_INITIALIZER: { + + // For any non-optional and writable right members. + // Important: unlike in case of ~r~, here we treat initialized fields, such as @Final ones as optional + // fields. + if (WRITABLE_FIELDS_PREDICATE.apply(rightMember) && TypeUtils.isMandatoryField(rightMember)) { + + // If left is ~w~, then getters are required. + if (STRUCTURAL_WRITE_ONLY_FIELDS == leftStrategy + && !(GETTERS_PREDICATE.apply(leftMember) || GETTERS_PREDICATE.apply(leftOtherAccessor))) { + + info.wrongMembers.add(rightMember.getName() + + " failed: non-optional writable field requires a getter in subtype."); + + // Otherwise any readable fields are fine. + } else if (!(READABLE_FIELDS_PREDICATE.apply(leftMember) + || READABLE_FIELDS_PREDICATE.apply(leftOtherAccessor))) { + info.wrongMembers.add(rightMember.getName() + + " failed: non-optional writable field requires a readable field or a getter in subtype."); + } + } + } + break; + + default: { + + // If the left member is ~r~, ~w~ and/or ~i~ type. + if (STRUCTURAL_READ_ONLY_FIELDS == leftStrategy || STRUCTURAL_WRITE_ONLY_FIELDS == leftStrategy + || STRUCTURAL_FIELD_INITIALIZER == leftStrategy) { + // If right is writable field, a getter/setter pair is mandatory on the left. + if (isWriteableField(rightMember)) { + if (!isGetterSetterPair(leftMember, leftOtherAccessor)) { + info.wrongMembers.add(rightMember.getName() + + " failed: writable field requires a getter/setter pair in subtype."); + } + } + // If right is readable, we require an explicit getter. + if (READABLE_FIELDS_PREDICATE.apply(rightMember)) { + if (!(GETTERS_PREDICATE.apply(leftMember) || GETTERS_PREDICATE.apply(leftOtherAccessor))) { + info.wrongMembers + .add(rightMember.getName() + " failed: read-only field requires a getter in subtype."); + } + } + // If there is a setter on the right, then we need a setter on the left. + if (SETTERS_PREDICATE.apply(rightMember)) { + if (!(SETTERS_PREDICATE.apply(leftMember) || SETTERS_PREDICATE.apply(leftOtherAccessor))) { + info.wrongMembers.add(rightMember.getName() + " failed: setter requires a setter in subtype."); + } + } + + } else { + // for a writable field on the right-hand side, require a getter/setter pair on the left + if (isWriteableField(rightMember) && leftMember instanceof FieldAccessor) { + if (!(leftOtherAccessor instanceof TSetter)) { + if (leftTypeRef + .getASTNodeOptionalFieldStrategy() != OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL) { + boolean isSpecialCaseOfDispensableGetterForOptionalField = leftMember instanceof TSetter + && rightMember.isOptional() + && (leftTypeRef + .getASTNodeOptionalFieldStrategy() == OptionalFieldStrategy.GETTERS_OPTIONAL); + if (!isSpecialCaseOfDispensableGetterForOptionalField) { + // special error message in case only either a getter XOR setter is supplied for a field + String msgSpecial = (leftMember instanceof TGetter && rightMember.isOptional()) + ? "optional writable field requires at least a setter in subtype." + : "writable field requires a field or a getter/setter pair in subtype."; + info.wrongMembers.add(rightMember.getName() + " failed: " + msgSpecial); + } + } + } else { + // check type of setter as usual + checkMembers(leftTypeRef, leftOtherAccessor, rightMember, info); + } + } + + } + } + } + } + + /** Returns with {@code true} iff the the arguments are a getter-setter accessor pair. */ + private boolean isGetterSetterPair(TMember firstLeft, TMember secondLeft) { + return (GETTERS_PREDICATE.apply(firstLeft) || GETTERS_PREDICATE.apply(secondLeft)) + && (SETTERS_PREDICATE.apply(secondLeft) || SETTERS_PREDICATE.apply(secondLeft)); + } + + /** + * Checks if the member 'left' (may be null if not found) fulfills the structural requirement + * represented by member 'right'. In the error case, the two lists of error messages in 'info' are updated + * accordingly. + *

+ * NOTE: in case of a field on right-hand side, this method accepts a getter OR(!) a setter of appropriate type on + * left side; the requirement that BOTH a getter AND setter must be provided for a writable field must be checked + * outside this method. + */ + private void checkMembers(TypeRef leftTypeRef, TMember left, TMember right, StructTypingInfo info) { + RuleEnvironment G = info.G; + + // !!! keep the following aligned with below method #reduceMembers() !!! + if (left == null) { + // no corresponding member found on left side + if (memberIsMissing(leftTypeRef, right, info)) { + + if (right instanceof TMethod) { + if (((TMethod) right).isCallSignature()) { + // in case a call signature is missing on the left side, it is ok + // if the left side is itself a function and is override-compatible: + tsh.addSubstitutions(G, info.right); + TypeRef rightTypeRef = ts.substTypeVariables(G, TypeUtils.createTypeRef((TMethod) right)); + Result result = ts.subtype(G, info.left, rightTypeRef); + if (result.isSuccess()) { + return; // success + } + if (ts.subtypeSucceeded(G, info.left, functionTypeRef(G))) { + info.wrongMembers.add(keywordProvider.keyword(right, info.rightStrategy) + " failed: " + + result.getFailureMessage()); + return; + } + } + + } else if (right instanceof TField) { + OptionalFieldStrategy leftOptionalStrategy = leftTypeRef.getASTNodeOptionalFieldStrategy(); + if (leftOptionalStrategy == OptionalFieldStrategy.GETTERS_OPTIONAL) { + info.missingMembers.add("setter or field " + right.getName()); + return; + } + } + + info.missingMembers.add(keywordProvider.keyword(right, info.rightStrategy) + " " + right.getName()); + } + + } else { + // found a corresponding member + // -> make sure types are compatible + + Variance variance; + if (left.isOptional() && !right.isOptional()) { + info.missingMembers.add(left.getName() + + " failed: non-optional member requires a corresponding non-optional member in the structural subtype."); + return; + } else if (isWriteableField(right) && left instanceof TField) { + // Fields are on both sides. + // T_FL = T_FR + if (isReadOnlyField(left) + && STRUCTURAL_FIELD_INITIALIZER != info.rightStrategy + && STRUCTURAL_READ_ONLY_FIELDS != info.rightStrategy) { + info.wrongMembers.add(right.getName() + " failed: field is read-only."); + return; + } else { + variance = Variance.INV; + } + } else if (right instanceof TSetter || left instanceof TSetter) { + // Setter on one side (other side may be field or setter). + // contra-variant + variance = Variance.CONTRA; + } else { + // Other cases such as methods and function expressions. + // Only L<:R. + variance = Variance.CO; + } + + Pair mtypes = getMemberTypes(left, right, info); + Result result = tsh.checkTypeArgumentCompatibility(G, mtypes.getKey(), mtypes.getValue(), + Optional.of(variance), true); + if (result.isFailure()) { + info.wrongMembers.add(getMemberName(right) + " failed: " + result.getFailureMessage()); + } + } + } + + /** + * Same as previous method, but instead of actually checking the types, we return 0..2 constraints. This would + * normally belong into class InferenceContext, but is placed here to keep it aligned with above method + * more easily. + */ + public List reduceMembers(RuleEnvironment G, + TypeRef leftTypeRef, TMember left, TMember right, StructTypingInfo info) { + + // !!! keep the following aligned with above method #checkMembers() !!! + if (left == null) { + // no corresponding member found on left side + if (memberIsMissing(leftTypeRef, right, info)) { + if (right instanceof TMethod) { + if (((TMethod) right).isCallSignature()) { + // in case a call signature is missing on the left side, it is ok + // if the left side is itself a function and is override-compatible: + tsh.addSubstitutions(G, info.right); + TypeRef rightTypeRef = ts.substTypeVariables(G, TypeUtils.createTypeRef((TMethod) right)); + return Collections.singletonList( + new TypeConstraint(info.left, rightTypeRef, Variance.CO)); + } + } + return Collections.singletonList(TypeConstraint.FALSE); + } else { + return Collections.emptyList(); // this is like returning TypeConstraint.TRUE + } + + } else { + + Variance variance; + if (left.isOptional() && !right.isOptional()) { + return Collections.singletonList(TypeConstraint.FALSE); + } else if (isWriteableField(right) && left instanceof TField) { + if (isReadOnlyField(left) + && STRUCTURAL_FIELD_INITIALIZER != info.rightStrategy + && STRUCTURAL_READ_ONLY_FIELDS != info.rightStrategy) { + return Collections.singletonList(TypeConstraint.FALSE); + } else { + variance = Variance.INV; + } + } else if (right instanceof TSetter || left instanceof TSetter) { + variance = Variance.CONTRA; + } else { + variance = Variance.CO; + } + Pair mtypes = getMemberTypes(left, right, info); + return tsh.reduceTypeArgumentCompatibilityCheck(G, mtypes.getKey(), mtypes.getValue(), + Optional.of(variance), false); + } + } + + private boolean memberIsMissing(TypeRef leftTypeRef, TMember right, StructTypingInfo info) { + boolean rightMemberIsOptional = rightMemberIsOptional(leftTypeRef, right, info.rightStrategy); + if (rightMemberIsOptional) { + // nothing to do (rightMember is optional or initialized for ~i~ typing.) + return false; + } else if (right != null && right.getContainingType() == objectType(info.G)) { + // ignore object members, can only happen if left is structural field type + return false; + } else { + // standard case of missing member on left side -> report error + return true; + } + } + + /** + * This method implements Req-IDE-240500 in language spec. + */ + private boolean rightMemberIsOptional(TypeRef leftTypeRef, TMember right, TypingStrategy rightStrategy) { + OptionalFieldStrategy leftOptionalStrategy = leftTypeRef.getASTNodeOptionalFieldStrategy(); + + // Whether a right member is optional depends on both the right typing strategy and the right optionality field + // strategy. + switch (rightStrategy) { + case STRUCTURAL: + case STRUCTURAL_FIELDS: { + // L <: ~N or L <: ~~N + if (!right.isOptional()) { + return false; + } else { + return (leftOptionalStrategy == OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL) || + (right instanceof TGetter && isOptionalityLessRestrictedOrEqual(leftOptionalStrategy, + OptionalFieldStrategy.GETTERS_OPTIONAL)); + } + } + + case STRUCTURAL_WRITE_ONLY_FIELDS: { + // L <: ~w~N + return right.isOptional() && (leftOptionalStrategy == OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL); + } + + case STRUCTURAL_READ_ONLY_FIELDS: { + // L <: ~r~N + return right.isOptional() + && isOptionalityLessRestrictedOrEqual(leftOptionalStrategy, OptionalFieldStrategy.GETTERS_OPTIONAL); + } + + case STRUCTURAL_FIELD_INITIALIZER: { + // L <: ~i~N + return (right.isOptional() + && isOptionalityLessRestrictedOrEqual(leftOptionalStrategy, OptionalFieldStrategy.GETTERS_OPTIONAL)) + || + TypeUtils.isInitializedField(right) || TypeUtils.isOptionalSetter(right); + } + + default: { + return false; + } + } + } + + /** + * Store a guard in the given rule environment to note that we are in the process of inferring left ~<: right. + *

+ * IDEBUG-171: If we are already computing the structural subtype relation left ~<: right and we are again asked + * whether left ~<: right, then we simply want to return success. The rationale is that if we run into a cycle while + * checking the members' types, we can simply say the members causing the cycle won't render the overall evaluation + * false ("an uns soll es nicht scheitern"). + *

+ * EXAMPLE 1: + * + *

+	 * class Element {
+	 * 	public ~Element child;
+	 * }
+	 * var ~Element e1;
+	 * var ~Element e2;
+	 * //e1 = e2;   // remove comment to get stack overflow when disabling if-clause in #isSubtype()
+	 * 
+ *

+ * EXAMPLE 2: + * + *

+	 * class A {
+	 * 	public ~B f;
+	 * }
+	 * class B {
+	 * 	public ~A f;
+	 * }
+	 * var ~A a;
+	 * var ~B b;
+	 * //a = b;   // remove comment to get stack overflow when disabling if-clause in #isSubtype()
+	 * 
+ * + * Note how this is analogous to what EMF is doing when computing structural equality as explained in the paragraph + * on "populating a two way map" in the following EMF API documentation: {@link EqualityHelper} + */ + private void rememberStructuralSubtypingInProgressFor(RuleEnvironment G, TypeRef left, TypeRef right) { + G.put(Pair.of(GUARD_STRUCTURAL_TYPING_COMPUTER__IN_PROGRESS_FOR_TYPE_REF, (Pair.of(wrap(left), wrap(right)))), + Boolean.TRUE); + } + + private boolean isStructuralSubtypingInProgressFor(RuleEnvironment G, TypeRef left, TypeRef right) { + return G.get(Pair.of(GUARD_STRUCTURAL_TYPING_COMPUTER__IN_PROGRESS_FOR_TYPE_REF, + (Pair.of(wrap(left), wrap(right))))) != null; + } + + private TypeCompareUtils.SemanticEqualsWrapper wrap(TypeRef typeRef) { + return new TypeCompareUtils.SemanticEqualsWrapper(typeRef); + } + + private Pair getMemberTypes(TMember leftMember, TMember rightMember, + StructTypingInfo info) { + + RuleEnvironment G = info.G; + TypeRef typeLeftRaw = ts.type(G, leftMember); + TypeRef typeRightRaw = ts.type(G, rightMember); + + // replace bound type variables with type arguments + RuleEnvironment G_left = RuleEnvironmentExtensions.wrap(G); + RuleEnvironment G_right = RuleEnvironmentExtensions.wrap(G); + tsh.addSubstitutions(G_left, info.left); + tsh.addSubstitutions(G_right, info.right); + + TypeRef typeLeft = ts.substTypeVariables(G_left, typeLeftRaw); + TypeRef typeRight = ts.substTypeVariables(G_right, typeRightRaw); + + return Pair.of(typeLeft, typeRight); + } + + private String getMemberName(TMember member) { + if (member instanceof TMethod) { + if (((TMethod) member).isCallSignature()) { + return "call signature"; + } else if (((TMethod) member).isConstructSignature()) { + return "construct signature"; + } + } + return member.getName(); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/StructuralTypingComputer.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/StructuralTypingComputer.xtend deleted file mode 100644 index 5adde4c0ee..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/StructuralTypingComputer.xtend +++ /dev/null @@ -1,587 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.typesystem.utils - -import com.google.common.base.Optional -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.Collections -import java.util.List -import org.eclipse.emf.ecore.util.EcoreUtil.EqualityHelper -import org.eclipse.n4js.ts.typeRefs.OptionalFieldStrategy -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeArgument -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.FieldAccessor -import org.eclipse.n4js.ts.types.PrimitiveType -import org.eclipse.n4js.ts.types.TEnum -import org.eclipse.n4js.ts.types.TField -import org.eclipse.n4js.ts.types.TGetter -import org.eclipse.n4js.ts.types.TMember -import org.eclipse.n4js.ts.types.TMethod -import org.eclipse.n4js.ts.types.TN4Classifier -import org.eclipse.n4js.ts.types.TSetter -import org.eclipse.n4js.ts.types.TypeVariable -import org.eclipse.n4js.ts.types.TypingStrategy -import org.eclipse.n4js.ts.types.util.Variance -import org.eclipse.n4js.types.utils.TypeCompareUtils -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.constraints.TypeConstraint -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.N4JSLanguageUtils.EnumKind -import org.eclipse.n4js.utils.StructuralMembersTriple -import org.eclipse.n4js.utils.StructuralTypesHelper -import org.eclipse.n4js.validation.N4JSElementKeywordProvider -import org.eclipse.xtend.lib.annotations.Data - -import static org.eclipse.n4js.ts.types.TypingStrategy.* -import static org.eclipse.n4js.typesystem.utils.StructuralTypingResult.* -import static org.eclipse.n4js.utils.StructuralMembersPredicates.* -import static org.eclipse.n4js.validation.IssueCodes.TYS_NO_SUBTYPE - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* -import static extension org.eclipse.n4js.utils.N4JSLanguageUtils.* - -/** - */ -@Singleton -class StructuralTypingComputer extends TypeSystemHelperStrategy { - - @Inject private N4JSTypeSystem ts; - @Inject private N4JSElementKeywordProvider keywordProvider; - @Inject private StructuralTypesHelper structuralTypesHelper; - - @Data - public static class StructTypingInfo { - val RuleEnvironment G; - val TypeRef left; - val TypeRef right; - val TypingStrategy leftStrategy; - val TypingStrategy rightStrategy; - val List missingMembers = newArrayList(); - val List wrongMembers = newArrayList(); - } - - public def StructuralTypingResult isStructuralSubtype(RuleEnvironment G, TypeRef left, TypeRef right) { - val leftStrategy = left.typingStrategy; - val rightStrategy = right.typingStrategy; - - // shortcut: keep in sync with Reducer#reduceStructuralTypeRef() - if (leftStrategy === rightStrategy - && left.declaredType === right.declaredType - && (left.declaredType instanceof TN4Classifier || left.declaredType instanceof TypeVariable) // <-- IDEBUG-838 - && left.structuralMembers.empty && right.structuralMembers.empty) { - - if (!left.generic) { - return result(left, right, emptyList, emptyList); - } else if (left instanceof ParameterizedTypeRef && right instanceof ParameterizedTypeRef) { - val leftTypeArgs = left.typeArgsWithDefaults; - val rightTypeArgs = right.typeArgsWithDefaults; - if (leftTypeArgs.size === rightTypeArgs.size && leftTypeArgs.size === right.declaredType.typeVars.size) { - var typeArgsEqual = true; - for (var i = 0; typeArgsEqual && i < leftTypeArgs.size; i++) { - val leftTypeArg = leftTypeArgs.get(i); - val rightTypeArg = rightTypeArgs.get(i); - - val variance = right.declaredType.getVarianceOfTypeVar(i); - val tempResult = tsh.checkTypeArgumentCompatibility(G, leftTypeArg, rightTypeArg, Optional.of(variance), false); - - if (tempResult.failure) { - return failure(left.typeRefAsString + " is not a structural subtype of " + right.typeRefAsString + " due to type argument incompatibility: " + tempResult.failureMessage); - } - } - return result(left, right, emptyList, emptyList); - } - } - } - - // check if we are dealing with structural primitive types - val primitiveSubtypingResult = isPrimitiveStructuralSubtype(G, left, right); - if (null !== primitiveSubtypingResult) { - return primitiveSubtypingResult; - } - - // recursion guard (see method #isStructuralSubtypingInProgressFor() for details) - if (G.isStructuralSubtypingInProgressFor(left, right)) { - return result(left, right, emptyList, emptyList); - } - val G2 = G.wrap; - G2.rememberStructuralSubtypingInProgressFor(left, right); - - val info = new StructTypingInfo(G2, left, right, leftStrategy, rightStrategy); // we'll collect error messages in here - - val iter = structuralTypesHelper.getMembersTripleIterator(G2, left, right, true); - while (iter.hasNext) { - // check if left member can fulfill the structural requirement imposed by right member - checkMembers(left, iter.next, info); - } - - return result(left, right, info.missingMembers, info.wrongMembers); - } - - /** - * Special handling for primitive-structural types. - * - *

Note that this method only returns a non-null {@link StructuralTypingResult} if primitive structural subtyping is - * applicable for the given operands {@code left} and {@code right}.

- * - * @returns A {@link StructuralTypingResult} if primitive structural typing is applicable. {@code null} otherwise. - */ - def StructuralTypingResult isPrimitiveStructuralSubtype(RuleEnvironment G, TypeRef leftRaw, TypeRef right) { - - // for the purpose of the rules implemented here, a number-/string-based enum behaves like type 'number'/'string' (lower-case) - val left = changeNumberOrStringBasedEnumToPrimitive(G, leftRaw); - - // check if we're dealing with structural primitive types - val rightIsPrimitive = right.declaredType instanceof PrimitiveType; - val leftIsPrimitive = left.declaredType instanceof PrimitiveType - - // primitive type on the right and non-primitive on the left - if (rightIsPrimitive && !leftIsPrimitive) { - return failure(TYS_NO_SUBTYPE.getMessage(leftRaw.typeRefAsString, right.typeRefAsString)); - } - // primitive type on the left and non-primitive on the right - else if (leftIsPrimitive && !rightIsPrimitive) { - return failure(TYS_NO_SUBTYPE.getMessage(leftRaw.typeRefAsString, right.typeRefAsString)); - } - // primitive types on both sides - else if (leftIsPrimitive && rightIsPrimitive) { - // types must match nominally - return if (left.declaredType === right.declaredType) { - success(); - } else { - failure(TYS_NO_SUBTYPE.getMessage(leftRaw.typeRefAsString, right.typeRefAsString)); - } - } - // neither left nor right is primitive - else { - // shouldn't be handled by this method - return null; - } - } - - /** - * Replace type references pointing to the type of a @NumberBased / @StringBased enum by - * a reference to built-in type number / string, leaving all other types unchanged. - */ - def private TypeRef changeNumberOrStringBasedEnumToPrimitive(RuleEnvironment G, TypeRef typeRef) { - val declType = typeRef.declaredType; - if (declType instanceof TEnum) { - val enumKind = N4JSLanguageUtils.getEnumKind(declType); - if (enumKind === EnumKind.NumberBased) { - return G.numberTypeRef; - } else if (enumKind === EnumKind.StringBased) { - return G.stringTypeRef; - } - } - return typeRef; - } - - def private void checkMembers(TypeRef leftTypeRef, StructuralMembersTriple triple, StructTypingInfo info) { - - val leftMember = triple.left; - val rightMember = triple.right; - val leftOtherAccessor = triple.leftOtherAccessor; - val leftStrategy = info.leftStrategy; - val rightStrategy = info.rightStrategy; - - checkMembers(leftTypeRef, leftMember, rightMember, info); - - switch (rightStrategy) { - - // For any ~r~ right members. - case STRUCTURAL_READ_ONLY_FIELDS: { - - // For any readable, non-optional right members. Initialized fields does not count as optional. - val handleOptionality = leftTypeRef.ASTNodeOptionalFieldStrategy.isOptionalityLessRestrictedOrEqual(OptionalFieldStrategy.GETTERS_OPTIONAL); - val memberNecessary = !rightMember.optional || (rightMember.optional && !handleOptionality); - if (memberNecessary && READABLE_FIELDS_PREDICATE.apply(rightMember)) { - - // For ~w~ left members requires an explicit getter. - if (STRUCTURAL_WRITE_ONLY_FIELDS === leftStrategy - && !GETTERS_PREDICATE.apply(leftMember) - && !GETTERS_PREDICATE.apply(leftOtherAccessor)) { - - info.wrongMembers.add(rightMember.name + " failed: readable field requires a getter in subtype."); - - // Otherwise any readable field is enough. - } else if (!READABLE_FIELDS_PREDICATE.apply(leftMember) && !READABLE_FIELDS_PREDICATE.apply(leftOtherAccessor)) { - info.wrongMembers.add(rightMember.name + " failed: readable field requires a readable field or a getter in subtype."); - } - } - } - - // For any ~w~ right members. - case STRUCTURAL_WRITE_ONLY_FIELDS: { - - // For any writable right members. - if (WRITABLE_FIELDS_PREDICATE.apply(rightMember) && (leftTypeRef.ASTNodeOptionalFieldStrategy != OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL)) { - - // If left is either ~r~ or ~i~, then an explicit setter is required. - if ((STRUCTURAL_READ_ONLY_FIELDS === leftStrategy || STRUCTURAL_FIELD_INITIALIZER === leftStrategy) - && !SETTERS_PREDICATE.apply(leftMember) - && !SETTERS_PREDICATE.apply(leftOtherAccessor)) { - - info.wrongMembers.add(rightMember.name + " failed: writable field requires a setter in subtype."); - - // Otherwise any writable field (data field or setter) is sufficient. - } else if (!WRITABLE_FIELDS_PREDICATE.apply(leftMember) && !WRITABLE_FIELDS_PREDICATE.apply(leftOtherAccessor)) { - info.wrongMembers.add(rightMember.name + " failed: writable field requires a writable field or a setter in subtype."); - } - } - } - - // For any ~i~ right members. - case STRUCTURAL_FIELD_INITIALIZER: { - - // For any non-optional and writable right members. - // Important: unlike in case of ~r~, here we treat initialized fields, such as @Final ones as optional fields. - if (WRITABLE_FIELDS_PREDICATE.apply(rightMember) && TypeUtils.isMandatoryField(rightMember)) { - - // If left is ~w~, then getters are required. - if (STRUCTURAL_WRITE_ONLY_FIELDS === leftStrategy - && !(GETTERS_PREDICATE.apply(leftMember) || GETTERS_PREDICATE.apply(leftOtherAccessor))) { - - info.wrongMembers.add(rightMember.name + " failed: non-optional writable field requires a getter in subtype."); - - // Otherwise any readable fields are fine. - } else if (!(READABLE_FIELDS_PREDICATE.apply(leftMember) || READABLE_FIELDS_PREDICATE.apply(leftOtherAccessor))) { - info.wrongMembers.add(rightMember.name + " failed: non-optional writable field requires a readable field or a getter in subtype."); - } - } - } - - default: { - - // If the left member is ~r~, ~w~ and/or ~i~ type. - if (STRUCTURAL_READ_ONLY_FIELDS === leftStrategy || STRUCTURAL_WRITE_ONLY_FIELDS === leftStrategy || STRUCTURAL_FIELD_INITIALIZER === leftStrategy) { - switch (rightMember) { - // If right is writable field, a getter/setter pair is mandatory on the left. - case rightMember.writeableField: { - if (!isGetterSetterPair(leftMember, leftOtherAccessor)) { - info.wrongMembers.add(rightMember.name + " failed: writable field requires a getter/setter pair in subtype."); - } - } - // If right is readable, we require an explicit getter. - case READABLE_FIELDS_PREDICATE.apply(rightMember): { - if (!(GETTERS_PREDICATE.apply(leftMember) || GETTERS_PREDICATE.apply(leftOtherAccessor))) { - info.wrongMembers.add(rightMember.name + " failed: read-only field requires a getter in subtype."); - } - } - // If there is a setter on the right, then we need a setter on the left. - case SETTERS_PREDICATE.apply(rightMember) : { - if (!(SETTERS_PREDICATE.apply(leftMember) || SETTERS_PREDICATE.apply(leftOtherAccessor))) { - info.wrongMembers.add(rightMember.name + " failed: setter requires a setter in subtype."); - } - } - } - } else { - // for a writable field on the right-hand side, require a getter/setter pair on the left - if (rightMember.writeableField && leftMember instanceof FieldAccessor) { - if (!(leftOtherAccessor instanceof TSetter)) { - if(leftTypeRef.ASTNodeOptionalFieldStrategy != OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL) { - val isSpecialCaseOfDispensableGetterForOptionalField = leftMember instanceof TSetter - && rightMember.optional - && (leftTypeRef.ASTNodeOptionalFieldStrategy == OptionalFieldStrategy.GETTERS_OPTIONAL); - if(!isSpecialCaseOfDispensableGetterForOptionalField) { - // special error message in case only either a getter XOR setter is supplied for a field - val msgSpecial = if(leftMember instanceof TGetter && rightMember.optional) { - "optional writable field requires at least a setter in subtype." - } else { - "writable field requires a field or a getter/setter pair in subtype." - }; - info.wrongMembers.add(rightMember.name + " failed: " + msgSpecial); - } - } - } else { - // check type of setter as usual - checkMembers(leftTypeRef, leftOtherAccessor, rightMember, info); - } - } - - } - } - } - } - - /** Returns with {@code true} iff the the arguments are a getter-setter accessor pair. */ - def private isGetterSetterPair(TMember firstLeft, TMember secondLeft) { - (GETTERS_PREDICATE.apply(firstLeft) || GETTERS_PREDICATE.apply(secondLeft)) - && (SETTERS_PREDICATE.apply(secondLeft) || SETTERS_PREDICATE.apply(secondLeft)); - } - - /** - * Checks if the member 'left' (may be null if not found) fulfills the structural requirement represented - * by member 'right'. In the error case, the two lists of error messages in 'info' are updated accordingly. - *

- * NOTE: in case of a field on right-hand side, this method accepts a getter OR(!) a setter of appropriate type - * on left side; the requirement that BOTH a getter AND setter must be provided for a writable field must be - * checked outside this method. - */ - def private void checkMembers(TypeRef leftTypeRef, TMember left, TMember right, StructTypingInfo info) { - val G = info.G; - - // !!! keep the following aligned with below method #reduceMembers() !!! - if (left === null) { - // no corresponding member found on left side - if (memberIsMissing(leftTypeRef, right, info)) { - - if (right instanceof TMethod) { - if (right.isCallSignature) { - // in case a call signature is missing on the left side, it is ok - // if the left side is itself a function and is override-compatible: - tsh.addSubstitutions(G, info.right); - val TypeRef rightTypeRef = ts.substTypeVariables(G, TypeUtils.createTypeRef(right)); - val result = ts.subtype(G, info.left, rightTypeRef); - if (result.success) { - return; // success - } - if (ts.subtypeSucceeded(G, info.left, G.functionTypeRef)) { - info.wrongMembers.add(keywordProvider.keyword(right, info.rightStrategy) + " failed: " + result.failureMessage); - return; - } - } - - } else if (right instanceof TField) { - val leftOptionalStrategy = leftTypeRef.ASTNodeOptionalFieldStrategy; - if (leftOptionalStrategy === OptionalFieldStrategy.GETTERS_OPTIONAL) { - info.missingMembers.add("setter or field " + right.name); - return; - } - } - - info.missingMembers.add(keywordProvider.keyword(right, info.rightStrategy) + " " + right.name); - } - - } else { - // found a corresponding member - // -> make sure types are compatible - - - var Variance variance; - if (left.optional && !right.optional) { - info.missingMembers.add(left.name + ' failed: non-optional member requires a corresponding non-optional member in the structural subtype.'); - return; - } else if (right.writeableField && left instanceof TField) { - // Fields are on both sides. - // T_FL = T_FR - if (left.readOnlyField - && STRUCTURAL_FIELD_INITIALIZER !== info.rightStrategy - && STRUCTURAL_READ_ONLY_FIELDS !== info.rightStrategy) { - info.wrongMembers.add(right.name + " failed: field is read-only."); - return; - } else { - variance = Variance.INV; - } - } else if (right instanceof TSetter || left instanceof TSetter) { - // Setter on one side (other side may be field or setter). - // contra-variant - variance = Variance.CONTRA; - } else { - // Other cases such as methods and function expressions. - // Only L<:R. - variance = Variance.CO; - } - - val mtypes = getMemberTypes(left, right, info); - val result = tsh.checkTypeArgumentCompatibility(G, mtypes.key, mtypes.value, Optional.of(variance), true); - if (result.failure) { - info.wrongMembers.add(getMemberName(right) + " failed: " + result.failureMessage); - } - } - } - - /** - * Same as previous method, but instead of actually checking the types, we return 0..2 constraints. This would normally - * belong into class InferenceContext, but is placed here to keep it aligned with above method more - * easily. - */ - def public List reduceMembers(RuleEnvironment G, - TypeRef leftTypeRef, TMember left, TMember right, StructTypingInfo info) { - - // !!! keep the following aligned with above method #checkMembers() !!! - if (left === null) { - // no corresponding member found on left side - if (memberIsMissing(leftTypeRef, right, info)) { - if (right instanceof TMethod) { - if (right.isCallSignature) { - // in case a call signature is missing on the left side, it is ok - // if the left side is itself a function and is override-compatible: - tsh.addSubstitutions(G, info.right); - val TypeRef rightTypeRef = ts.substTypeVariables(G, TypeUtils.createTypeRef(right)); - return Collections.singletonList( - new TypeConstraint(info.left, rightTypeRef, Variance.CO)); - } - } - return Collections.singletonList(TypeConstraint.FALSE); - } else { - return Collections.emptyList(); // this is like returning TypeConstraint.TRUE - } - - } else { - - var Variance variance; - if (left.optional && !right.optional) { - return Collections.singletonList(TypeConstraint.FALSE); - } else if (right.writeableField && left instanceof TField) { - if (left.readOnlyField - && STRUCTURAL_FIELD_INITIALIZER !== info.rightStrategy - && STRUCTURAL_READ_ONLY_FIELDS !== info.rightStrategy) { - return Collections.singletonList(TypeConstraint.FALSE); - } else { - variance = Variance.INV; - } - } else if (right instanceof TSetter || left instanceof TSetter) { - variance = Variance.CONTRA; - } else { - variance = Variance.CO; - } - val mtypes = getMemberTypes(left, right, info); - return tsh.reduceTypeArgumentCompatibilityCheck(G, mtypes.key, mtypes.value, Optional.of(variance), false); - } - } - - def private boolean memberIsMissing(TypeRef leftTypeRef, TMember right, StructTypingInfo info) { - val rightMemberIsOptional = rightMemberIsOptional(leftTypeRef, right, info.rightStrategy); - if (rightMemberIsOptional) { - // nothing to do (rightMember is optional or initialized for ~i~ typing.) - return false; - } else if (right?.containingType === info.G.objectType) { - // ignore object members, can only happen if left is structural field type - return false; - } else { - // standard case of missing member on left side -> report error - return true; - } - } - - /** - * This method implements Req-IDE-240500 in language spec. - */ - def private boolean rightMemberIsOptional(TypeRef leftTypeRef, TMember right, TypingStrategy rightStrategy) { - val leftOptionalStrategy = leftTypeRef.ASTNodeOptionalFieldStrategy; - - // Whether a right member is optional depends on both the right typing strategy and the right optionality field strategy. - val boolean rightMemberIsOptional = switch (rightStrategy) { - case STRUCTURAL, - case STRUCTURAL_FIELDS: { - // L <: ~N or L <: ~~N - if (!right.optional) { - false - } else { - (leftOptionalStrategy == OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL) || - (right instanceof TGetter && leftOptionalStrategy.isOptionalityLessRestrictedOrEqual(OptionalFieldStrategy.GETTERS_OPTIONAL)) - } - } - - case STRUCTURAL_WRITE_ONLY_FIELDS: { - // L <: ~w~N - right.optional && (leftOptionalStrategy == OptionalFieldStrategy.FIELDS_AND_ACCESSORS_OPTIONAL) - } - - case STRUCTURAL_READ_ONLY_FIELDS: { - // L <: ~r~N - right.optional && leftOptionalStrategy.isOptionalityLessRestrictedOrEqual(OptionalFieldStrategy.GETTERS_OPTIONAL) - } - - case STRUCTURAL_FIELD_INITIALIZER: { - // L <: ~i~N - (right.optional && leftOptionalStrategy.isOptionalityLessRestrictedOrEqual(OptionalFieldStrategy.GETTERS_OPTIONAL)) || - TypeUtils.isInitializedField(right) || TypeUtils.isOptionalSetter(right) - } - - default: { - false - } - }; - return rightMemberIsOptional; - } - - /** - * Store a guard in the given rule environment to note that we are in the process of inferring - * left ~<: right. - *

- * IDEBUG-171: - * If we are already computing the structural subtype relation left ~<: right and we are again asked - * whether left ~<: right, then we simply want to return success. - * The rationale is that if we run into a cycle while checking the members' types, we can simply say the - * members causing the cycle won't render the overall evaluation false ("an uns soll es nicht scheitern"). - *

- * EXAMPLE 1: - *

-	 * class Element {
-	 * 	public ~Element child;
-	 * }
-	 * var ~Element e1;
-	 * var ~Element e2;
-	 * //e1 = e2;   // remove comment to get stack overflow when disabling if-clause in #isSubtype()
-	 * 
- *

- * EXAMPLE 2: - *

-	 * class A {
-	 * 	public ~B f;
-	 * }
-	 * class B {
-	 * 	public ~A f;
-	 * }
-	 * var ~A a;
-	 * var ~B b;
-	 * //a = b;   // remove comment to get stack overflow when disabling if-clause in #isSubtype()
-	 * 
- * Note how this is analogous to what EMF is doing when computing structural equality as explained - * in the paragraph on "populating a two way map" in the following EMF API documentation: - * {@link EqualityHelper} - */ - def private void rememberStructuralSubtypingInProgressFor(RuleEnvironment G, TypeRef left, TypeRef right) { - G.put(GUARD_STRUCTURAL_TYPING_COMPUTER__IN_PROGRESS_FOR_TYPE_REF -> (left.wrap -> right.wrap), Boolean.TRUE); - } - - def private boolean isStructuralSubtypingInProgressFor(RuleEnvironment G, TypeRef left, TypeRef right) { - return G.get(GUARD_STRUCTURAL_TYPING_COMPUTER__IN_PROGRESS_FOR_TYPE_REF -> (left.wrap -> right.wrap)) !== null; - } - - def private TypeCompareUtils.SemanticEqualsWrapper wrap(TypeRef typeRef) { - new TypeCompareUtils.SemanticEqualsWrapper(typeRef); - } - - def private Pair getMemberTypes(TMember leftMember, TMember rightMember, - StructTypingInfo info) { - - val G = info.G; - val typeLeftRaw = ts.type(G, leftMember); - val typeRightRaw = ts.type(G, rightMember); - - // replace bound type variables with type arguments - val G_left = G.wrap; - val G_right = G.wrap; - tsh.addSubstitutions(G_left, info.left); - tsh.addSubstitutions(G_right, info.right); - - val typeLeft = ts.substTypeVariables(G_left, typeLeftRaw); - val typeRight = ts.substTypeVariables(G_right, typeRightRaw); - - return typeLeft -> typeRight; - } - - - def private String getMemberName(TMember member) { - if (member instanceof TMethod) { - if (member.isCallSignature) { - return "call signature"; - } else if (member.isConstructSignature) { - return "construct signature"; - } - } - return member.name; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SubtypeComputer.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SubtypeComputer.java new file mode 100644 index 0000000000..f1195b6bf1 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SubtypeComputer.java @@ -0,0 +1,314 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.typesystem.utils; + +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.addTypeMapping; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.getCancelIndicator; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.undefinedTypeRef; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.voidType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.wrap; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef; +import org.eclipse.n4js.ts.typeRefs.TypeArgument; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.InferenceVariable; +import org.eclipse.n4js.ts.types.TClassifier; +import org.eclipse.n4js.ts.types.TFormalParameter; +import org.eclipse.n4js.ts.types.TFunction; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.ts.types.TypeVariable; +import org.eclipse.n4js.ts.types.util.Variance; +import org.eclipse.n4js.types.utils.TypeUtils; +import org.eclipse.n4js.typesystem.N4JSTypeSystem; +import org.eclipse.n4js.typesystem.constraints.InferenceContext; +import org.eclipse.n4js.utils.DeclMergingHelper; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.xtext.service.OperationCanceledManager; + +import com.google.common.base.Optional; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Contains some helper methods to compute if type A is a subtype of type B. Note that the main logic for subtype + * computation is contained in file n4js.xsemantics. For structural typing there is a separate helper class called + * {@link StructuralTypingComputer}. + */ +@Singleton +class SubtypeComputer extends TypeSystemHelperStrategy { + + @Inject + private N4JSTypeSystem ts; + @Inject + private DeclMergingHelper declMergingHelper; + @Inject + private OperationCanceledManager operationCanceledManager; + + /** + * Returns true iff function/method 'left' is a subtype of function/method 'right'. + */ + boolean isSubtypeFunction(RuleEnvironment G, FunctionTypeExprOrRef left, FunctionTypeExprOrRef right) { + EList leftTypeVars = left.getTypeVars(); + EList rightTypeVars = right.getTypeVars(); + + if (leftTypeVars.isEmpty() && rightTypeVars.isEmpty()) { + + // both non-generic + return primIsSubtypeFunction(G, left, right); + + } else if (!leftTypeVars.isEmpty() && rightTypeVars.isEmpty()) { + + // left is generic, right is non-generic + // (i.e. cases like: {function(T):T} <: {function(string):string}) + // rationale: if there exists a valid binding of left's type variables + // so that bound(left) <: right, then left <: right + + InferenceContext infCtx = new InferenceContext(ts, tsh, declMergingHelper, operationCanceledManager, + getCancelIndicator(G), G); // start with no inference variables + // create an inference for each type in left + FunctionTypeExprOrRef left_withInfVars = infCtx.newInferenceVariablesFor(left); + // assuming 'left' was {function(T):T}, then left_withInfVars is now: {function(α):α} (non-generic!) + infCtx.addConstraint(left_withInfVars, right, Variance.CO); + Map solution = infCtx.solve(); // will give us something like α->string + if (solution != null) { + RuleEnvironment G_solution = newRuleEnvironment(G); + for (Entry entry : solution.entrySet()) { + addTypeMapping(G_solution, entry.getKey(), entry.getValue()); + } + TypeRef leftSubst = ts.substTypeVariables(G_solution, left_withInfVars); + if (leftSubst instanceof FunctionTypeExprOrRef) { + return primIsSubtypeFunction(G, (FunctionTypeExprOrRef) leftSubst, right); + } + } + return false; + + } else { + + // at least one is generic + // require same number of type parameters (an thus both have to be generic) + if (leftTypeVars.size() != rightTypeVars.size()) + return false; + + // + // STEP #1: apply ordinary subtype rules for functions + // + /* + * Here we substitute the type variables in the right function with matching (as in: same index) type + * variables of the left function. That is, the following substitution is applied before the function types + * are compared (here illustrated with a method override scenario): + * + * class Super { T m(T t); // {function(T):T} *1 } class Sub extends Super { U m(object o); // + * {function(object):U} *2 } + * + * The signature *1 from Super is converted to a signature {function(U):U} which is then compared to the + * subtype's method *2 {function(object):U}. + * + * The last step is to check the bounds of the type variables themselves. + */ + RuleEnvironment G2 = wrap(G); + for (int i = 0; i < leftTypeVars.size(); i++) { + addTypeMapping(G2, rightTypeVars.get(i), TypeUtils.createTypeRef(leftTypeVars.get(i))); + } + TypeRef rightSubst = ts.substTypeVariables(G2, right); + if (!(rightSubst instanceof FunctionTypeExprOrRef && + primIsSubtypeFunction(G, left, (FunctionTypeExprOrRef) rightSubst))) + return false; + + // + // STEP #2: check type variable bounds + // + // the following is required in a method override scenario where 'left' is the overriding + // and 'right' the overridden method + if (left.getDeclaredType() != null && left.getDeclaredType().eContainer() instanceof TClassifier) { + tsh.addSubstitutions(G2, TypeUtils.createTypeRef((TClassifier) left.getDeclaredType().eContainer())); + } + + return isMatchingTypeVariableBounds(G2, leftTypeVars, rightTypeVars); + } + } + + /** + * Contains the core logic for subtype relation of functions/methods but without taking into account type + * variables of generic functions/methods. Generic functions are handled in method + * {@link #isSubtypeFunction(RuleEnvironment,FunctionTypeExprOrRef,FunctionTypeExprOrRef)}. + */ + private boolean primIsSubtypeFunction(RuleEnvironment G, FunctionTypeExprOrRef left, FunctionTypeExprOrRef right) { + + // return type + TypeRef leftReturnTypeRef = left.getReturnTypeRef(); + TypeRef rightReturnTypeRef = right.getReturnTypeRef(); + if (rightReturnTypeRef != null) { + + // f():void <: f():void --> true + // f():B <: f():void --> true + // f():B? <: f():void --> true + // f():void <: f():A --> false, except A==undefined + // f():B <: f():A --> B <: A + // f():B? <: f():A --> false (!) + // f():void <: f():A? --> true (!) + // f():B <: f():A? --> B <: A + // f():B? <: f():A? --> B <: A + + // note these special cases, that follow from the above rules: + // f():void <: f():undefined --> true + // f():B <: f():undefined --> false (!) + // f():B? <: f():undefined --> false (!) + // f():undefined <: f():void --> true + // f():undefined <: f():A --> true + // f():undefined <: f():A? --> true + + if (rightReturnTypeRef.getDeclaredType() != voidType(G)) { + Type rightFunType = right.getDeclaredType(); + boolean isRightReturnOptional = (rightFunType instanceof TFunction) + ? ((TFunction) rightFunType).isReturnValueOptional() + : right.isReturnValueOptional(); + + if (leftReturnTypeRef != null && leftReturnTypeRef.getDeclaredType() != voidType(G)) { + // both are non-void + if (left.isReturnValueOptional() && !isRightReturnOptional) { + return false; + } else if (!checkTypeArgumentCompatibility(G, leftReturnTypeRef, rightReturnTypeRef, Variance.CO)) { + return false; + } + } else { + // left is void, right is non-void + if (!isRightReturnOptional && !ts.equaltypeSucceeded(G, rightReturnTypeRef, undefinedTypeRef(G))) { + return false; + } + } + } + } + + // formal parameters + int k = left.getFpars().size(); + int n = right.getFpars().size(); + if (k <= n) { + if (k > 0) { + var i = 0; + while (i < k) { + TFormalParameter R = right.getFpars().get(i); + TFormalParameter L = left.getFpars().get(i); + + if ((R.isVariadic() || R.isOptional()) && !(L.isOptional() || L.isVariadic())) { + return false; + } + + if (!checkTypeArgumentCompatibility(G, L.getTypeRef(), R.getTypeRef(), Variance.CONTRA)) + return false; + i = i + 1; + } + TFormalParameter L = left.getFpars().get(k - 1); + if (L.isVariadic()) { + while (i < n) { + TFormalParameter R = right.getFpars().get(i); + if (!checkTypeArgumentCompatibility(G, L.getTypeRef(), R.getTypeRef(), Variance.CONTRA)) + return false; + i = i + 1; + } + } + } + } else { // k>n + + // {function(A, A...)} <: {function(A)} + int i = 0; + while (i < n) { + TFormalParameter R = right.getFpars().get(i); + TFormalParameter L = left.getFpars().get(i); + + if ((R.isVariadic() || R.isOptional()) && !(L.isOptional() || L.isVariadic())) { + return false; + } + + if (!checkTypeArgumentCompatibility(G, L.getTypeRef(), R.getTypeRef(), Variance.CONTRA)) + return false; + i = i + 1; + } + TFormalParameter R = (n > 0) + ? right.getFpars().get(n - 1) + : + // if right hand side has no parameters at all, e.g. {function(A?)} <: {function()} + null; + while (i < k) { + TFormalParameter L = left.getFpars().get(i); + if (!(L.isOptional() || L.isVariadic())) { + return false; + } + if (R != null && R.isVariadic()) { + if (!checkTypeArgumentCompatibility(G, L.getTypeRef(), R.getTypeRef(), Variance.CONTRA)) + return false; + } + i = i + 1; + } + } + + // declaredThisType + // contra-variant behavior: + TypeRef rThis = right.getDeclaredThisType(); + TypeRef lThis = left.getDeclaredThisType(); + if (rThis != null) { + return lThis == null || checkTypeArgumentCompatibility(G, lThis, rThis, Variance.CONTRA); + } else { + + // Should fail: + if (lThis != null) { + return false; + } + } + + return true; + } + + private boolean checkTypeArgumentCompatibility(RuleEnvironment G, TypeArgument leftArg, TypeArgument rightArg, + Variance variance) { + Result result = tsh.checkTypeArgumentCompatibility(G, leftArg, rightArg, Optional.of(variance), false); + return result.isSuccess(); + } + + /** + * Checks bounds of type variables in left and right. Upper bound on left side must be a super type of upper bound + * on right side. + */ + private boolean isMatchingTypeVariableBounds(RuleEnvironment G, List left, + List right) { + + // check type variable bounds + for (var i = 0; i < right.size(); i++) { + TypeVariable leftTypeVar = left.get(i); + TypeVariable rightTypeVar = right.get(i); + TypeRef leftDeclUB = leftTypeVar.getDeclaredUpperBound(); + TypeRef rightDeclUB = rightTypeVar.getDeclaredUpperBound(); + TypeRef leftUpperBound = (leftDeclUB == null) + ? N4JSLanguageUtils.getTypeVariableImplicitUpperBound(G) + : leftDeclUB; + TypeRef rightUpperBound = (rightDeclUB == null) + ? N4JSLanguageUtils.getTypeVariableImplicitUpperBound(G) + : rightDeclUB; + TypeRef rightUpperBoundSubst = ts.substTypeVariables(G, rightUpperBound); + + // leftUpperBound must be a super(!) type of rightUpperBound, + // i.e. rightUpperBound <: leftUpperBound + if (!isSubtype(G, rightUpperBoundSubst, leftUpperBound)) { + return false; + } + } + return true; + } + + private boolean isSubtype(RuleEnvironment G, TypeArgument left, TypeArgument right) { + return ts.subtype(G, left, right).isSuccess(); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SubtypeComputer.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SubtypeComputer.xtend deleted file mode 100644 index 3b85fbfd44..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/typesystem/utils/SubtypeComputer.xtend +++ /dev/null @@ -1,309 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.typesystem.utils - -import com.google.common.base.Optional -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.List -import org.eclipse.n4js.ts.typeRefs.FunctionTypeExprOrRef -import org.eclipse.n4js.ts.typeRefs.TypeArgument -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TClassifier -import org.eclipse.n4js.ts.types.TFormalParameter -import org.eclipse.n4js.ts.types.TFunction -import org.eclipse.n4js.ts.types.TypeVariable -import org.eclipse.n4js.ts.types.util.Variance -import org.eclipse.n4js.types.utils.TypeUtils -import org.eclipse.n4js.typesystem.N4JSTypeSystem -import org.eclipse.n4js.typesystem.constraints.InferenceContext -import org.eclipse.n4js.utils.DeclMergingHelper -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.xtext.service.OperationCanceledManager - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Contains some helper methods to compute if type A is a subtype of type B. - * Note that the main logic for subtype computation is contained in file - * n4js.xsemantics. For structural typing there is a separate helper class - * called {@link StructuralTypingComputer}. - */ -@Singleton -package class SubtypeComputer extends TypeSystemHelperStrategy { - - @Inject - private N4JSTypeSystem ts; - @Inject - private TypeSystemHelper tsh; - @Inject - private DeclMergingHelper declMergingHelper; - @Inject - private OperationCanceledManager operationCanceledManager; - - /** - * Returns true iff function/method 'left' is a subtype of function/method 'right'. - */ - def boolean isSubtypeFunction(RuleEnvironment G, FunctionTypeExprOrRef left, FunctionTypeExprOrRef right) { - val leftTypeVars = left.typeVars; - val rightTypeVars = right.typeVars; - - if (leftTypeVars.empty && rightTypeVars.empty) { - - // both non-generic - return primIsSubtypeFunction(G, left, right); - - } else if (!leftTypeVars.empty && rightTypeVars.empty) { - - // left is generic, right is non-generic - // (i.e. cases like: {function(T):T} <: {function(string):string}) - // rationale: if there exists a valid binding of left's type variables - // so that bound(left) <: right, then left <: right - - val infCtx = new InferenceContext(ts, tsh, declMergingHelper, operationCanceledManager, G.cancelIndicator, G); // start with no inference variables - val left_withInfVars = infCtx.newInferenceVariablesFor(left); // create an inference variable for each type param in left - // assuming 'left' was {function(T):T}, then left_withInfVars is now: {function(α):α} (non-generic!) - infCtx.addConstraint(left_withInfVars, right, Variance.CO); - val solution = infCtx.solve; // will give us something like α->string - if (solution !== null) { - val G_solution = G.newRuleEnvironment; - solution.entrySet.forEach[G_solution.addTypeMapping(key,value)]; - val leftSubst = ts.substTypeVariables(G_solution, left_withInfVars); - if (leftSubst instanceof FunctionTypeExprOrRef) { - return primIsSubtypeFunction(G, leftSubst, right); - } - } - return false; - - } else { - - // at least one is generic - // require same number of type parameters (an thus both have to be generic) - if (leftTypeVars.size !== rightTypeVars.size) - return false; - - // - // STEP #1: apply ordinary subtype rules for functions - // - /* - * Here we substitute the type variables in the right function with matching (as in: same index) - * type variables of the left function. That is, the following substitution is applied before the - * function types are compared (here illustrated with a method override scenario): - * - * class Super { - * T m(T t); // {function(T):T} *1 - * } - * class Sub extends Super { - * U m(object o); // {function(object):U} *2 - * } - * - * The signature *1 from Super is converted to a signature {function(U):U} which is then compared - * to the subtype's method *2 {function(object):U}. - * - * The last step is to check the bounds of the type variables themselves. - */ - val G2 = G.wrap - for (i : 0 ..< leftTypeVars.size) { - G2.addTypeMapping(rightTypeVars.get(i), TypeUtils.createTypeRef(leftTypeVars.get(i))) - } - val TypeRef rightSubst = ts.substTypeVariables(G2, right); - if (!(rightSubst instanceof FunctionTypeExprOrRef && - primIsSubtypeFunction(G, left, rightSubst as FunctionTypeExprOrRef))) - return false; - - // - // STEP #2: check type variable bounds - // - // the following is required in a method override scenario where 'left' is the overriding - // and 'right' the overridden method - if (left.declaredType?.eContainer instanceof TClassifier) - tsh.addSubstitutions(G2, TypeUtils.createTypeRef(left.declaredType.eContainer as TClassifier)); - - return isMatchingTypeVariableBounds(G2, leftTypeVars, rightTypeVars); - } - } - - /** - * Contains the core logic for subtype relation of functions/methods but without - * taking into account type variables of generic functions/methods. Generic functions are handled - * in method {@link #isSubtypeFunction(RuleEnvironment,FunctionTypeExprOrRef,FunctionTypeExprOrRef)}. - */ - private def boolean primIsSubtypeFunction(RuleEnvironment G, FunctionTypeExprOrRef left, FunctionTypeExprOrRef right) { - - // return type - val leftReturnTypeRef = left.returnTypeRef; - val rightReturnTypeRef = right.returnTypeRef; - if (rightReturnTypeRef !== null) { - - // f():void <: f():void --> true - // f():B <: f():void --> true - // f():B? <: f():void --> true - // f():void <: f():A --> false, except A==undefined - // f():B <: f():A --> B <: A - // f():B? <: f():A --> false (!) - // f():void <: f():A? --> true (!) - // f():B <: f():A? --> B <: A - // f():B? <: f():A? --> B <: A - - // note these special cases, that follow from the above rules: - // f():void <: f():undefined --> true - // f():B <: f():undefined --> false (!) - // f():B? <: f():undefined --> false (!) - // f():undefined <: f():void --> true - // f():undefined <: f():A --> true - // f():undefined <: f():A? --> true - - if (rightReturnTypeRef.declaredType !== G.voidType) { - val rightFunType = right.declaredType; - val boolean isRightReturnOptional = if (rightFunType instanceof TFunction) - rightFunType.returnValueOptional - else - right.isReturnValueOptional; - - if (leftReturnTypeRef !== null && leftReturnTypeRef.declaredType !== G.voidType) { - // both are non-void - if (left.isReturnValueOptional && !isRightReturnOptional) { - return false; - } else if (!checkTypeArgumentCompatibility(G, leftReturnTypeRef, rightReturnTypeRef, Variance.CO)) { - return false; - } - } else { - // left is void, right is non-void - if (!isRightReturnOptional && !ts.equaltypeSucceeded(G, rightReturnTypeRef, G.undefinedTypeRef)) { - return false; - } - } - } - } - - // formal parameters - val k = left.fpars.size; - val n = right.fpars.size; - if (k <= n) { - if (k > 0) { - var i = 0; - while (i < k) { - val R = right.fpars.get(i); - val L = left.fpars.get(i); - - if ((R.variadic || R.optional) && !(L.optional || L.variadic)) { - return false; - } - - if (!checkTypeArgumentCompatibility(G, L.typeRef, R.typeRef, Variance.CONTRA)) - return false; - i = i + 1; - } - val L = left.fpars.get(k - 1); - if (L.variadic) { - while (i < n) { - val R = right.fpars.get(i); - if (!checkTypeArgumentCompatibility(G, L.typeRef, R.typeRef, Variance.CONTRA)) - return false; - i = i + 1; - } - } - } - } else { // k>n - - // {function(A, A...)} <: {function(A)} - var i = 0; - while (i < n) { - val R = right.fpars.get(i); - val L = left.fpars.get(i); - - if ((R.variadic || R.optional) && !(L.optional || L.variadic)) { - return false; - } - - if (!checkTypeArgumentCompatibility(G, L.typeRef, R.typeRef, Variance.CONTRA)) - return false; - i = i + 1; - } - val TFormalParameter R = if (n > 0) { - right.fpars.get(n - 1) - } else { - // if right hand side has no parameters at all, e.g. {function(A?)} <: {function()} - null - }; - while (i < k) { - val L = left.fpars.get(i); - if (! (L.optional || L.variadic)) { - return false; - } - if (R !== null && R.variadic) { - if (!checkTypeArgumentCompatibility(G, L.typeRef, R.typeRef, Variance.CONTRA)) - return false; - } - i = i + 1; - } - } - - // declaredThisType - // contra-variant behavior: - val rThis = right.declaredThisType - val lThis = left.declaredThisType - if (rThis !== null) { - return lThis === null || checkTypeArgumentCompatibility(G, lThis, rThis, Variance.CONTRA); - } else { - - // Should fail: - if (lThis !== null) { - return false; - } - } - - return true; - } - - private def boolean checkTypeArgumentCompatibility(RuleEnvironment G, TypeArgument leftArg, TypeArgument rightArg, Variance variance) { - val result = tsh.checkTypeArgumentCompatibility(G, leftArg, rightArg, Optional.of(variance), false); - return result.success; - } - - /** - * Checks bounds of type variables in left and right. - * Upper bound on left side must be a super type of upper bound on right side. - */ - private def boolean isMatchingTypeVariableBounds(RuleEnvironment G, List left, - List right) { - - // check type variable bounds - for (var i = 0; i < right.size; i++) { - val leftTypeVar = left.get(i) - val rightTypeVar = right.get(i) - val leftDeclUB = leftTypeVar.declaredUpperBound; - val rightDeclUB = rightTypeVar.declaredUpperBound; - val leftUpperBound = if (leftDeclUB===null) { - N4JSLanguageUtils.getTypeVariableImplicitUpperBound(G) - } else { - leftDeclUB - }; - val rightUpperBound = if (rightDeclUB===null) { - N4JSLanguageUtils.getTypeVariableImplicitUpperBound(G) - } else { - rightDeclUB - }; - val rightUpperBoundSubst = ts.substTypeVariables(G, rightUpperBound); - - // leftUpperBound must be a super(!) type of rightUpperBound, - // i.e. rightUpperBound <: leftUpperBound - if (!G.isSubtype(rightUpperBoundSubst, leftUpperBound)) { - return false - } - } - return true - } - - private def boolean isSubtype(RuleEnvironment G, TypeArgument left, TypeArgument right) { - return ts.subtype(G, left, right).success; - } -}