| /******************************************************************************* |
| * Copyright (c) 2012 NumberFour AG |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * NumberFour AG - initial API and Implementation (Alex Panchenko) |
| *******************************************************************************/ |
| package org.eclipse.dltk.javascript.typeinfo; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.dltk.annotations.Nullable; |
| import org.eclipse.dltk.javascript.typeinference.ReferenceLocation; |
| |
| /** |
| * Finds the least common supertype among the specified type expressions or |
| * declarations. Only supertypes are considered at the moment, implemented |
| * traits (interfaces) are not checked. |
| * <p> |
| * There are 2 entry points: {@link #evaluate(Collection)} which works with type |
| * expressions and {@link #leastCommonSuperType(List)} which works with type |
| * declarations. |
| * </p> |
| */ |
| public class CommonSuperTypeFinder { |
| |
| static class State { |
| int anyCount; |
| final Set<IRSimpleType> simpleTypes = new HashSet<IRSimpleType>(); |
| final Set<IRClassType> classTypes = new HashSet<IRClassType>(); |
| final Set<IRArrayType> arrayTypes = new HashSet<IRArrayType>(); |
| final Set<IRMapType> mapTypes = new HashSet<IRMapType>(); |
| final Set<IRLocalType> localTypes = new HashSet<IRLocalType>(); |
| final Set<IRRecordType> recordTypes = new HashSet<IRRecordType>(); |
| final Set<IRType> others = new HashSet<IRType>(); |
| |
| void addAll(Collection<? extends IRType> types) { |
| for (IRType type : types) { |
| add(type); |
| } |
| } |
| |
| void add(IRType type) { |
| if (type == RTypes.none() || type == RTypes.undefined()) { |
| return; // just ignore |
| } else if (type == RTypes.any()) { |
| ++anyCount; |
| } else if (type instanceof IRUnionType) { |
| addAll(((IRUnionType) type).getTargets()); // expand |
| } else if (type instanceof IRArrayType) { |
| arrayTypes.add((IRArrayType) type); |
| } else if (type instanceof IRSimpleType) { |
| simpleTypes.add((IRSimpleType) type); |
| } else if (type instanceof IRClassType) { |
| classTypes.add((IRClassType) type); |
| } else if (type instanceof IRMapType) { |
| mapTypes.add((IRMapType) type); |
| } else if (type instanceof IRLocalType) { |
| localTypes.add((IRLocalType) type); |
| } else if (type instanceof IRRecordType) { |
| recordTypes.add((IRRecordType) type); |
| } else { |
| others.add(type); |
| } |
| } |
| |
| private int countCollections() { |
| int result = 0; |
| if (!simpleTypes.isEmpty()) |
| ++result; |
| if (!classTypes.isEmpty()) |
| ++result; |
| if (!arrayTypes.isEmpty()) |
| ++result; |
| if (!mapTypes.isEmpty()) |
| ++result; |
| if (!localTypes.isEmpty()) |
| ++result; |
| if (!recordTypes.isEmpty()) |
| ++result; |
| if (!others.isEmpty()) |
| ++result; |
| return result; |
| } |
| |
| private boolean areJavaScriptObjects(Collection<? extends IRType> types) { |
| for (IRType type : types) { |
| if (!type.isJavaScriptObject()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private boolean areJavaScriptObjects() { |
| // arrays & maps are always JS, check other sets |
| return areJavaScriptObjects(simpleTypes) |
| && areJavaScriptObjects(classTypes) |
| && areJavaScriptObjects(others); |
| } |
| |
| IRType find(ITypeSystem typeSystem) { |
| if (anyCount != 0) { |
| return RTypes.any(); |
| } |
| if (countCollections() != 1) { |
| return areJavaScriptObjects() ? RTypes.OBJECT : RTypes.any(); |
| } |
| if (!simpleTypes.isEmpty()) { |
| if (simpleTypes.size() == 1) { |
| return getSingleItem(simpleTypes); |
| } |
| final List<IRTypeDeclaration> declarations = new ArrayList<IRTypeDeclaration>(); |
| for (IRSimpleType type : simpleTypes) { |
| declarations.add(type.getDeclaration()); |
| } |
| final IRTypeDeclaration common = leastCommonSuperType(declarations); |
| return common != null ? RTypes.simple(common) : RTypes.any(); |
| } else if (!classTypes.isEmpty()) { |
| if (classTypes.size() == 1) { |
| return getSingleItem(classTypes); |
| } |
| final List<IRTypeDeclaration> declarations = new ArrayList<IRTypeDeclaration>(); |
| for (IRClassType type : classTypes) { |
| if (type.getDeclaration() == null) { |
| return type; |
| } |
| declarations.add(type.getDeclaration()); |
| } |
| return RTypes.classType(leastCommonSuperType(declarations)); |
| } else if (!arrayTypes.isEmpty()) { |
| if (arrayTypes.size() == 1) { |
| return getSingleItem(arrayTypes); |
| } |
| final Set<IRType> itemTypes = new HashSet<IRType>(); |
| for (IRArrayType type : arrayTypes) { |
| // TODO (alex) skip empty arrays |
| itemTypes.add(type.getItemType()); |
| } |
| return RTypes.arrayOf(typeSystem, |
| evaluate(typeSystem, itemTypes)); |
| } else if (!mapTypes.isEmpty()) { |
| if (mapTypes.size() == 1) { |
| return getSingleItem(mapTypes); |
| } |
| final Set<IRType> itemTypes = new HashSet<IRType>(); |
| for (IRMapType type : mapTypes) { |
| itemTypes.add(type.getValueType()); |
| } |
| return RTypes.mapOf(RTypes.STRING, |
| evaluate(typeSystem, itemTypes)); |
| } else if (!localTypes.isEmpty()) { |
| if (localTypes.size() == 1) { |
| return getSingleItem(localTypes); |
| } |
| final Set<ReferenceLocation> locations = new HashSet<ReferenceLocation>(); |
| for (IRLocalType type : localTypes) { |
| locations.add(type.getReferenceLocation()); |
| } |
| // TODO super type check? |
| return locations.size() == 1 ? localTypes.iterator().next() |
| : RTypes.OBJECT; |
| } else if (!recordTypes.isEmpty()) { |
| if (recordTypes.size() == 1) { |
| return getSingleItem(recordTypes); |
| } |
| List<IRRecordMember> commonMembers = new ArrayList<IRRecordMember>(); |
| for (IRRecordType type : recordTypes) { |
| if (commonMembers.size() == 0) |
| commonMembers.addAll(type.getMembers()); |
| else { |
| commonMembers.retainAll(type.getMembers()); |
| } |
| } |
| return commonMembers.size() == 0 ? RTypes.OBJECT : RTypes |
| .recordType(commonMembers); |
| } else { |
| assert !others.isEmpty(); |
| if (others.size() == 1) { |
| return getSingleItem(others); |
| } |
| // TODO (alex) better support for record types |
| return RTypes.OBJECT; |
| } |
| } |
| |
| } |
| |
| /** |
| * Returns the least common super type among the specified type expressions |
| * or {@link RTypes#any()}. |
| */ |
| public static IRType evaluate(ITypeSystem typeSystem, |
| Collection<? extends IRType> types) { |
| if (types.isEmpty()) { |
| return RTypes.any(); |
| } else { |
| if (types.size() == 1) { |
| final IRType type = getSingleItem(types); |
| if (!(type instanceof IRUnionType)) { |
| return type; |
| } |
| } |
| final State state = new State(); |
| state.addAll(types); |
| return state.find(typeSystem); |
| } |
| } |
| |
| /** |
| * Returns the least common super type among the specified |
| * {@link IRTypeDeclaration}s or null, if no such type could be found. |
| */ |
| @Nullable |
| public static IRTypeDeclaration leastCommonSuperType( |
| List<IRTypeDeclaration> declarations) { |
| if (declarations.isEmpty()) { |
| return null; |
| } else if (declarations.size() == 1) { |
| return declarations.get(0); |
| } |
| // initial hierarchy based on the 0th type |
| final List<IRTypeDeclaration> hierarchy = new ArrayList<IRTypeDeclaration>(); |
| for (IRTypeDeclaration d = declarations.get(0);;) { |
| hierarchy.add(d); |
| d = d.getSuperType(); |
| if (d == null) |
| break; |
| } |
| Collections.reverse(hierarchy); |
| // handle others starting from the 1st type |
| for (int i = 1; i < declarations.size(); ++i) { |
| for (IRTypeDeclaration declaration = declarations.get(i);;) { |
| final int index = hierarchy.lastIndexOf(declaration); |
| if (index >= 0) { // common type found, remove the tail |
| for (int j = hierarchy.size(); --j > index;) { |
| hierarchy.remove(j); |
| } |
| break; // ... and handle the next type |
| } |
| declaration = declaration.getSuperType(); |
| if (declaration == null) { // no common type found |
| // TODO (alex) check mixed java/javascript case |
| return RTypes.OBJECT.getDeclaration(); |
| } |
| } |
| } |
| return hierarchy.get(hierarchy.size() - 1); |
| } |
| |
| @SuppressWarnings("unchecked") |
| static <T> T getSingleItem(Collection<T> values) { |
| assert values.size() == 1; |
| if (values instanceof List) { |
| return ((List<T>) values).get(0); |
| } else { |
| return (T) values.toArray()[0]; |
| } |
| } |
| |
| } |