blob: 31c6d7819b93cee6f48b1c200a1455f4e438c9b7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 xored software, Inc.
*
* 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:
* xored software, Inc. - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.internal.javascript.ti;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.annotations.Internal;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.javascript.ast.Script;
import org.eclipse.dltk.javascript.core.JavaScriptPlugin;
import org.eclipse.dltk.javascript.internal.core.ThreadTypeSystemImpl;
import org.eclipse.dltk.javascript.parser.JSProblem;
import org.eclipse.dltk.javascript.parser.JSProblemReporter;
import org.eclipse.dltk.javascript.typeinference.IValueCollection;
import org.eclipse.dltk.javascript.typeinference.IValueParent;
import org.eclipse.dltk.javascript.typeinference.IValueReference;
import org.eclipse.dltk.javascript.typeinference.ReferenceKind;
import org.eclipse.dltk.javascript.typeinfo.AttributeKey;
import org.eclipse.dltk.javascript.typeinfo.IElementResolver;
import org.eclipse.dltk.javascript.typeinfo.ILocalTypeReference;
import org.eclipse.dltk.javascript.typeinfo.IMemberEvaluator;
import org.eclipse.dltk.javascript.typeinfo.IModelBuilder;
import org.eclipse.dltk.javascript.typeinfo.IRLocalType;
import org.eclipse.dltk.javascript.typeinfo.IRMember;
import org.eclipse.dltk.javascript.typeinfo.IRRecordType;
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.ITypeInfoContext;
import org.eclipse.dltk.javascript.typeinfo.ITypeProvider;
import org.eclipse.dltk.javascript.typeinfo.ITypeSystem;
import org.eclipse.dltk.javascript.typeinfo.RTypes;
import org.eclipse.dltk.javascript.typeinfo.ReferenceSource;
import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager;
import org.eclipse.dltk.javascript.typeinfo.TypeMode;
import org.eclipse.dltk.javascript.typeinfo.TypeUtil;
import org.eclipse.dltk.javascript.typeinfo.model.JSType;
import org.eclipse.dltk.javascript.typeinfo.model.Member;
import org.eclipse.dltk.javascript.typeinfo.model.Property;
import org.eclipse.dltk.javascript.typeinfo.model.RecordType;
import org.eclipse.dltk.javascript.typeinfo.model.SimpleType;
import org.eclipse.dltk.javascript.typeinfo.model.Type;
import org.eclipse.dltk.javascript.typeinfo.model.TypeInfoModelFactory;
import org.eclipse.dltk.javascript.typeinfo.model.TypeInfoModelLoader;
import org.eclipse.dltk.javascript.typeinfo.model.TypeKind;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
public class TypeInferencer2 extends TypeSystemImpl implements
ITypeInferenceContext {
private TypeInferencerVisitor visitor;
private ReferenceSource source;
private void initializeVisitor() {
if (visitor == null) {
visitor = new TypeInferencerVisitor(this);
}
visitor.initialize();
}
public void setVisitor(TypeInferencerVisitor visitor) {
this.visitor = visitor;
}
public void setModelElement(IModelElement modelElement) {
setSource(ReferenceSource.create(modelElement));
}
public void setSource(ReferenceSource source) {
this.source = source;
}
private static final boolean DEBUG = false;
public void doInferencing(Script script) {
if (DEBUG)
System.out.println("Visiting "
+ source
+ " with "
+ (visitor != null ? visitor.getClass().getName()
: "Default") + " in "
+ Thread.currentThread().getName());
final ITypeSystem saved = CURRENT.get();
try {
((ThreadTypeSystemImpl) CURRENT).set(this);
elements.clear();
modelBuilders = null;
typeProviders = null;
initializeVisitor();
visitor.visit(script);
visitor.done();
// IValueCollection collection = visitor.getCollection();
// visitor = null;
// return collection;
} catch (PositionReachedException e) {
// visitor = null;
throw e;
} catch (TypeInferencerVisitorBase.TIWrappedException e) {
log(e.getCause());
} catch (RuntimeException e) {
log(e);
} catch (AssertionError e) {
log(e);
} finally {
((ThreadTypeSystemImpl) CURRENT).set(saved);
}
// return null;
}
protected void log(Throwable e) {
final JSProblemReporter reporter = visitor.getProblemReporter();
if (reporter != null) {
reporter.reportProblem(new JSProblem(e));
}
JavaScriptPlugin.error(e);
}
public IValueReference evaluate(ASTNode node) {
initializeVisitor();
return visitor.visit(node);
}
public IValueCollection getCollection() {
return visitor.getCollection();
}
public IValueCollection currentCollection() {
return visitor.peekContext();
}
/**
* Implementation of the {@link ILocalTypeReference}
*/
private static class LocalType implements ILocalTypeReference {
final Type type;
private boolean enabled;
public LocalType(Type type) {
this.type = type;
this.enabled = true;
}
public Type getType() {
return type;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean value) {
this.enabled = value;
}
@Override
public String toString() {
return "(" + type + ", enabled=" + enabled + ")";
}
}
/**
* Container for potentially multiple local types with the same name.
*/
@SuppressWarnings("serial")
private static class LocalTypeBucket extends ArrayList<LocalType> {
public LocalTypeBucket() {
super(1);
}
Type unknownType;
}
/**
* Local (i.e. temporary and transient) types, which are manually registered
* using {@link ITypeInfoContext#registerLocalType(Type, Object)}. The
* registration is valid only within the same module, and this field is
* cleared before processing the next module.
*/
private final Map<String, LocalTypeBucket> localTypes = new HashMap<String, LocalTypeBucket>();
/*
* @see ITypeInfoContext#registerLocalType(Type)
*/
public ILocalTypeReference registerLocalType(Type type) {
assert type != null;
final String name = type.getName();
assert name != null;
assert type.eResource() == null;
final LocalType result;
synchronized (localTypes) {
LocalTypeBucket locals = localTypes.get(name);
if (locals == null) {
locals = new LocalTypeBucket();
localTypes.put(name, locals);
} else {
for (LocalType localType : locals) {
if (type.equals(localType.type)) {
return localType;
}
}
}
result = new LocalType(type);
typeRS.addToResource(type);
locals.add(result);
}
return result;
}
/*
* @see ITypeInfoContext#getLocalTypes(String)
*/
public ILocalTypeReference[] getLocalTypes(String name) {
synchronized (localTypes) {
LocalTypeBucket locals = localTypes.get(name);
if (locals != null) {
return locals.toArray(new ILocalTypeReference[locals.size()]);
} else {
return new ILocalTypeReference[0];
}
}
}
private final Map<String, Type> types = new HashMap<String, Type>();
public Type getType(String typeName) {
if (typeName == null || typeName.length() == 0) {
return null;
}
final boolean queryProviders = canQueryTypeProviders();
return getType(typeName, null, queryProviders, true, !queryProviders,
true);
}
public SimpleType getTypeRef(String typeName) {
return TypeUtil.ref(getType(typeName));
}
@Override
public Type getKnownType(String typeName) {
return getKnownType(typeName, null);
}
public Type getKnownType(String typeName, TypeMode mode) {
if (typeName == null || typeName.length() == 0) {
return null;
}
final boolean queryProviders = canQueryTypeProviders();
return getType(typeName, mode, queryProviders, true, !queryProviders,
false);
}
/**
* Allows creation of unknown types in {@link #resolveType(Type)}. Most of
* the time it doesn't matter, but in some special cases original proxy
* should be kept, that's why this method is available for override.
*/
protected boolean resolveToUnknown() {
return true;
}
@Override
public Type doResolveType(Type type) {
final String typeName = URI.decode(((InternalEObject) type).eProxyURI()
.fragment());
final Type resolved = getType(typeName, null, true, true, false,
resolveToUnknown());
if (resolved != null) {
return resolved;
}
return type;
}
public Set<String> listTypes(TypeMode mode, String prefix) {
Set<String> result = new HashSet<String>();
final TypeInfoModelLoader loader = TypeInfoModelLoader.getInstance();
Set<String> typeNames = mode == TypeMode.CODE ? loader
.listTypeLiterals(prefix) : loader.listTypes(prefix);
if (typeNames != null) {
result.addAll(typeNames);
}
for (ITypeProvider provider : getTypeProviders()) {
typeNames = provider.listTypes(this, mode, prefix);
if (typeNames != null) {
result.addAll(typeNames);
}
}
String lowerPrefix = prefix.toLowerCase();
synchronized (localTypes) {
Set<String> localTypeNames = localTypes.keySet();
for (String name : localTypeNames) {
String typeName = name;
if (typeName.toLowerCase().equals(lowerPrefix))
continue; // skip the thing that is already typed.
int index = typeName.lastIndexOf('.');
if (index != -1) {
typeName = typeName.substring(index + 1);
}
if (typeName.toLowerCase().startsWith(lowerPrefix)) {
result.add(name);
}
}
}
return result;
}
/**
* @return the source
*/
public ReferenceSource getSource() {
return source;
}
public IValueCollection getTopValueCollection() {
if (resolve) {
for (IMemberEvaluator evaluator : TypeInfoManager
.getMemberEvaluators()) {
final IValueCollection collection = evaluator
.getTopValueCollection(this);
if (collection != null) {
return collection;
}
}
}
return null;
}
public IModelElement getModelElement() {
return source != null ? source.getModelElement() : null;
}
private enum TypeResolveMode {
SIMPLE, PROXY, UNKNOWN
}
@Internal
Type getType(String typeName, TypeMode mode, boolean queryProviders,
boolean queryPredefined, boolean allowProxy, boolean allowUnknown) {
synchronized (localTypes) {
final LocalTypeBucket locals = localTypes.get(typeName);
if (locals != null) {
for (LocalType localType : locals) {
if (localType.isEnabled()) {
return localType.type;
}
}
if (locals.unknownType != null) {
return allowUnknown ? locals.unknownType : null;
}
}
}
Type type;
synchronized (types) {
type = types.get(typeName);
}
if (type != null) {
return type;
}
type = loadType(typeName, mode, queryProviders, queryPredefined);
if (type != null) {
validateTypeInfo(type);
synchronized (types) {
types.put(typeName, type);
}
typeRS.addToResource(type);
return type;
}
if (allowProxy) {
return TypeUtil.createProxy(typeName);
}
if (allowUnknown) {
return createUnknown(typeName);
}
return null;
}
private void validateTypeInfo(Type type) {
final Resource resource = ((EObject) type).eResource();
if (resource != null) {
final ResourceSet resourceSet = resource.getResourceSet();
if (resourceSet != null) {
Assert.isLegal(!(resourceSet instanceof TypeResourceSet),
"Type " + type.getName() + " has invalid resource: "
+ resource + " (" + resourceSet.getClass()
+ ")");
}
}
// TODO check that member referenced types are contained or proxy
}
@Internal
Type createUnknown(String typeName) {
final Type type = TypeInfoModelFactory.eINSTANCE.createType();
type.setName(typeName);
type.setKind(TypeKind.UNKNOWN);
synchronized (localTypes) {
LocalTypeBucket locals = localTypes.get(typeName);
if (locals != null) {
for (LocalType localType : locals) {
if (localType.isEnabled()) {
return localType.type;
}
}
if (locals.unknownType != null) {
return locals.unknownType;
}
} else {
locals = new LocalTypeBucket();
localTypes.put(typeName, locals);
}
locals.unknownType = type;
}
typeRS.add(type);
return type;
}
private final Map<String, Boolean> activeTypeRequests = new HashMap<String, Boolean>();
private boolean canQueryTypeProviders() {
return activeTypeRequests.isEmpty();
}
private Type loadType(String typeName, TypeMode mode,
boolean queryProviders, boolean queryPredefined) {
if (queryProviders
&& activeTypeRequests.put(typeName, Boolean.FALSE) == null) {
try {
for (ITypeProvider provider : getTypeProviders()) {
final Type type = provider.getType(this, mode, typeName);
if (type != null && !type.eIsProxy()) {
return type;
}
}
} finally {
activeTypeRequests.remove(typeName);
}
}
if (queryPredefined) {
final TypeInfoModelLoader loader = TypeInfoModelLoader
.getInstance();
final Type type;
if (mode == TypeMode.CODE) {
type = loader.getTypeLiteral(typeName);
} else {
type = loader.getType(typeName);
}
if (type != null) {
return type;
}
}
return null;
}
private ITypeProvider[] typeProviders = null;
public ITypeProvider[] getTypeProviders() {
if (typeProviders == null) {
typeProviders = createTypeProviders();
}
return typeProviders;
}
protected ITypeProvider[] createTypeProviders() {
return TypeInfoManager.createTypeProviders(this);
}
private class TypeResourceSet extends ResourceSetImpl {
public TypeResourceSet() {
TypeInfoModelLoader.getInstance().initializeURIMap(this);
}
@Override
public EObject getEObject(URI uri, boolean loadOnDemand) {
if (TypeUtil.isTypeProxy(uri)) {
final String typeName = URI.decode(uri.fragment());
final Type type = resolveTypeProxy(typeName);
if (type == null) {
return createUnknown(typeName);
} else {
return type;
}
}
return super.getEObject(uri, loadOnDemand);
}
protected Type resolveTypeProxy(String typeName) {
return getType(typeName, null, true, false, false, false);
}
public synchronized Resource getResource() {
if (typesResource == null) {
typesResource = new ResourceImpl(
TypeUtil.createProxyResourceURI());
getResources().add(typesResource);
}
return typesResource;
}
private Resource typesResource = null;
public void addToResource(final Type type) {
final EObject object = type;
if (object.eResource() == null) {
add(type);
}
}
protected synchronized void add(Type type) {
getResource().getContents().add(type);
}
protected synchronized void removeAll(Collection<Type> types) {
getResource().getContents().removeAll(types);
}
}
private final TypeResourceSet typeRS = new TypeResourceSet();
private boolean resolve = true;
private Map<String, IRMember> elements = new HashMap<String, IRMember>();
public IRMember resolve(String name) {
if (name == null)
return null;
{
final IRMember element = elements.get(name);
if (element != null) {
return element;
}
}
Member element = TypeInfoModelLoader.getInstance().getMember(name);
if (element != null) {
final IRMember r = convertMember(element, null);
elements.put(name, r);
return r;
}
if (resolve) {
for (IElementResolver resolver : TypeInfoManager
.getElementResolvers()) {
element = resolver.resolveElement(this, name);
if (element != null) {
final IRMember r = convertMember(element, null);
elements.put(name, r);
return r;
}
}
}
return null;
}
@Override
public IValue valueOf(IRMember member) {
if (!(member.getSource() instanceof Member)) {
return null;
}
final Member source = (Member) member.getSource();
for (IMemberEvaluator evaluator : TypeInfoManager.getMemberEvaluators()) {
final IValueCollection collection = evaluator.valueOf(this, source);
if (collection != null) {
if (collection instanceof IValueProvider) {
IValue value = ((IValueProvider) collection).getValue();
if (member.getType() != null) {
value.setDeclaredType(member.getType());
}
if (value.getKind() == ReferenceKind.UNKNOWN) {
if (source instanceof Property) {
value.setKind(ReferenceKind.PROPERTY);
} else {
value.setKind(ReferenceKind.METHOD);
}
}
if (value instanceof ImmutableValue) {
ElementValue elementValue = ElementValue
.createFor(member);
return new ValueWithElementValue(
(ImmutableValue) value, elementValue);
}
return value;
} else {
break;
}
}
}
return null;
}
public Set<String> listGlobals(String prefix) {
final Set<String> result = new HashSet<String>();
for (Member member : TypeInfoModelLoader.getInstance().listMembers(
prefix)) {
result.add(member.getName());
}
for (IElementResolver resolver : TypeInfoManager.getElementResolvers()) {
Set<String> globals = resolver.listGlobals(this, prefix);
if (globals != null) {
result.addAll(globals);
}
}
return result;
}
public void setDoResolve(boolean resolve) {
this.resolve = resolve;
}
private IModelBuilder[] modelBuilders = null;
public IModelBuilder[] getModelBuilders() {
if (modelBuilders == null) {
modelBuilders = TypeInfoManager.getModelBuilders(this);
}
return modelBuilders;
}
private final Map<AttributeKey<?>, List<Object>> attributes = new HashMap<AttributeKey<?>, List<Object>>();
@SuppressWarnings("unchecked")
public <T> T getAttribute(AttributeKey<T> key) {
final List<Object> values = attributes.get(key);
return values != null && !values.isEmpty() ? (T) values.get(values
.size() - 1) : null;
}
public <T> void pushAttribute(AttributeKey<T> key, T value) {
List<Object> values = attributes.get(key);
if (values == null) {
values = new ArrayList<Object>(4);
attributes.put(key, values);
}
values.add(value);
}
@SuppressWarnings("unchecked")
public <T> T popAttribute(AttributeKey<T> key) {
final List<Object> values = attributes.get(key);
if (values != null && !values.isEmpty()) {
return (T) values.remove(values.size() - 1);
} else {
return null;
}
}
@Override
public String toString() {
final TypeInferencerVisitor v = visitor;
String className = getClass().getSimpleName();
if (className.length() == 0) {
className = getClass().getName();
}
return className + "(" + source + ","
+ (v != null ? v.getClass().getSimpleName() : null) + ")";
}
public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
if (adapter == ITypeInfoContext.class || adapter == ITypeSystem.class) {
return this;
} else if (adapter == ReferenceSource.class) {
return getSource();
} else {
return null;
}
}
/**
* Removes all registered local types, this method is called by the
* {@link org.eclipse.dltk.internal.javascript.validation.TypeInfoValidator}
* after each module is processed.
*/
public void resetLocalState() {
final List<Type> copy;
synchronized (localTypes) {
copy = new ArrayList<Type>();
for (LocalTypeBucket locals : localTypes.values()) {
for (LocalType localType : locals) {
copy.add(localType.type);
}
if (locals.unknownType != null) {
copy.add(locals.unknownType);
}
}
localTypes.clear();
}
if (!copy.isEmpty()) {
typeRS.removeAll(copy);
}
attributes.clear();
}
/*
* @see ITypeInfoContext#contextualize(JSType)
*/
public IRType contextualize(JSType type) {
if (type != null) {
final IRType rt = RTypes.create(this, type);
final IRTypeDeclaration contextTypeDeclaration = getAttribute(CONTEXTUALIZE_WITH);
if (contextTypeDeclaration != null && isContextualizable(rt)) {
final IRTypeTransformer transformer = newTypeContextualizer(contextTypeDeclaration);
return transformer.transform(rt);
}
return rt;
} else {
return null;
}
}
private Map<String, Object> recordTypes = new HashMap<String, Object>();
public void registerRecordType(RecordType type) {
recordTypes.put(type.getName(), type);
}
public IRRecordType resolveRecordType(String name) {
Object recordType = recordTypes.get(name);
if (recordType instanceof IRRecordType)
return (IRRecordType) recordType;
if (recordType instanceof RecordType) {
IRRecordType irType = RTypes.recordType();
recordTypes.put(name, irType);
irType.init(this, ((RecordType) recordType).getMembers());
return irType;
}
return null;
}
public IRLocalType resolveLocalType(String name) {
if (visitor == null)
return null;
IValueReference result = null;
IValueCollection currentCollection = currentCollection();
if (name.indexOf('.') != -1) {
String[] scopes = name.split("\\.");
IValueParent child = currentCollection;
for (String scope : scopes) {
child = child.getChild(scope);
}
result = (IValueReference) child;
} else {
while (currentCollection != null) {
IValueReference child = currentCollection.getChild(name);
if (child.exists()) {
result = child;
break;
} else {
currentCollection = currentCollection.getParent();
}
}
}
if (result != null && result.getKind() == ReferenceKind.FUNCTION) {
return RTypes.localType(name, result);
}
return null;
}
}