/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.type;
import com.google.dart.engine.AnalysisEngine;
import com.google.dart.engine.internal.element.ElementPair;
import com.google.dart.engine.type.Type;
import com.google.dart.engine.type.UnionType;
import com.google.dart.engine.utilities.translation.DartExpressionBody;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* In addition to the methods of the {@code UnionType} interface we add a factory method
* {@code union} for building unions.
*/
public class UnionTypeImpl extends TypeImpl implements UnionType {
/**
* Any unions in the {@code types} will be flattened in the returned union. If there is only one
* type after flattening then it will be returned directly, instead of a singleton union. Nulls
* are discarded, unless all types are null, in which case an exception is raised.
*
* @param types the {@code Type}s to union
* @return a {@code Type} comprising the {@code Type}s in {@code types}
*/
public static Type union(Type... types) {
Set<Type> set = new HashSet<Type>();
for (Type t : types) {
if (t instanceof UnionType) {
set.addAll(((UnionType) t).getElements());
} else {
if (t != null) {
set.add(t);
}
}
}
if (set.size() == 0) {
// TODO(collinsn): better to return [null] here? The use case is e.g.
//
// union(null, null) ==> null;
//
// instead of raising an exception.
throw new IllegalArgumentException("No known use case for empty unions.");
} else if (set.size() == 1) {
return set.iterator().next();
} else {
return new UnionTypeImpl(set);
}
}
/**
* The types in this union.
*/
private Set<Type> types;
/**
* This constructor should only be called by the {@code union} factory: it does not check that its
* argument {@code types} contains no union types.
*
* @param types
*/
private UnionTypeImpl(Set<Type> types) {
// The element is null because union types are not associated with program elements.
// We make the name null because that's what [FunctionTypeImpl] uses when the element is null.
super(null, null);
this.types = types;
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof UnionType)) {
return false;
} else if (this == other) {
return true;
} else {
return types.equals(((UnionType) other).getElements());
}
}
@Override
public String getDisplayName() {
StringBuilder builder = new StringBuilder();
String prefix = "{";
for (Type t : types) {
builder.append(prefix);
builder.append(t.getDisplayName());
prefix = ",";
}
builder.append("}");
return builder.toString();
}
@DartExpressionBody("_types")
@Override
public Set<Type> getElements() {
return Collections.unmodifiableSet(types);
}
@Override
public int hashCode() {
return types.hashCode();
}
@Override
public Type substitute(Type[] argumentTypes, Type[] parameterTypes) {
ArrayList<Type> out = new ArrayList<Type>();
for (Type t : types) {
out.add(t.substitute(argumentTypes, parameterTypes));
}
return union(out.toArray(new Type[out.size()]));
}
@Override
protected void appendTo(StringBuilder builder) {
String prefix = "{";
for (Type t : types) {
builder.append(prefix);
((TypeImpl) t).appendTo(builder);
prefix = ",";
}
builder.append("}");
}
@Override
protected boolean internalEquals(Object object, Set<ElementPair> visitedElementPairs) {
// Since union types are immutable, I don't think it's
// possible to construct a self-referential union type. Of course, a self-referential
// non-union type could intermediate through a union type, but since union types
// don't occur in user programs this is not a problem we expect to run into any time
// soon.
return this.equals(object);
}
@Override
protected boolean internalIsMoreSpecificThan(Type type, boolean withDynamic,
Set<TypePair> visitedTypePairs) {
// What version of subtyping do we want? See discussion below in [internalIsSubtypeOf].
if (AnalysisEngine.getInstance().getStrictUnionTypes()) {
// The less unsound version: all.
for (Type t : types) {
if (!((TypeImpl) t).internalIsMoreSpecificThan(type, withDynamic, visitedTypePairs)) {
return false;
}
}
return true;
} else {
// The more unsound version: any.
for (Type t : types) {
if (((TypeImpl) t).internalIsMoreSpecificThan(type, withDynamic, visitedTypePairs)) {
return true;
}
}
return false;
}
}
@Override
protected boolean internalIsSubtypeOf(Type type, Set<TypePair> visitedTypePairs) {
// Premature optimization opportunity: if [type] is also a union type, we could instead
// do a subset test on the underlying element sets.
// What version of union-type subtyping do we want? We choose the more unsound version,
// motivated by the following example. Suppose classes [A] and [B] are unrelated
// and that [C] extends [B]:
//
// A B
// |
// C
//
// If [ab : {A, B}] and [ac : {A, C}] then we'd intuitively expect either
// both or neither of the assignments
//
// B b1 = ab;
// B b2 = ac;
//
// to be allowed; they're both allowed under the more unsound subtyping rule.
//
// However, under the less unsound subtyping rule, the assignment to [b1] is
// allowed, but the assignment to [b2] is not. The reason is that assignment
// compatible [T1 <=> T1] for interface types is defined by [T1 <: T2] or
// [T2 <: T1]. So, in the [b1 = ab] case, the test [B <: {A, B}] always passes,
// for both definitions of union-type subtyping. On the other hand, the test
// [B <: {A, C}] always fails, leaving only [{A, C} <: B], which only passes
// for the more unsound rule.
//
// An alternative solution, which lets us keep the more sound version of
// union-type subtyping, is to instead redefine assignment compatibility [<=>] for
// union types. By instead defining
//
// {T1, ..., Tn} <=> T
//
// iff
//
// Ti <=> T
//
// for all [i], we reject both assignments above.
//
// An interesting consequence: we usually think of [A <: B] meaning that
// knowing an expression has type [A] is more informative than knowing it has type [B].
// Of course, this intuition is already wrong once we have [dynamic], but it becomes
// more wrong when we define union-type subtyping in the more unsound way. We now e.g.
// have [{A, B} <: B], where the LHS is surely less informative than the RHS!
if (AnalysisEngine.getInstance().getStrictUnionTypes()) {
// The less unsound version: all.
//
// For this version to make sense we also need to redefine assignment compatibility [<=>].
// See discussion above.
for (Type t : types) {
if (!((TypeImpl) t).internalIsSubtypeOf(type, visitedTypePairs)) {
return false;
}
}
return true;
} else {
// The more unsound version: any.
for (Type t : types) {
if (((TypeImpl) t).internalIsSubtypeOf(type, visitedTypePairs)) {
return true;
}
}
return false;
}
}
/**
* The more-specific-than test for union types on the RHS is uniform in non-union LHSs. So, other
* {@code TypeImpl}s can call this method to implement {@code internalIsMoreSpecificThan} for
* union types.
*
* @param type
* @param visitedTypePairs
* @return true if {@code type} is more specific than this union type
*/
protected boolean internalUnionTypeIsLessSpecificThan(Type type, boolean withDynamic,
Set<TypePair> visitedTypePairs) {
// This implementation does not make sense when [type] is a union type, at least
// for the "less unsound" version of [internalIsMoreSpecificThan] above.
if (type instanceof UnionType) {
throw new IllegalArgumentException("Only non-union types are supported.");
}
for (Type t : types) {
if (((TypeImpl) type).internalIsMoreSpecificThan(t, withDynamic, visitedTypePairs)) {
return true;
}
}
return false;
}
/**
* The supertype test for union types is uniform in non-union subtypes. So, other {@code TypeImpl}
* s can call this method to implement {@code internalIsSubtypeOf} for union types.
*
* @param type
* @param visitedTypePairs
* @return true if this union type is a super type of {@code type}
*/
protected boolean internalUnionTypeIsSuperTypeOf(Type type, Set<TypePair> visitedTypePairs) {
// This implementation does not make sense when [type] is a union type, at least
// for the "less unsound" version of [internalIsSubtypeOf] above.
if (type instanceof UnionType) {
throw new IllegalArgumentException("Only non-union types are supported.");
}
for (Type t : types) {
if (((TypeImpl) type).internalIsSubtypeOf(t, visitedTypePairs)) {
return true;
}
}
return false;
}
}