package org.yinwang.pysonar;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yinwang.pysonar.ast.Node;
import org.yinwang.pysonar.types.ModuleType;
import org.yinwang.pysonar.types.Type;
import java.util.HashSet;
import java.util.Set;
/**
* An {@code NBinding} collects information about a fully qualified name (qname)
* in the code graph.<p>
*
* Each qname has an associated {@link org.yinwang.pysonar.types.Type}. When a particular qname is
* assigned values of different types at different locations, its type is
* represented as a {@link org.yinwang.pysonar.types.UnionType}. <p>
*
* Each qname has a set of one or more definitions, and a set of zero or
* more references. Definitions and references correspond to code locations. <p>
*/
public class Binding implements Comparable<Object> {
/**
* In addition to its type, each binding has a {@link Kind} enumeration that
* attempts to represent the structural role the name plays in the code.
* This is a rough categorization that takes into account type information,
* structural (AST) information, and possibly other semantics. It can help
* IDEs with presentation decisions, and can be useful to expose to users as
* a parameter for filtering queries on the graph.
*/
public enum Kind {
ATTRIBUTE, // attr accessed with "." on some other object
CLASS, // class definition
CONSTRUCTOR, // __init__ functions in classes
FUNCTION, // plain function
METHOD, // static or instance method
MODULE, // file
PARAMETER, // function param
SCOPE, // top-level variable ("scope" means we assume it can have attrs)
VARIABLE // local variable
}
// The indexer is heavily memory-constrained, so these sets are initially
// small. The vast majority of bindings have only one definition.
private static final int DEF_SET_INITIAL_CAPACITY = 1;
private static final int REF_SET_INITIAL_CAPACITY = 1;
// yinw: C-style bit-field changed to straightfoward booleans.
// The compiler should compact the space unless it is stupid.
private boolean isStatic = false; // static fields/methods
private boolean isSynthetic = false; // auto-generated bindings
private boolean isReadonly = false; // non-writable attributes
private boolean isDeprecated = false; // documented as deprecated
private boolean isBuiltin = false; // not from a source file
@NotNull
private String name; // unqualified name
@NotNull
private String qname; // qualified name
private Type type; // inferred type
public Kind kind; // name usage context
public int tag; // control-flow tag
private Set<Def> defs; // definitions (may be multiple)
private Set<Ref> refs;
public Binding(@NotNull String id, Node node, Type type, Kind kind, int tag) {
this(id, node, type, kind);
this.tag = tag;
}
public Binding(@NotNull String id, Node node, @NotNull Type type, @NotNull Kind kind) {
name = id;
qname = type.getTable().getPath();
defs = new HashSet<>(DEF_SET_INITIAL_CAPACITY);
addDef(node);
this.type = type;
this.kind = kind;
Indexer.idx.registerBinding(this);
}
/**
* Returns the unqualified name.
*/
@NotNull
public String getName() {
return name;
}
/**
* Sets the binding's qualified name. This should in general be the
* same as {@code binding.getType().getTable().getPath()}.
*/
public void setQname(String qname) {
this.qname = qname;
}
/**
* Returns the qualified name.
*/
@NotNull
public String getQname() {
return qname;
}
public void addDef(@Nullable Node node) {
if (node != null) {
Def def = new Def(node, this);
addDef(def);
}
}
public void addDef(Def def) {
def.setBinding(this);
Set<Def> defs = getDefs();
defs.add(def);
if (def.isURL()) {
markBuiltin();
}
}
public void addRef(Ref ref) {
getRefs().add(ref);
}
// Returns one definition (even if there are many)
@NotNull
public Def getSingle() {
return getDefs().iterator().next();
}
public void setType(Type type) {
this.type = type;
}
public Type getType() {
return type;
}
public void setKind(Kind kind) {
this.kind = kind;
}
public Kind getKind() {
return kind;
}
public void markStatic() {
isStatic = true;
}
public boolean isStatic() {
return isStatic;
}
public void markSynthetic() {
isSynthetic = true;
}
public boolean isSynthetic() {
return isSynthetic;
}
public void markReadOnly() {
isReadonly = true;
}
public boolean isReadOnly() {
return isReadonly;
}
public boolean isDeprecated() {
return isDeprecated;
}
public void markDeprecated() {
isDeprecated = true;
}
public boolean isBuiltin() {
return isBuiltin;
}
public void markBuiltin() {
isBuiltin = true;
}
/**
* Bindings can be sorted by their location for outlining purposes.
*/
public int compareTo(@NotNull Object o) {
return getSingle().getStart() - ((Binding)o).getSingle().getStart();
}
/**
* Return the (possibly empty) set of definitions for this binding.
* @return the defs
*/
public Set<Def> getDefs() {
if (defs == null) {
defs = new HashSet<>(DEF_SET_INITIAL_CAPACITY);
}
return defs;
}
@Nullable
public Def getDef() {
if (defs == null || defs.isEmpty()) {
return null;
} else {
return defs.iterator().next();
}
}
/**
* Returns the number of definitions found for this binding.
*/
public int getNumDefs() {
return defs == null ? 0 : defs.size();
}
public boolean hasRefs() {
return refs == null ? false : !refs.isEmpty();
}
public int getNumRefs() {
return refs == null ? 0 : refs.size();
}
/**
* Returns the set of references to this binding.
*/
public Set<Ref> getRefs() {
if (refs == null) {
refs = new HashSet<>(REF_SET_INITIAL_CAPACITY);
}
return refs;
}
/**
* Returns a filename associated with this binding, for debug
* messages.
* @return the filename associated with the type (if present)
* or the first definition (if present), otherwise a string
* describing what is known about the binding's source.
*/
@Nullable
public String getFirstFile() {
Type bt = getType();
if (bt instanceof ModuleType) {
String file = bt.asModuleType().getFile();
return file != null ? file : "<built-in module>";
}
if (defs != null) {
for (Def def : defs) {
String file = def.getFile();
if (file != null) {
return file;
}
}
return "<built-in binding>";
}
return "<unknown source>";
}
@NotNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("<Binding:");
sb.append(":qname=").append(qname);
sb.append(":type=").append(type);
sb.append(":kind=").append(kind);
sb.append(":defs=").append(defs);
sb.append(":refs=");
if (getRefs().size() > 10) {
sb.append("[");
sb.append(refs.iterator().next());
sb.append(", ...(");
sb.append(refs.size() - 1);
sb.append(" more)]");
} else {
sb.append(refs);
}
sb.append(">");
return sb.toString();
}
/*
* Multiple bindings can exist for the same qname, but they should appear at
* different locations. Otherwise they are considered the same by the equals
* method of NBinding.
*/
@Override
public boolean equals(Object o) {
if (o instanceof Binding) {
Binding other = (Binding) o;
if (other.getDef() != null) {
return other.getDef().equals(getDef());
}
}
return false;
}
}