package org.yinwang.pysonar.ast; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.yinwang.pysonar.Binding; import org.yinwang.pysonar.Builtins; import org.yinwang.pysonar.Indexer; import org.yinwang.pysonar.Scope; import org.yinwang.pysonar.types.*; import java.util.*; import java.util.Set; import static org.yinwang.pysonar.Binding.Kind.ATTRIBUTE; import static org.yinwang.pysonar.Binding.Kind.CLASS; public class Call extends Node { public Node func; public List<Node> args; public List<Keyword> keywords; public Node kwargs; public Node starargs; public Call(Node func, List<Node> args, List<Keyword> keywords, Node kwargs, Node starargs, int start, int end) { super(start, end); this.func = func; this.args = args; this.keywords = keywords; this.kwargs = kwargs; this.starargs = starargs; addChildren(func, kwargs, starargs); addChildren(args); addChildren(keywords); } /** * Most of the work here is done by the static method invoke, which is also * used by Indexer.applyUncalled. By using a static method we avoid building * a NCall node for those dummy calls. */ @NotNull @Override public Type resolve(Scope s, int tag) { Type opType = resolveExpr(func, s, tag); List<Type> aTypes = resolveAndConstructList(args, s, tag); Map<String, Type> kwTypes = new HashMap<String, Type>(); for (Keyword kw : keywords) { kwTypes.put(kw.getArg(), kw.getValue().resolve(s, 0)); } Type kwargsType = kwargs == null ? null : resolveExpr(kwargs, s, tag); Type starargsType = starargs == null ? null : resolveExpr(starargs, s, tag); if (opType.isUnionType()) { Set<Type> types = opType.asUnionType().getTypes(); Type retType = Indexer.idx.builtins.unknown; for (Type funcType : types) { Type t = resolveCall(funcType, aTypes, kwTypes, kwargsType, starargsType, tag); retType = UnionType.union(retType, t); } return retType; } else { return resolveCall(opType, aTypes, kwTypes, kwargsType, starargsType, tag); } } @Nullable private Type resolveCall(@NotNull Type rator, List<Type> aTypes, Map<String, Type> kwTypes, Type kwargsType, Type starargsType, int tag) { if (rator.isFuncType()) { FunType ft = rator.asFuncType(); return apply(ft, aTypes, kwTypes, kwargsType, starargsType, this, tag); } else if (rator.isClassType()) { return new InstanceType(rator, this, aTypes, tag); } else { addWarning("calling non-function and non-class: " + rator); return Indexer.idx.builtins.unknown; } } @NotNull public static Type apply(@NotNull FunType func, @Nullable List<Type> aTypes, Map<String, Type> kTypes, Type kwargsType, Type starargsType, @Nullable Node call, int tag) { Indexer.idx.removeUncalled(func); if (func.func != null && !func.func.called) { Indexer.idx.nCalled++; func.func.called = true; } if (func.getFunc() == null) { // func without definition (possibly builtins) return func.getReturnType(); } else if (call != null && Indexer.idx.inStack(call)) { func.setSelfType(null); return Indexer.idx.builtins.unknown; } if (call != null) { Indexer.idx.pushStack(call); } List<Type> argTypeList = new ArrayList<>(); if (func.getSelfType() != null) { argTypeList.add(func.getSelfType()); } else if (func.getCls() != null) { argTypeList.add(func.getCls().getCanon()); } if (aTypes != null) { argTypeList.addAll(aTypes); } bindMethodAttrs(func, tag); Scope funcTable = new Scope(func.getEnv(), Scope.ScopeType.FUNCTION); if (func.getTable().getParent() != null) { funcTable.setPath(func.getTable().getParent().extendPath(func.func.name.id)); } else { funcTable.setPath(func.func.name.id); } Type fromType = bindParams(call, funcTable, func.func.args, func.func.vararg, func.func.kwarg, argTypeList, func.defaultTypes, kTypes, kwargsType, starargsType, tag); Type cachedTo = func.getMapping(fromType); if (cachedTo != null) { func.setSelfType(null); return cachedTo; } else { Type toType = resolveExpr(func.func.body, funcTable, tag); if (missingReturn(toType)) { Indexer.idx.putProblem(func.func.name, "Function not always return a value"); if (call != null) { Indexer.idx.putProblem(call, "Call not always return a value"); } } func.addMapping(fromType, toType); func.setSelfType(null); return toType; } } @NotNull static private Type bindParams(@Nullable Node call, @NotNull Scope funcTable, @NotNull List<Node> args, Name fvarargs, Name fkwargs, @Nullable List<Type> aTypes, @Nullable List<Type> dTypes, @Nullable Map<String, Type> kwTypes, Type kwargsType, @Nullable Type starargsType, int tag) { TupleType fromType = new TupleType(); int aSize = aTypes == null ? 0 : aTypes.size(); int dSize = dTypes == null ? 0 : dTypes.size(); int nPositional = args.size() - dSize; if (starargsType != null && starargsType.isListType()) { starargsType = starargsType.asListType().toTupleType(); } for (int i = 0, j = 0; i < args.size(); i++) { Node arg = args.get(i); Type aType; if (i < aSize) { aType = aTypes.get(i); } else if (i - nPositional >= 0 && i - nPositional < dSize) { aType = dTypes.get(i - nPositional); } else if (kwTypes != null && args.get(i).isName() && kwTypes.containsKey(args.get(i).asName().getId())) { aType = kwTypes.get(args.get(i).asName().getId()); kwTypes.remove(args.get(i).asName().getId()); } else if (starargsType != null && starargsType.isTupleType() && j < starargsType.asTupleType().getElementTypes().size()) { aType = starargsType.asTupleType().get(j++); } else { aType = Indexer.idx.builtins.unknown; if (call != null) { Indexer.idx.putProblem(args.get(i), "unable to bind argument:" + args.get(i)); } } NameBinder.bind(funcTable, arg, aType, Binding.Kind.PARAMETER, tag); fromType.add(aType); } if (kwTypes != null && !kwTypes.isEmpty()) { Type kwValType = UnionType.newUnion(kwTypes.values()); NameBinder.bind(funcTable, fkwargs, new DictType(Indexer.idx.builtins.BaseStr, kwValType), Binding.Kind.PARAMETER, tag); } else { NameBinder.bind(funcTable, fkwargs, Indexer.idx.builtins.unknown, Binding.Kind.PARAMETER, tag); } if (aTypes.size() > args.size()) { Type starType = new TupleType(aTypes.subList(args.size(), aTypes.size())); NameBinder.bind(funcTable, fvarargs, starType, Binding.Kind.PARAMETER, tag); } else { NameBinder.bind(funcTable, fvarargs, Indexer.idx.builtins.unknown, Binding.Kind.PARAMETER, tag); } return fromType; } static void bindMethodAttrs(@NotNull FunType cl, int tag) { if (cl.getTable().getParent() != null) { Type cls = cl.getTable().getParent().getType(); if (cls != null && cls.isClassType()) { addReadOnlyAttr(cl, "im_class", cls, CLASS, tag); addReadOnlyAttr(cl, "__class__", cls, CLASS, tag); addReadOnlyAttr(cl, "im_self", cls, ATTRIBUTE, tag); addReadOnlyAttr(cl, "__self__", cls, ATTRIBUTE, tag); } } } @Nullable static Binding addReadOnlyAttr(@NotNull FunType cl, String name, @NotNull Type type, Binding.Kind kind, int tag) { Binding b = cl.getTable().put(name, Builtins.newDataModelUrl("the-standard-type-hierarchy"), type, kind, tag); b.markSynthetic(); b.markStatic(); b.markReadOnly(); return b; } static boolean missingReturn(@NotNull Type toType) { boolean hasNone = false; boolean hasOther = false; if (toType.isUnionType()) { for (Type t : toType.asUnionType().getTypes()) { if (t == Indexer.idx.builtins.None || t == Indexer.idx.builtins.Cont) { hasNone = true; } else { hasOther = true; } } } return hasNone && hasOther; } @NotNull @Override public String toString() { return "<Call:" + func + ":" + args + ":" + start + ">"; } @Override public void visit(@NotNull NodeVisitor v) { if (v.visit(this)) { visitNode(func, v); visitNodeList(args, v); visitNodeList(keywords, v); visitNode(kwargs, v); visitNode(starargs, v); } } }