blob: 2d38121ca043ed856f00a67e8c45fe016d46df56 [file] [log] [blame]
/*******************************************************************************
* 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.internal.javascript.ti;
import static org.eclipse.core.runtime.Platform.getDebugOption;
import static org.eclipse.dltk.javascript.typeinfo.RTypes.none;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.dltk.annotations.NonNull;
import org.eclipse.dltk.annotations.Nullable;
import org.eclipse.dltk.javascript.internal.core.RConstructor;
import org.eclipse.dltk.javascript.internal.core.RMethod;
import org.eclipse.dltk.javascript.internal.core.RParameter;
import org.eclipse.dltk.javascript.internal.core.RParameterizedTypeDeclaration;
import org.eclipse.dltk.javascript.internal.core.RProperty;
import org.eclipse.dltk.javascript.internal.core.RTypeDeclaration;
import org.eclipse.dltk.javascript.typeinfo.IRArrayType;
import org.eclipse.dltk.javascript.typeinfo.IRConstructor;
import org.eclipse.dltk.javascript.typeinfo.IRContextualizableType;
import org.eclipse.dltk.javascript.typeinfo.IRMember;
import org.eclipse.dltk.javascript.typeinfo.IRMethod;
import org.eclipse.dltk.javascript.typeinfo.IRParameter;
import org.eclipse.dltk.javascript.typeinfo.IRProperty;
import org.eclipse.dltk.javascript.typeinfo.IRSimpleType;
import org.eclipse.dltk.javascript.typeinfo.IRType;
import org.eclipse.dltk.javascript.typeinfo.IRTypeDeclaration;
import org.eclipse.dltk.javascript.typeinfo.IRTypeTransformer;
import org.eclipse.dltk.javascript.typeinfo.ITypeSystem;
import org.eclipse.dltk.javascript.typeinfo.RTypes;
import org.eclipse.dltk.javascript.typeinfo.model.Constructor;
import org.eclipse.dltk.javascript.typeinfo.model.GenericType;
import org.eclipse.dltk.javascript.typeinfo.model.JSType;
import org.eclipse.dltk.javascript.typeinfo.model.Member;
import org.eclipse.dltk.javascript.typeinfo.model.Method;
import org.eclipse.dltk.javascript.typeinfo.model.Parameter;
import org.eclipse.dltk.javascript.typeinfo.model.ParameterizedType;
import org.eclipse.dltk.javascript.typeinfo.model.Property;
import org.eclipse.dltk.javascript.typeinfo.model.SimpleType;
import org.eclipse.dltk.javascript.typeinfo.model.Type;
import org.eclipse.dltk.javascript.typeinfo.model.TypeInfoModelLoader;
import org.eclipse.dltk.javascript.typeinfo.model.TypeVariable;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.InternalEObject;
public class TypeSystemImpl implements ITypeSystem {
/*
* @see ITypeSystem#getKnownType(java.lang.String)
*/
public Type getKnownType(String typeName) {
return TypeInfoModelLoader.getInstance().getType(typeName);
}
/*
* @see ITypeSystem#resolveType(Type)
*/
public Type resolveType(Type type) {
if (type != null && type.isProxy()) {
return doResolveType(type);
} else {
return type;
}
}
protected Type doResolveType(Type type) {
final String typeName = URI.decode(((InternalEObject) type).eProxyURI()
.fragment());
final Type resolved = TypeInfoModelLoader.getInstance().getType(
typeName);
if (resolved != null) {
return resolved;
}
return type;
}
/*
* @see ITypeSystem#valueOf(Member)
*/
public IValue valueOf(IRMember member) {
return null;
}
private final Object lock = new Object();
private final Map<Type, RTypeDeclaration> declarations = new HashMap<Type, RTypeDeclaration>();
public IRTypeDeclaration convert(Type type) {
return convert0(resolveType(type));
}
final IRTypeDeclaration convert0(Type type) {
final ITypeSystem preferred = type.getMetaType()
.getPreferredTypeSystem(type);
if (preferred != null && preferred != this) {
return convertInPreferred(preferred, type);
}
return convert1(type);
}
private IRTypeDeclaration convertInPreferred(ITypeSystem typeSystem,
Type type) {
if (typeSystem instanceof TypeSystemImpl) {
return ((TypeSystemImpl) typeSystem).convert1(type);
} else {
return typeSystem.convert(type);
}
}
final IRTypeDeclaration convert1(Type type) {
synchronized (lock) {
return convertType(type, null);
}
}
/**
* Clears the cached data of this type system.
*/
public void reset() {
synchronized (lock) {
declarations.clear();
parameterized.clear();
contextualized.clear();
if (values != null) {
values.clear();
}
}
}
private RTypeDeclaration convertType(Type type, Set<Type> processedTypes) {
{
final RTypeDeclaration declaration = declarations.get(type);
if (declaration != null) {
return declaration;
}
}
if (processedTypes == null) {
processedTypes = new HashSet<Type>();
} else { // when called for super type/traits
final ITypeSystem preferred = type.getMetaType()
.getPreferredTypeSystem(type);
if (preferred != null && preferred != this) {
return (RTypeDeclaration) convertInPreferred(preferred, type);
}
}
if (!processedTypes.add(type)) {
return null;
}
final RTypeDeclaration declaration = new RTypeDeclaration(this, type);
declarations.put(type, declaration);
if (TRACE) {
log("Creating", declaration, "declarations.size=",
declarations.size());
}
buildType(declaration, type, type.getAdditionalMembers(null),
processedTypes);
return declaration;
}
private void buildType(final RTypeDeclaration declaration, Type type,
@Nullable final Member[] additionalMembers, Set<Type> processedTypes) {
final SimpleType superType = type.getSuperTypeExpr();
if (superType != null && superType.getTarget() != null) {
if (superType.getTarget() instanceof GenericType) {
final GenericType generic = (GenericType) superType.getTarget();
if (superType instanceof ParameterizedType) {
declaration.setSuperType(parameterizeType(generic, RTypes
.convert(this, ((ParameterizedType) superType)
.getActualTypeArguments())));
} else if (declaration.isParameterized()) {
declaration.setSuperType(parameterizeType(generic,
declaration.getActualTypeArguments()));
} else {
declaration.setSuperType(convertType(generic,
processedTypes));
}
} else {
declaration.setSuperType(convertType(superType.getTarget(),
processedTypes));
}
}
final List<RTypeDeclaration> traits = new ArrayList<RTypeDeclaration>(
type.getTraits().size());
for (Type trait : type.getTraits()) {
final RTypeDeclaration t = convertType(trait, processedTypes);
if (t != null) {
traits.add(t);
}
}
declaration.setTraits(toImmutableList(traits));
final List<IRMember> members = new ArrayList<IRMember>(type
.getMembers().size()
+ (additionalMembers != null ? additionalMembers.length : 0));
for (Member member : type.getMembers()) {
members.add(convertMember(member, declaration));
}
if (additionalMembers != null && additionalMembers.length != 0) {
for (Member member : additionalMembers) {
members.add(convertMember(member, declaration));
}
}
declaration.setMembers(toImmutableList(members));
final List<IRConstructor> constructors = new ArrayList<IRConstructor>(
type.getConstructors().size());
for (Constructor constructor : type.getConstructors()) {
constructors.add(convertConstructor(constructor, declaration));
}
declaration.setConstructors(toImmutableList(constructors));
final Constructor staticConstructor = type.getStaticConstructor();
if (staticConstructor != null) {
declaration.setStaticConstructor(convertConstructor(
staticConstructor, declaration));
}
}
private static <E> List<E> toImmutableList(List<E> list) {
// TODO (alex) introduce real immutable lists
return list.isEmpty() ? Collections.<E> emptyList() : list;
}
private void log(Object... args) {
if (!TRACE)
return;
final StringBuilder sb = new StringBuilder(128);
sb.append('[').append(Thread.currentThread().getName()).append("] ");
sb.append(this.toString());
sb.append(":");
boolean space = true;
for (int i = 0; i < args.length; ++i) {
if (space) {
sb.append(' ');
}
final Object arg = args[i];
sb.append(arg);
space = !(arg instanceof String && ((String) arg).endsWith("="));
}
System.out.println(sb);
}
private IRType convert(final JSType type) {
return RTypes.create(this, type);
}
public IRType getTypeVariable(TypeVariable variable) {
for (int i = typeVariables.size(); --i >= 0;) {
final IRType type = typeVariables.get(i).getTypeVariable(variable);
if (type != null) {
return type;
}
}
return null;
}
protected IRMember convertMember(Member member,
IRTypeDeclaration declaration) {
if (member instanceof Method) {
return convertMethod((Method) member, declaration);
} else {
assert member instanceof Property;
return convertProperty((Property) member, declaration);
}
}
private IRMember convertProperty(Property property,
IRTypeDeclaration declaration) {
if (declaration != null && isLazy()) {
return new RProperty(property, declaration);
} else {
return new RProperty(property, convert(property.getType()),
declaration);
}
}
private IRMember convertMethod(Method method, IRTypeDeclaration declaration) {
if (declaration != null && isLazy()) {
return new RMethod(method, declaration);
} else {
return new RMethod(method, convert(method.getType()),
convertParameters(this, method.getParameters()),
declaration);
}
}
private IRConstructor convertConstructor(Constructor constructor,
RTypeDeclaration declaration) {
return new RConstructor(constructor, declaration); // always lazy
}
public static List<IRParameter> convertParameters(ITypeSystem typeSystem,
List<Parameter> parameters) {
if (parameters.isEmpty()) {
return Collections.emptyList();
}
final List<IRParameter> result = new ArrayList<IRParameter>(
parameters.size());
for (Parameter param : parameters) {
final IRType type = param.getType() != null ? RTypes.create(
typeSystem, param.getType()) : RTypes.any();
result.add(new RParameter(param.getName(), type, param.getKind()));
}
return toImmutableList(result);
}
private static class ParameterizedTypeKey {
final GenericType type;
final IRType[] parameters;
public ParameterizedTypeKey(GenericType type,
List<? extends IRType> params) {
this.type = type;
final int expectedParamCount = type.getTypeParameters().size();
this.parameters = new IRType[expectedParamCount];
for (int i = 0; i < expectedParamCount; ++i) {
IRType t;
if (i < params.size()) {
t = params.get(i);
if (t == null) {
t = none();
}
} else {
t = none();
}
this.parameters[i] = t;
}
}
@Override
public int hashCode() {
return type.hashCode() ^ Arrays.hashCode(parameters);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ParameterizedTypeKey) {
final ParameterizedTypeKey other = (ParameterizedTypeKey) obj;
return type == other.type
&& Arrays.equals(parameters, other.parameters);
}
return false;
}
@Override
public String toString() {
final StringBuilder parameterizedName = new StringBuilder(type
.getName().length() + parameters.length * 16);
parameterizedName.append(type.getName());
parameterizedName.append("<");
for (int i = 0; i < parameters.length; ++i) {
if (i > 0) {
parameterizedName.append(",");
}
parameterizedName.append(parameters[i].getName());
}
parameterizedName.append(">");
return parameterizedName.toString();
}
}
private final Map<ParameterizedTypeKey, RTypeDeclaration> parameterized = new HashMap<ParameterizedTypeKey, RTypeDeclaration>();
/*
* @see ITypeSystem#parameterize(Type, java.util.List)
*/
public IRTypeDeclaration parameterize(Type target,
List<? extends IRType> parameters) {
target = resolveType(target);
if (!(target instanceof GenericType)) {
return convert0(target);
}
synchronized (lock) {
return parameterizeType((GenericType) target, parameters);
}
}
private final List<RParameterizedTypeDeclaration> typeVariables = new ArrayList<RParameterizedTypeDeclaration>();
private RTypeDeclaration parameterizeType(GenericType genericType,
List<? extends IRType> parameters) {
final ParameterizedTypeKey key = new ParameterizedTypeKey(genericType,
parameters);
{
final RTypeDeclaration declaration = parameterized.get(key);
if (declaration != null) {
return declaration;
}
}
if (TRACE) {
log("Creating", key, "parameterized.size=", parameterized.size());
}
final RParameterizedTypeDeclaration declaration = new RParameterizedTypeDeclaration(
this, genericType, Arrays.asList(key.parameters));
parameterized.put(key, declaration);
typeVariables.add(declaration);
try {
buildType(declaration, genericType,
genericType.getAdditionalMembers(declaration
.getActualTypeArguments()), new HashSet<Type>());
} finally {
typeVariables.remove(typeVariables.size() - 1);
}
return declaration;
}
private static class ContextualizeKey {
@NonNull
final IRMember member;
@NonNull
final IRTypeDeclaration declaration;
public ContextualizeKey(@NonNull IRMember member,
@NonNull IRTypeDeclaration declaration) {
this.member = member;
this.declaration = declaration;
}
@Override
public int hashCode() {
return member.hashCode() ^ declaration.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ContextualizeKey) {
final ContextualizeKey other = (ContextualizeKey) obj;
return member.equals(other.member)
&& declaration.equals(other.declaration);
}
return false;
}
}
private final Map<ContextualizeKey, IRMember> contextualized = new HashMap<ContextualizeKey, IRMember>();
@SuppressWarnings("unchecked")
public <E extends IRMember> E contextualize(E member,
IRTypeDeclaration declaration) {
if (!isContextualizable(member)) {
return member;
}
final ContextualizeKey key = new ContextualizeKey(member, declaration);
synchronized (contextualized) {
final E cached = (E) contextualized.get(key);
if (cached != null) {
return cached;
}
final E result = contextualizeMember(member, declaration);
contextualized.put(key, result);
if (TRACE) {
log("Contextualized", result, "with", declaration,
"contextualized.size=", contextualized.size());
}
return result;
}
}
@SuppressWarnings("unchecked")
private <E extends IRMember> E contextualizeMember(E member,
IRTypeDeclaration declaration) {
final IRTypeTransformer transformer = newTypeContextualizer(declaration);
if (member instanceof IRMethod) {
final IRMethod method = (IRMethod) member;
final List<IRParameter> parameters = transformParameters(method,
transformer);
if (member instanceof IRConstructor) {
return (E) new RConstructor((Method) member.getSource(),
transformer.transform(member.getType()), parameters,
member.getDeclaringType());
} else {
return (E) new RMethod((Method) member.getSource(),
transformer.transform(member.getType()), parameters,
member.getDeclaringType());
}
} else {
assert member instanceof IRProperty;
return (E) new RProperty((Property) member.getSource(),
transformer.transform(member.getType()),
member.getDeclaringType());
}
}
protected IRTypeTransformer newTypeContextualizer(
IRTypeDeclaration declaration) {
return new TypeContextualizer(declaration);
}
private static List<IRParameter> transformParameters(final IRMethod method,
final IRTypeTransformer transformer) {
if (method.getParameterCount() == 0) {
return Collections.emptyList();
} else {
final List<IRParameter> parameters = new ArrayList<IRParameter>(
method.getParameterCount());
for (IRParameter parameter : method.getParameters()) {
final IRType newType = transformer.transform(parameter
.getType());
if (newType != parameter.getType()) {
parameters.add(new RParameter(parameter.getName(), newType,
parameter.getKind()));
} else {
parameters.add(parameter);
}
}
return toImmutableList(parameters);
}
}
private class TypeContextualizer implements IRTypeTransformer {
private final IRTypeDeclaration contextType;
public TypeContextualizer(IRTypeDeclaration declaration) {
this.contextType = declaration;
}
public IRType transform(IRType type) {
if (type instanceof IRContextualizableType) {
final IRContextualizableType c = (IRContextualizableType) type;
if (c.isContextualizable()) {
return c.contextualize(contextType);
}
}
return type != null ? type.transform(this) : null;
}
public IRTypeDeclaration transform(IRTypeDeclaration declaration) {
if (declaration.isParameterized()) {
boolean createNew = false;
final List<IRType> typeParams = new ArrayList<IRType>(
declaration.getActualTypeArguments());
for (ListIterator<IRType> i = typeParams.listIterator(); i
.hasNext();) {
final IRType type = i.next();
if (type instanceof IRContextualizableType) {
final IRContextualizableType c = (IRContextualizableType) type;
if (c.isContextualizable()) {
final IRType newType = c
.contextualize(this.contextType);
if (newType != type) {
i.set(newType);
createNew = true;
}
}
}
}
if (createNew) {
return parameterize(declaration.getSource(), typeParams);
}
}
return declaration;
}
}
private Map<Object, Object> values;
/*
* @see ITypeSystem#getValue(java.lang.Object )
*/
public Object getValue(Object key) {
assert key != null;
return values != null ? values.get(key) : null;
}
/*
* @see ITypeSystem#setValue(java.lang.Object , java.lang.Object)
*/
public void setValue(Object key, Object value) {
assert key != null;
if (values == null) {
values = new HashMap<Object, Object>();
}
values.put(key, value);
}
protected boolean isContextualizable(IRMember member) {
if (isContextualizable(member.getType())) {
return true;
}
if (member instanceof IRMethod) {
for (IRParameter parameter : ((IRMethod) member).getParameters()) {
if (isContextualizable(parameter.getType())) {
return true;
}
}
}
return false;
}
static boolean isContextualizable(IRType type) {
if (type == null) {
return false;
} else if (type instanceof IRContextualizableType) {
return ((IRContextualizableType) type).isContextualizable();
} else if (type instanceof IRArrayType) {
return isContextualizable(((IRArrayType) type).getItemType());
} else if (type instanceof IRSimpleType) {
final IRTypeDeclaration declaration = ((IRSimpleType) type)
.getDeclaration();
if (declaration.isParameterized()) {
for (IRType typeArgument : declaration.getActualTypeArguments()) {
if (isContextualizable(typeArgument)) {
return true;
}
}
}
}
return false;
}
public ITypeSystem getPrimary() {
return this;
}
protected final boolean isLazy() {
return true;
}
public static class TypeSystemStats {
private final int declarationCount;
private final int parameterizedCount;
private final int contextualizedCount;
public TypeSystemStats(int declarationCount, int parameterizedCount,
int contextualizedCount) {
this.declarationCount = declarationCount;
this.parameterizedCount = parameterizedCount;
this.contextualizedCount = contextualizedCount;
}
public int declarationCount() {
return declarationCount;
}
public int parameterizedCount() {
return parameterizedCount;
}
public int contextualizedCount() {
return contextualizedCount;
}
public boolean isEmpty() {
return declarationCount == 0 && parameterizedCount == 0
&& contextualizedCount == 0;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[declarationCount="
+ declarationCount + ",parameterizedCount="
+ parameterizedCount + ",contextualizedCount="
+ contextualizedCount + "]";
}
}
public TypeSystemStats stats() {
synchronized (lock) {
return new TypeSystemStats(declarations.size(),
parameterized.size(), contextualized.size());
}
}
private static final boolean TRACE = Boolean.valueOf(
getDebugOption("org.eclipse.dltk.javascript.core/traceTypeSystem"))
.booleanValue();
}