UNPKG

pxt-core

Version:

Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors

1,244 lines • 279 kB
/// <reference path='../built/pxtlib.d.ts' /> /// <reference path='../built/pxtcompiler.d.ts' /> var pxt; (function (pxt) { var py; (function (py) { let VarModifier; (function (VarModifier) { VarModifier[VarModifier["NonLocal"] = 0] = "NonLocal"; VarModifier[VarModifier["Global"] = 1] = "Global"; })(VarModifier = py.VarModifier || (py.VarModifier = {})); function isIndex(e) { return e.kind === "Index"; } py.isIndex = isIndex; function isSubscript(e) { return e.kind === "Subscript"; } py.isSubscript = isSubscript; })(py = pxt.py || (pxt.py = {})); })(pxt || (pxt = {})); var pxt; (function (pxt) { var py; (function (py_1) { var B = pxt.blocks; // global state let externalApis; // slurped from libraries let internalApis; // defined in Python let ctx; let currIteration = 0; let typeId = 0; // this measures if we gained additional information about type state // we run conversion several times, until we have all information possible let numUnifies = 0; let autoImport = true; let currErrorCtx = "???"; let verboseTypes = false; let lastAST = undefined; let lastFile; let diagnostics; let compileOptions; let syntaxInfo; let infoNode = undefined; let infoScope; // TODO: move to utils function isFalsy(t) { return t === null || t === undefined; } function isTruthy(t) { return t !== null && t !== undefined; } function stmtTODO(v) { pxt.tickEvent("python.todo.statement", { kind: v.kind }); return B.mkStmt(B.mkText("TODO: " + v.kind)); } function exprTODO(v) { pxt.tickEvent("python.todo.expression", { kind: v.kind }); return B.mkText(" {TODO: " + v.kind + "} "); } function docComment(cmt) { if (cmt.trim().split(/\n/).length <= 1) cmt = cmt.trim(); else cmt = cmt + "\n"; return B.mkStmt(B.mkText("/** " + cmt + " */")); } function defName(n, tp) { return { kind: "Name", id: n, isdef: true, ctx: "Store", tsType: tp }; } const tpString = mkType({ primType: "string" }); const tpNumber = mkType({ primType: "number" }); const tpBoolean = mkType({ primType: "boolean" }); const tpVoid = mkType({ primType: "void" }); const tpAny = mkType({ primType: "any" }); let tpBuffer = undefined; const builtInTypes = { "str": tpString, "string": tpString, "number": tpNumber, "bool": tpBoolean, "void": tpVoid, "any": tpAny, }; function ts2PyType(syntaxKind) { switch (syntaxKind) { case ts.SyntaxKind.StringKeyword: return tpString; case ts.SyntaxKind.NumberKeyword: return tpNumber; case ts.SyntaxKind.BooleanKeyword: return tpBoolean; case ts.SyntaxKind.VoidKeyword: return tpVoid; case ts.SyntaxKind.AnyKeyword: return tpAny; default: { // TODO: this could be null return tpBuffer; } } } function cleanSymbol(s) { let r = pxt.U.flatClone(s); delete r.pyAST; delete r.pyInstanceType; delete r.pyRetType; delete r.pySymbolType; delete r.moduleTypeMarker; delete r.declared; if (r.parameters) r.parameters = r.parameters.map(p => { p = pxt.U.flatClone(p); delete p.pyType; return p; }); return r; } function mapTsType(tp) { // TODO handle specifc generic types like: SparseArray<number[]> // wrapped in (...) if (tp[0] == "(" && pxt.U.endsWith(tp, ")")) { return mapTsType(tp.slice(1, -1)); } // lambda (...) => ... const arrowIdx = tp.indexOf(" => "); if (arrowIdx > 0) { const retTypeStr = tp.slice(arrowIdx + 4); if (retTypeStr.indexOf(")[]") == -1) { const retType = mapTsType(retTypeStr); const argsStr = tp.slice(1, arrowIdx - 1); const argsWords = argsStr ? argsStr.split(/, /) : []; const argTypes = argsWords.map(a => mapTsType(a.replace(/\w+\??: /, ""))); return mkFunType(retType, argTypes); } } // array ...[] if (pxt.U.endsWith(tp, "[]")) { return mkArrayType(mapTsType(tp.slice(0, -2))); } if (tp === "_py.Array") { return mkArrayType(tpAny); } // builtin const t = pxt.U.lookup(builtInTypes, tp); if (t) return t; // handle number litterals like "-20" (b/c TS loves to give specific types to const's) let isNum = !!tp && !isNaN(tp); // https://stackoverflow.com/questions/175739 if (isNum) return tpNumber; // generic if (tp == "T" || tp == "U") // TODO hack! return mkType({ primType: "'" + tp }); // union if (tp.indexOf("|") >= 0) { const parts = tp.split("|") .map(p => p.trim()); return mkType({ primType: "@union", typeArgs: parts.map(mapTsType) }); } // defined by a symbol, // either in external (non-py) APIs (like default/common packages) // or in internal (py) APIs (probably main.py) let sym = lookupApi(tp + "@type") || lookupApi(tp); if (!sym) { error(null, 9501, pxt.U.lf("unknown type '{0}' near '{1}'", tp, currErrorCtx || "???")); return mkType({ primType: tp }); } if (sym.kind == 7 /* SK.EnumMember */) return tpNumber; // sym.pyInstanceType might not be initialized yet and we don't want to call symbolType() here to avoid infinite recursion if (sym.kind == 8 /* SK.Class */ || sym.kind == 9 /* SK.Interface */) return sym.pyInstanceType || mkType({ classType: sym }); if (sym.kind == 6 /* SK.Enum */) return tpNumber; error(null, 9502, pxt.U.lf("'{0}' is not a type near '{1}'", tp, currErrorCtx || "???")); return mkType({ primType: tp }); } function getOrSetSymbolType(sym) { var _a; if (!sym.pySymbolType) { currErrorCtx = sym.pyQName; if (sym.parameters) { if (pxtc.service.isTaggedTemplate(sym)) { sym.parameters = [{ "name": "literal", "description": "", "type": "string", "options": {} }]; } for (let p of sym.parameters) { if (!p.pyType) p.pyType = mapTsType(p.type); } } const prevRetType = sym.pyRetType; if (isModule(sym)) { sym.pyRetType = mkType({ moduleType: sym }); } else { if (sym.retType) { if (((_a = sym.qName) === null || _a === void 0 ? void 0 : _a.endsWith(".__constructor")) && sym.retType === "void") { // This must be a TS class. Because python treats constructors as functions, // set the return type to be the class instead of void const classSym = lookupGlobalSymbol(sym.qName.substring(0, sym.qName.lastIndexOf("."))); if (classSym) { sym.pyRetType = mkType({ classType: classSym }); } else { sym.pyRetType = mapTsType(sym.retType); } } else { sym.pyRetType = mapTsType(sym.retType); } } else if (sym.pyRetType) { // nothing to do } else { pxt.U.oops("no type for: " + sym.pyQName); sym.pyRetType = mkType({}); } } if (prevRetType) { unify(sym.pyAST, prevRetType, sym.pyRetType); } if (sym.kind == 3 /* SK.Function */ || sym.kind == 1 /* SK.Method */) { let paramTypes = sym.parameters.map(p => p.pyType); if (paramTypes.some(isFalsy)) { error(null, 9526, pxt.U.lf("function symbol is missing parameter types near '{1}'", currErrorCtx || "???")); return mkType({}); } sym.pySymbolType = mkFunType(sym.pyRetType, paramTypes.filter(isTruthy)); } else sym.pySymbolType = sym.pyRetType; if (sym.kind == 8 /* SK.Class */ || sym.kind == 9 /* SK.Interface */) { sym.pyInstanceType = mkType({ classType: sym }); } currErrorCtx = undefined; } return sym.pySymbolType; } function lookupApi(name) { return pxt.U.lookup(internalApis, name) || pxt.U.lookup(externalApis, name); } function lookupGlobalSymbol(name) { var _a; if (!name) return undefined; let sym = lookupApi(name); if (sym) getOrSetSymbolType(sym); else if (name.indexOf(".") && !name.endsWith(".__constructor")) { const base = name.substring(0, name.lastIndexOf(".")); const baseSymbol = lookupGlobalSymbol(base); if ((baseSymbol === null || baseSymbol === void 0 ? void 0 : baseSymbol.kind) === 8 /* SK.Class */ && ((_a = baseSymbol.extendsTypes) === null || _a === void 0 ? void 0 : _a.length)) { return lookupGlobalSymbol(baseSymbol.extendsTypes[0] + name.substring(base.length)); } } return sym; } function initApis(apisInfo, tsShadowFiles) { internalApis = {}; externalApis = {}; let tsShadowFilesSet = pxt.U.toDictionary(tsShadowFiles, t => t); for (let sym of pxt.U.values(apisInfo.byQName)) { if (tsShadowFilesSet.hasOwnProperty(sym.fileName)) { continue; } let sym2 = sym; if (sym2.extendsTypes) sym2.extendsTypes = sym2.extendsTypes.filter(e => e != sym2.qName); if (!sym2.pyQName || !sym2.qName) { error(null, 9526, pxt.U.lf("Symbol '{0}' is missing qName for '{1}'", sym2.name, !sym2.pyQName ? "py" : "ts")); } externalApis[sym2.pyQName] = sym2; externalApis[sym2.qName] = sym2; } // TODO this is for testing mostly; we can do this lazily // for (let sym of U.values(externalApis)) { // if (sym) // getOrSetSymbolType(sym) // } tpBuffer = mapTsType("Buffer"); } function mkType(o = {}) { let r = pxt.U.flatClone(o); r.tid = ++typeId; return r; } function mkArrayType(eltTp) { return mkType({ primType: "@array", typeArgs: [eltTp] }); } function mkFunType(retTp, argTypes) { return mkType({ primType: "@fn" + argTypes.length, typeArgs: [retTp].concat(argTypes) }); } function isFunType(t) { return !!pxt.U.startsWith(t.primType || "", "@fn"); } function isPrimativeType(t) { return !!t.primType && !pxt.U.startsWith(t.primType, "@"); } function isUnionType(t) { return !!pxt.U.startsWith(t.primType || "", "@union"); } function instanceType(sym) { getOrSetSymbolType(sym); if (!sym.pyInstanceType) error(null, 9527, pxt.U.lf("Instance type symbol '{0}' is missing pyInstanceType", sym)); return sym.pyInstanceType; } function currentScope() { return ctx.currFun || ctx.currClass || ctx.currModule; } function topScope() { let current = currentScope(); while (current && current.parent) { current = current.parent; } return current; } function isTopLevel() { return ctx.currModule.name == "main" && !ctx.currFun && !ctx.currClass; } function addImport(a, name, scope) { const sym = lookupGlobalSymbol(name); if (!sym) error(a, 9503, pxt.U.lf("No module named '{0}'", name)); return sym; } function getHelperVariableName() { const scope = currentScope(); if (scope.nextHelperVariableId === undefined) { scope.nextHelperVariableId = 0; } return "___tempvar" + scope.nextHelperVariableId++; } function defvar(name, opts, modifier, scope) { if (!scope) scope = currentScope(); let varScopeSym = scope.vars[name]; let varSym = varScopeSym === null || varScopeSym === void 0 ? void 0 : varScopeSym.symbol; if (!varSym) { let pref = getFullName(scope); if (pref) { pref += "."; } let qualifiedName = pref + name; if (scope.kind === "ClassDef") { varSym = addSymbol(2 /* SK.Property */, qualifiedName); } else if (isLocalScope(scope) && (modifier === py_1.VarModifier.Global || modifier === py_1.VarModifier.NonLocal)) { varSym = addSymbol(4 /* SK.Variable */, name); } else if (isLocalScope(scope)) varSym = mkSymbol(4 /* SK.Variable */, name); else varSym = addSymbol(4 /* SK.Variable */, qualifiedName); varScopeSym = { symbol: varSym, modifier, }; scope.vars[name] = varScopeSym; } for (let k of Object.keys(opts)) { varSym[k] = opts[k]; } return varScopeSym; } function canonicalize(t) { if (t.unifyWith) { t.unifyWith = canonicalize(t.unifyWith); return t.unifyWith; } return t; } // TODO cache it? function getFullName(n) { let s = n; let pref = ""; if (s.parent && s.parent.kind !== "FunctionDef" && s.parent.kind !== "AsyncFunctionDef") { pref = getFullName(s.parent); if (!pref) pref = ""; else pref += "."; } let nn = n; if (n.kind == "Module" && nn.name == "main") return ""; if (nn.name) return pref + nn.name; else return pref + "?" + n.kind; } function applyTypeMap(s) { let over = pxt.U.lookup(typeMap, s); if (over) return over; for (let scopeVar of pxt.U.values(ctx.currModule.vars)) { let v = scopeVar.symbol; if (!v.isImport) continue; if (v.expandsTo == s) { if (!v.pyName) error(null, 9553, lf("missing pyName")); return v.pyName; } if (v.isImport && pxt.U.startsWith(s, (v.expandsTo || "") + ".")) { return v.pyName + s.slice(v.expandsTo.length); } } return s; } function t2s(t) { t = canonicalize(t); const suff = (s) => verboseTypes ? s : ""; if (t.primType) { if (t.typeArgs && t.primType == "@array") { return t2s(t.typeArgs[0]) + "[]"; } if (isFunType(t) && t.typeArgs) return "(" + t.typeArgs.slice(1).map(t => "_: " + t2s(t)).join(", ") + ") => " + t2s(t.typeArgs[0]); if (isUnionType(t) && t.typeArgs) { return t.typeArgs.map(t2s).join(" | "); } return t.primType + suff("/P"); } if (t.classType && t.classType.pyQName) return applyTypeMap(t.classType.pyQName) + suff("/C"); else if (t.moduleType && t.moduleType.pyQName) return applyTypeMap(t.moduleType.pyQName) + suff("/M"); else return "any"; } function mkDiag(astNode, category, code, messageText) { if (!astNode) astNode = lastAST; if (!astNode || !ctx || !ctx.currModule) { return { fileName: lastFile, start: 0, length: 0, line: undefined, column: undefined, code, category, messageText, }; } else { return { fileName: lastFile, start: astNode.startPos, length: astNode.endPos - astNode.startPos, line: undefined, column: undefined, code, category, messageText, }; } } // next free error 9576 function error(astNode, code, msg) { diagnostics.push(mkDiag(astNode, pxtc.DiagnosticCategory.Error, code, msg)); //const pos = position(astNode ? astNode.startPos || 0 : 0, mod.source) //currErrs += U.lf("{0} near {1}{2}", msg, mod.tsFilename.replace(/\.ts/, ".py"), pos) + "\n" } function typeError(a, t0, t1) { error(a, 9500, pxt.U.lf("types not compatible: {0} and {1}", t2s(t0), t2s(t1))); } function typeCtor(t) { if (t.primType) return t.primType; else if (t.classType) return t.classType; else if (t.moduleType) { // a class SymbolInfo can be used as both classType and moduleType // but these are different constructors (one is instance, one is class itself) if (!t.moduleType.moduleTypeMarker) t.moduleType.moduleTypeMarker = {}; return t.moduleType.moduleTypeMarker; } return null; } function isFree(t) { return !typeCtor(canonicalize(t)); } function canUnify(t0, t1) { t0 = canonicalize(t0); t1 = canonicalize(t1); if (t0 === t1) return true; let c0 = typeCtor(t0); let c1 = typeCtor(t1); if (!c0 || !c1) return true; if (c0 !== c1) return false; if (t0.typeArgs && t1.typeArgs) { for (let i = 0; i < Math.min(t0.typeArgs.length, t1.typeArgs.length); ++i) if (!canUnify(t0.typeArgs[i], t1.typeArgs[i])) return false; } return true; } function unifyClass(a, t, cd) { t = canonicalize(t); if (t.classType == cd) return; if (isFree(t)) { t.classType = cd; return; } unify(a, t, instanceType(cd)); } function unifyTypeOf(e, t1) { unify(e, typeOf(e), t1); } function unify(a, t0, t1) { if (t0 === t1) return; t0 = canonicalize(t0); t1 = canonicalize(t1); // We don't handle generic types yet, so bail out. Worst case // scenario is that we infer some extra types as "any" if (t0 === t1 || isGenericType(t0) || isGenericType(t1)) return; if (t0.primType === "any") { t0.unifyWith = t1; return; } const c0 = typeCtor(t0); const c1 = typeCtor(t1); if (c0 && c1) { if (c0 === c1) { t0.unifyWith = t1; // no type-state change here - actual change would be in arguments only if (t0.typeArgs && t1.typeArgs) { for (let i = 0; i < Math.min(t0.typeArgs.length, t1.typeArgs.length); ++i) unify(a, t0.typeArgs[i], t1.typeArgs[i]); } t0.unifyWith = t1; } else { typeError(a, t0, t1); } } else if (c0 && !c1) { unify(a, t1, t0); } else { // the type state actually changes here numUnifies++; t0.unifyWith = t1; // detect late unifications // if (currIteration > 2) error(a, `unify ${t2s(t0)} ${t2s(t1)}`) } } function isAssignable(fromT, toT) { // TODO: handle assignablity beyond interfaces and classes, e.g. "any", generics, arrays, ... const t0 = canonicalize(fromT); const t1 = canonicalize(toT); if (t0 === t1) return true; const c0 = typeCtor(t0); const c1 = typeCtor(t1); if (c0 === c1) return true; if (c0 && c1) { if (isSymbol(c0) && isSymbol(c1)) { // check extends relationship (e.g. for interfaces & classes) if (c0.extendsTypes && c0.extendsTypes.length) { if (c0.extendsTypes.some(e => e === c1.qName)) { return true; } } } // check unions if (isUnionType(t1)) { for (let uT of t1.typeArgs || []) { if (isAssignable(t0, uT)) return true; } return false; } } return false; } function narrow(e, constrainingType) { const t0 = canonicalize(typeOf(e)); const t1 = canonicalize(constrainingType); if (isAssignable(t0, t1)) { return; } // if we don't know if two types are assignable, we can try to unify them in common cases // TODO: unification is too strict but should always be sound if (isFunType(t0) || isFunType(t1) || isPrimativeType(t0) || isPrimativeType(t1)) { unify(e, t0, t1); } else { // if we're not sure about assinability or unification, we do nothing as future // iterations may unify or make assinability clear. // TODO: Ideally what we should do is track a "constraining type" similiar to how we track .union // per type, and ensure the constraints are met as we unify or narrow types. The difficulty is that // this depends on a more accurate assignability check which will take some work to get right. } } function isSymbol(c) { return !!(c === null || c === void 0 ? void 0 : c.name); } function isGenericType(t) { var _a; return !!((_a = t === null || t === void 0 ? void 0 : t.primType) === null || _a === void 0 ? void 0 : _a.startsWith("'")); } function mkSymbol(kind, qname) { let m = /(.*)\.(.*)/.exec(qname); let name = m ? m[2] : qname; let ns = m ? m[1] : ""; return { kind: kind, name: name, pyName: name, qName: qname, pyQName: qname, namespace: ns, attributes: {}, pyRetType: mkType() }; } function addSymbol(kind, qname) { let sym = internalApis[qname]; if (sym) { sym.kind = kind; return sym; } sym = mkSymbol(kind, qname); if (!sym.pyQName) error(null, 9527, pxt.U.lf("Symbol '{0}' is missing pyQName", qname)); internalApis[sym.pyQName] = sym; return sym; } function isLocalScope(scope) { let s = scope; while (s) { if (s.kind == "FunctionDef") return true; s = s.parent; } return false; } function addSymbolFor(k, n, scope) { if (!n.symInfo) { let qn = getFullName(n); if (pxt.U.endsWith(qn, ".__init__")) qn = qn.slice(0, -9) + ".__constructor"; scope = scope || currentScope(); if (isLocalScope(scope)) n.symInfo = mkSymbol(k, qn); else n.symInfo = addSymbol(k, qn); const sym = n.symInfo; sym.pyAST = n; if (!sym.pyName) error(null, 9528, pxt.U.lf("Symbol '{0}' is missing pyName", sym.qName || sym.name)); scope.vars[sym.pyName] = { symbol: sym }; } return n.symInfo; } // TODO optimize ? function listClassFields(cd) { let qn = cd.symInfo.qName; return pxt.U.values(internalApis).filter(e => e.namespace == qn && e.kind == 2 /* SK.Property */); } function getClassField(ct, n, isStatic, checkOnly = false, skipBases = false) { let qid; if (n === "__init__") { qid = ct.pyQName + ".__constructor"; } else { if (n.startsWith(ct.pyQName + ".")) { qid = n; } else { qid = ct.pyQName + "." + n; } } let f = lookupGlobalSymbol(qid); if (f) return f; if (!skipBases) { for (let b of ct.extendsTypes || []) { let sym = lookupGlobalSymbol(b); if (sym) { if (sym == ct) pxt.U.userError("field lookup loop on: " + sym.qName + " / " + n); let classF = getClassField(sym, n, isStatic, true); if (classF) return classF; } } } if (!checkOnly && ct.pyAST && ct.pyAST.kind == "ClassDef") { let sym = addSymbol(2 /* SK.Property */, qid); sym.isInstance = !isStatic; return sym; } return null; } function getTypesForFieldLookup(recvType) { let t = canonicalize(recvType); return [ t.classType, ...resolvePrimTypes(t.primType), t.moduleType ].filter(isTruthy); } function getTypeField(recv, n, checkOnly = false) { const recvType = typeOf(recv); const constructorTypes = getTypesForFieldLookup(recvType); for (let ct of constructorTypes) { let isModule = !!recvType.moduleType; let f = getClassField(ct, n, isModule, checkOnly); if (f) { if (isModule) { if (f.isInstance) error(null, 9505, pxt.U.lf("the field '{0}' of '{1}' is not static", n, ct.pyQName)); } else { if (isSuper(recv)) f.isProtected = true; else if (isThis(recv)) { if (!ctx.currClass) error(null, 9529, pxt.U.lf("no class context found for {0}", f.pyQName)); if (f.namespace != ctx.currClass.symInfo.qName) { f.isProtected = true; } } } return f; } } return null; } function resolvePrimTypes(primType) { let res = []; if (primType == "@array") { res = [lookupApi("_py.Array"), lookupApi("Array")]; } else if (primType == "string") { // we need to check both the special "_py" namespace and the typescript "String" // class because for example ".length" is only defined in the latter res = [lookupApi("_py.String"), lookupApi("String")]; } return res.filter(a => !!a); } function lookupVar(n) { let s = currentScope(); let v = pxt.U.lookup(s.vars, n); if (v) return v; while (s) { let v = pxt.U.lookup(s.vars, n); if (v) return v; // go to parent, excluding class scopes do { s = s.parent; } while (s && s.kind == "ClassDef"); } //if (autoImport && lookupGlobalSymbol(n)) { // return addImport(currentScope(), n, ctx.currModule) //} return null; } function lookupScopeSymbol(n) { if (!n) return null; const firstDot = n.indexOf("."); if (firstDot > 0) { const scopeVar = lookupVar(n.slice(0, firstDot)); const v = scopeVar === null || scopeVar === void 0 ? void 0 : scopeVar.symbol; // expand name if needed if (v && v.pyQName != v.pyName) n = v.pyQName + n.slice(firstDot); } else { const v = lookupVar(n); if (v) return v; } let globalSym = lookupGlobalSymbol(n); if (!globalSym) return undefined; return { symbol: globalSym }; } function lookupSymbol(n) { var _a; return (_a = lookupScopeSymbol(n)) === null || _a === void 0 ? void 0 : _a.symbol; } function getClassDef(e) { let n = getName(e); let s = lookupSymbol(n); if (s && s.pyAST && s.pyAST.kind == "ClassDef") return s.pyAST; return null; } function typeOf(e) { if (e.tsType) { return canonicalize(e.tsType); } else { e.tsType = mkType(); return e.tsType; } } function isOfType(e, name) { let t = typeOf(e); if (t.classType && t.classType.pyQName == name) return true; if (t2s(t) == name) return true; return false; } function resetCtx(m) { ctx = { currClass: undefined, currFun: undefined, currModule: m, blockDepth: 0 }; lastFile = m.tsFilename.replace(/\.ts$/, ".py"); } function isModule(s) { if (!s) return false; switch (s.kind) { case 5 /* SK.Module */: case 9 /* SK.Interface */: case 8 /* SK.Class */: case 6 /* SK.Enum */: return true; default: return false; } } function scope(f) { const prevCtx = pxt.U.flatClone(ctx); let r; try { r = f(); } finally { ctx = prevCtx; } return r; } function todoExpr(name, e) { if (!e) return B.mkText(""); return B.mkGroup([B.mkText("/* TODO: " + name + " "), e, B.mkText(" */")]); } function todoComment(name, n) { if (n.length == 0) return B.mkText(""); return B.mkGroup([B.mkText("/* TODO: " + name + " "), B.mkGroup(n), B.mkText(" */"), B.mkNewLine()]); } function doKeyword(k) { let t = expr(k.value); if (k.arg) return B.mkInfix(B.mkText(k.arg), "=", t); else return B.mkGroup([B.mkText("**"), t]); } function compileType(e) { if (!e) return mkType(); let tpName = tryGetName(e); if (tpName) { let sym = lookupApi(tpName + "@type") || lookupApi(tpName); if (sym) { getOrSetSymbolType(sym); if (sym.kind == 6 /* SK.Enum */) return tpNumber; if (sym.pyInstanceType) return sym.pyInstanceType; } else if (builtInTypes[tpName]) return builtInTypes[tpName]; error(e, 9506, pxt.U.lf("cannot find type '{0}'", tpName)); } else { // translate Python to TS type annotation for arrays // example: List[str] => string[] if (py_1.isSubscript(e) /*i.e. [] syntax*/) { let isList = tryGetName(e.value) === "List"; if (isList) { if (py_1.isIndex(e.slice)) { let listTypeArg = compileType(e.slice.value); let listType = mkArrayType(listTypeArg); return listType; } } } } error(e, 9507, pxt.U.lf("invalid type syntax")); return mkType({}); } function doArgs(n, isMethod) { var _a; const args = n.args; if (args.kwonlyargs.length) error(n, 9517, pxt.U.lf("keyword-only arguments not supported yet")); let nargs = args.args.slice(); if (isMethod) { if (((_a = nargs[0]) === null || _a === void 0 ? void 0 : _a.arg) !== "self") n.symInfo.isStatic = true; else { nargs.shift(); } } else { if (nargs.some(a => a.arg == "self")) error(n, 9519, pxt.U.lf("non-methods cannot have an argument called 'self'")); } if (!n.symInfo.parameters) { let didx = args.defaults.length - nargs.length; n.symInfo.parameters = nargs.map(a => { if (!a.annotation) error(n, 9519, pxt.U.lf("Arg '{0}' missing annotation", a.arg)); let tp = compileType(a.annotation); let defl = ""; if (didx >= 0) { defl = B.flattenNode([expr(args.defaults[didx])]).output; unify(a, tp, typeOf(args.defaults[didx])); } didx++; return { name: a.arg, description: "", type: "", initializer: defl, default: defl, pyType: tp }; }); } let lst = n.symInfo.parameters.map(p => { let scopeV = defvar(p.name, { isParam: true }); let v = scopeV === null || scopeV === void 0 ? void 0 : scopeV.symbol; if (!p.pyType) error(n, 9530, pxt.U.lf("parameter '{0}' missing pyType", p.name)); unify(n, getOrSetSymbolType(v), p.pyType); let res = [quote(p.name), typeAnnot(p.pyType, true)]; if (p.default) { res.push(B.mkText(" = " + p.default)); } return B.mkGroup(res); }); if (args.vararg) lst.push(B.mkText("TODO *" + args.vararg.arg)); if (args.kwarg) lst.push(B.mkText("TODO **" + args.kwarg.arg)); return B.H.mkParenthesizedExpression(B.mkCommaSep(lst)); } function accessAnnot(f) { if (!f.pyName || f.pyName[0] != "_") return B.mkText(""); return f.isProtected ? B.mkText("protected ") : B.mkText("private "); } const numOps = { Sub: 1, Div: 1, Pow: 1, LShift: 1, RShift: 1, BitOr: 1, BitXor: 1, BitAnd: 1, FloorDiv: 1, Mult: 1, // this can be also used on strings and arrays, but let's ignore that for now }; const arithmeticCompareOps = { Eq: 1, NotEq: 1, Lt: 1, LtE: 1, Gt: 1, GtE: 1 }; const opMapping = { Add: "+", Sub: "-", Mult: "*", MatMult: "Math.matrixMult", Div: "/", Mod: "%", Pow: "**", LShift: "<<", RShift: ">>", BitOr: "|", BitXor: "^", BitAnd: "&", FloorDiv: "Math.idiv", And: "&&", Or: "||", Eq: "==", NotEq: "!=", Lt: "<", LtE: "<=", Gt: ">", GtE: ">=", Is: "===", IsNot: "!==", In: "py.In", NotIn: "py.NotIn", }; const prefixOps = { Invert: "~", Not: "!", UAdd: "P+", USub: "P-", }; const typeMap = { "adafruit_bus_device.i2c_device.I2CDevice": "pins.I2CDevice" }; function stmts(ss) { ctx.blockDepth++; const res = B.mkBlock(ss.map(stmt)); ctx.blockDepth--; return res; } function exprs0(ee) { ee = ee.filter(e => !!e); return ee.map(expr); } function setupScope(n) { if (!n.vars) { n.vars = {}; n.parent = currentScope(); n.blockDepth = ctx.blockDepth; } } function typeAnnot(t, defaultToAny = false) { let s = t2s(t); if (s === "any") { // TODO: // example from minecraft doc snippet: // player.onChat("while",function(num1){while(num1<10){}}) // -> py -> ts -> // player.onChat("while",function(num1:any;/**TODO:type**/){while(num1<10){;}}) // work around using any: // return B.mkText(": any /** TODO: type **/") // but for now we can just omit the type and most of the type it'll be inferable return defaultToAny ? B.mkText(": any") : B.mkText(""); } return B.mkText(": " + t2s(t)); } function guardedScope(v, f) { try { return scope(f); } catch (e) { pxt.log(e); return B.mkStmt(todoComment(`conversion failed for ${v.name || v.kind}`, [])); } } function shouldInlineFunction(si) { if (!si || !si.pyAST) return false; if (si.pyAST.kind != "FunctionDef") return false; const fn = si.pyAST; if (!fn.callers || fn.callers.length != 1) return false; if (fn.callers[0].inCalledPosition) return false; return true; } function emitFunctionDef(n, inline = false) { return guardedScope(n, () => { var _a, _b, _c, _d; const isMethod = !!ctx.currClass && !ctx.currFun; const topLev = isTopLevel(); const nested = !!ctx.currFun; setupScope(n); const existing = lookupSymbol(getFullName(n)); const sym = addSymbolFor(isMethod ? 1 /* SK.Method */ : 3 /* SK.Function */, n); if (!inline) { if (existing && existing.declared === currIteration) { error(n, 9520, lf("Duplicate function declaration")); } sym.declared = currIteration; if (shouldInlineFunction(sym)) { return B.mkText(""); } } if (isMethod) sym.isInstance = true; ctx.currFun = n; let prefix = ""; let funname = n.name; const remainingDecorators = n.decorator_list.filter(d => { if (tryGetName(d) == "property") { prefix = "get"; return false; } if (d.kind == "Attribute" && d.attr == "setter" && d.value.kind == "Name") { funname = d.value.id; prefix = "set"; return false; } return true; }); let nodes = [ todoComment("decorators", remainingDecorators.map(expr)) ]; if (n.body.length >= 1 && n.body[0].kind == "Raise") n.alwaysThrows = true; if (isMethod) { if (!ctx.currClass) error(n, 9531, lf("method '{0}' is missing current class context", sym.pyQName)); if (!sym.pyRetType) error(n, 9532, lf("method '{0}' is missing a return type", sym.pyQName)); if (n.name == "__init__") { nodes.push(B.mkText("constructor")); unifyClass(n, sym.pyRetType, ctx.currClass.symInfo); } else { if (funname == "__get__" || funname == "__set__") { let scopeValueVar = n.vars["value"]; let valueVar = scopeValueVar === null || scopeValueVar === void 0 ? void 0 : scopeValueVar.symbol; if (funname == "__set__" && valueVar) { let cf = getClassField(ctx.currClass.symInfo, "__get__", false); if (cf && cf.pyAST && cf.pyAST.kind == "FunctionDef") unify(n, valueVar.pyRetType, cf.pyRetType); } funname = funname.replace(/_/g, ""); } if (!prefix) { prefix = funname[0] == "_" ? (sym.isProtected ? "protected" : "private") : "public"; if (n.symInfo.isStatic) { prefix += " static"; } } nodes.push(B.mkText(prefix + " "), quote(funname)); } } else { pxt.U.assert(!prefix); if (n.name[0] == "_" || topLev || inline || nested) nodes.push(B.mkText("function "), quote(funname)); else nodes.push(B.mkText("export function "), quote(funname)); } let retType = n.name == "__init__" ? undefined : (n.returns ? compileType(n.returns) : sym.pyRetType); nodes.push(doArgs(n, isMethod), retType && canonicalize(retType) != tpVoid ? typeAnnot(retType) : B.mkText("")); // make sure type is initialized getOrSetSymbolType(sym); let body = n.body.map(stmt); if (n.name == "__init__") { if (!ctx.currClass) error(n, 9533, lf("__init__ method '{0}' is missing current class context", sym.pyQName)); if ((_a = ctx.currClass) === null || _a === void 0 ? void 0 : _a.baseClass) { const firstStatement = n.body[0]; const superConstructor = ctx.currClass.baseClass.pyQName + ".__constructor"; if (((_d = (_c = (_b = firstStatement.value) === null || _b === void 0 ? void 0 : _b.func) === null || _c === void 0 ? void 0 : _c.symbolInfo) === null || _d === void 0 ? void 0 : _d.pyQName) !== superConstructor) { error(n, 9575, lf("Sub classes must call 'super().__init__' as the first statement inside an __init__ method")); } } for (let f of listClassFields(ctx.currClass)) { let p = f.pyAST; if (p && p.value) { body.push(B.mkStmt(B.mkText(`this.${quoteStr(f.pyName)} = `), expr(p.value))); } } } const hoisted = collectHoistedDeclarations(n); nodes.push(B.mkBlock(hoisted.concat(body))); let ret = B.mkGroup(nodes); if (inline) nodes[nodes.length - 1].noFinalNewline = true; else ret = B.mkStmt(ret); return ret; }); } const stmtMap = { FunctionDef: (n) => emitFunctionDef(n), ClassDef: (n) => guardedScope(n, () => { setupScope(n); const sym = addSymbolFor(8 /* SK.Class */, n); pxt.U.assert(!ctx.currClass); let topLev = isTopLevel(); ctx.currClass = n; n.isNamespace = n.decorator_list.some(d => d.kind == "Name" && d.id == "namespace"); let nodes = n.isNamespace ? [B.mkText("namespace "), quote(n.name)] : [ todoComment("keywords", n.keywords.map(doKeyword)), todoComment("decorators", n.decorator_list.map(expr)), B.mkText(topLev ? "class " : "export class "), quote(n.name) ]; if (!n.isNamespace && n.bases.length > 0) { if (tryGetName(n.bases[0]) == "Enum") { n.isEnum = true; } else { nodes.push(B.mkText(" extends ")); nodes.push(B.mkCommaSep(n.bases.map(expr))); let b = getClassDef(n.bases[0]); if (b) { n.baseClass = b.symInfo; sym.extendsTypes = [b.symInfo.pyQName]; } else { const nm = tryGetName(n.bases[0]); if (nm) { const localSym = lookupSymbol(nm); const globalSym = lookupGlobalSymbol(nm); n.baseClass = localSym || globalSy