pxt-core
Version:
Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors
1,244 lines • 279 kB
JavaScript
/// <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