org.python.indexer.ast.NNode Maven / Gradle / Ivy
Show all versions of jython-slim Show documentation
/**
* Copyright 2009, Google Inc. All rights reserved.
* Licensed to PSF under a Contributor Agreement.
*/
package org.python.indexer.ast;
import org.python.indexer.Indexer;
import org.python.indexer.IndexingException;
import org.python.indexer.NBinding;
import org.python.indexer.Scope;
import org.python.indexer.types.NClassType;
import org.python.indexer.types.NFuncType;
import org.python.indexer.types.NType;
import org.python.indexer.types.NUnionType;
import org.python.indexer.types.NUnknownType;
import java.util.List;
public abstract class NNode implements java.io.Serializable {
static final long serialVersionUID = 3682719481356964898L;
private int start = 0;
private int end = 1;
protected NNode parent = null;
/**
* This is marked transient to prevent serialization. We re-resolve ASTs
* after deserializing them. It is private to ensure that the type is never
* {@code null}, as much code in the indexer assumes this precondition.
*/
private transient NType type = Indexer.idx.builtins.None;
public NNode() {
}
public NNode(int start, int end) {
setStart(start);
setEnd(end);
}
public void setParent(NNode parent) {
this.parent = parent;
}
public NNode getParent() {
return parent;
}
public NNode getAstRoot() {
if (parent == null) {
return this;
}
return parent.getAstRoot();
}
public void setStart(int start) {
this.start = start;
}
public void setEnd(int end) {
this.end = end;
}
public int start() {
return start;
}
public int end() {
return end;
}
public int length() {
return end - start;
}
/**
* Utility alias for {@code getType().getTable()}.
*/
public Scope getTable() {
return getType().getTable();
}
/**
* Returns the type for this node. It is never {@code null}.
* If the node has not been resolved, the type will default to
* {@code Indexer.idx.builtins.None}.
*/
public NType getType() {
if (type == null) {
type = Indexer.idx.builtins.None;
}
return type;
}
/**
* Sets the type for the node.
* @param newType the new type
* @return {@code newType}
* @throws IllegalArgumentException if {@code newType} is {@code null}
*/
public NType setType(NType newType) {
if (newType == null) {
throw new IllegalArgumentException();
}
return type = newType;
}
/**
* Adds a new type for the node, creating a union of the previous type
* and the new type.
* @param newType the new type
* @return the resulting type for the node
* @throws IllegalArgumentException if {@code newType} is {@code null}
*/
public NType addType(NType newType) {
if (newType == null) {
throw new IllegalArgumentException();
}
return type = NUnionType.union(getType(), newType);
}
/**
* Returns {@code true} if this is a name-binding node. Includes functions/lambdas,
* function/lambda params, classes, assignments, imports, and implicit assignment via for
* statements and except clauses.
*
* @see PEP 227
*/
public boolean bindsName() {
return false;
}
/**
* Called by resolver to bind names into the passed scope.
*/
protected void bindNames(Scope s) throws Exception {
throw new UnsupportedOperationException("Not a name-binding node type");
}
/**
* @return the path to the code that generated this AST
*/
public String getFile() {
return parent != null ? parent.getFile() : null;
}
public void addChildren(NNode... nodes) {
if (nodes != null) {
for (NNode n : nodes) {
if (n != null) {
n.setParent(this);
}
}
}
}
public void addChildren(List extends NNode> nodes) {
if (nodes != null) {
for (NNode n : nodes) {
if (n != null) {
n.setParent(this);
}
}
}
}
private static NType handleExceptionInResolve(NNode n, Throwable t) {
Indexer.idx.handleException("Unable to resolve: " + n + " in " + n.getFile(), t);
return new NUnknownType();
}
public static NType resolveExpr(NNode n, Scope s) {
if (n == null) {
return new NUnknownType();
}
// This try-catch enables error recovery when there are bugs in
// the indexer. Rather than unwinding all the way up to the module
// level (and failing to load the module), we record an error for this
// node and continue.
try {
NType result = n.resolve(s);
if (result == null) {
Indexer.idx.warn(n + " resolved to a null type");
return n.setType(new NUnknownType());
}
return result;
} catch (IndexingException ix) {
throw ix;
} catch (Exception x) {
return handleExceptionInResolve(n, x);
} catch (StackOverflowError soe) {
String msg = "Unable to resolve: " + n + " in " + n.getFile() + " (stack overflow)";
Indexer.idx.warn(msg);
return handleExceptionInResolve(n, soe);
}
}
/**
* Node should set the resolved type in its {@link #type} field
* and also return it.
*/
public NType resolve(Scope s) throws Exception {
return getType();
}
public boolean isCall() {
return this instanceof NCall;
}
public boolean isModule() {
return this instanceof NModule;
}
public boolean isClassDef() {
return false;
}
public boolean isFunctionDef() {
return false;
}
public boolean isLambda() {
return false;
}
public boolean isName() {
return this instanceof NName;
}
protected void visitNode(NNode n, NNodeVisitor v) {
if (n != null) {
n.visit(v);
}
}
protected void visitNodeList(List extends NNode> nodes, NNodeVisitor v) {
if (nodes != null) {
for (NNode n : nodes) {
if (n != null) {
n.visit(v);
}
}
}
}
/**
* Visits this node and optionally its children.
*
* @param visitor the object to call with this node.
* If the visitor returns {@code true}, the node also
* passes its children to the visitor.
*/
public abstract void visit(NNodeVisitor visitor);
/**
* Returns the innermost enclosing scope for doing (non-attribute) name
* lookups. If the current node defines a scope, it returns the parent
* scope for name lookups.
*
* @return the enclosing function, class, instance, module or builtin scope.
* If this node has not yet been resolved, returns the builtin
* namespace.
*/
public Scope getEnclosingNamespace() {
if (parent == null || this.isModule()) {
return Indexer.idx.globaltable;
}
NNode up = this;
while ((up = up.parent) != null) {
if (up.isFunctionDef() || up.isClassDef() || up.isModule()) {
NType type = up.getType();
if (type == null || type.getTable() == null) {
return Indexer.idx.globaltable;
}
return type.getTable();
}
}
return Indexer.idx.globaltable;
}
protected void addWarning(String msg) {
Indexer.idx.putProblem(this, msg);
}
protected void addWarning(NNode loc, String msg) {
Indexer.idx.putProblem(loc, msg);
}
protected void addError(String msg) {
Indexer.idx.putProblem(this, msg);
}
protected void addError(NNode loc, String msg) {
Indexer.idx.putProblem(loc, msg);
}
/**
* Utility method to resolve every node in {@code nodes} and
* return the union of their types. If {@code nodes} is empty or
* {@code null}, returns a new {@link NUnknownType}.
*/
protected NType resolveListAsUnion(List extends NNode> nodes, Scope s) {
if (nodes == null || nodes.isEmpty()) {
return new NUnknownType();
}
NType result = null;
for (NNode node : nodes) {
NType nodeType = resolveExpr(node, s);
if (result == null) {
result = nodeType;
} else {
result = NUnionType.union(result, nodeType);
}
}
return result;
}
/**
* Resolves each element of a node list in the passed scope.
* Node list may be empty or {@code null}.
*/
protected void resolveList(List extends NNode> nodes, Scope s) {
if (nodes != null) {
for (NNode n : nodes) {
resolveExpr(n, s);
}
}
}
/**
* Assumes nodes are always traversed in increasing order of their start
* positions.
*/
static class DeepestOverlappingNodeFinder extends GenericNodeVisitor {
private int offset;
private NNode deepest;
public DeepestOverlappingNodeFinder(int offset) {
this.offset = offset;
}
/**
* Returns the deepest node overlapping the desired source offset.
* @return the node, or {@code null} if no node overlaps the offset
*/
public NNode getNode() {
return deepest;
}
@Override
public boolean dispatch(NNode node) {
// This node ends before the offset, so don't look inside it.
if (offset > node.end) {
return false; // don't traverse children, but do keep going
}
if (offset >= node.start) {
deepest = node;
return true; // visit kids
}
// this node starts after the offset, so we're done
throw new NNodeVisitor.StopIterationException();
}
}
/**
* Searches the AST for the deepest node that overlaps the specified source
* offset. Can be called from any node in the AST, as it traverses to the
* parent before beginning the search.
* @param sourceOffset the spot at which to look for a node
* @return the deepest AST node whose start is greater than or equal to the offset,
* and whose end is less than or equal to the offset. Returns {@code null}
* if no node overlaps {@code sourceOffset}.
*/
public NNode getDeepestNodeAtOffset(int sourceOffset) {
NNode ast = getAstRoot();
DeepestOverlappingNodeFinder finder = new DeepestOverlappingNodeFinder(sourceOffset);
try {
ast.visit(finder);
} catch (NNodeVisitor.StopIterationException six) {
// expected
}
return finder.getNode();
}
}