in pxtblocks/blocklycompiler.ts [404:590]
function infer(allBlocks: Blockly.Block[], e: Environment, w: Blockly.Workspace) {
if (allBlocks) allBlocks.filter(b => b.isEnabled()).forEach((b: Blockly.Block) => {
try {
switch (b.type) {
case "math_op2":
unionParam(e, b, "x", ground(pNumber.type));
unionParam(e, b, "y", ground(pNumber.type));
break;
case "math_op3":
unionParam(e, b, "x", ground(pNumber.type));
break;
case "math_arithmetic":
case "logic_compare":
switch (b.getFieldValue("OP")) {
case "ADD": case "MINUS": case "MULTIPLY": case "DIVIDE":
case "LT": case "LTE": case "GT": case "GTE": case "POWER":
unionParam(e, b, "A", ground(pNumber.type));
unionParam(e, b, "B", ground(pNumber.type));
break;
case "AND": case "OR":
attachPlaceholderIf(e, b, "A", pBoolean.type);
attachPlaceholderIf(e, b, "B", pBoolean.type);
break;
case "EQ": case "NEQ":
attachPlaceholderIf(e, b, "A");
attachPlaceholderIf(e, b, "B");
let p1 = returnType(e, getInputTargetBlock(b, "A"));
let p2 = returnType(e, getInputTargetBlock(b, "B"));
try {
union(p1, p2);
} catch (e) {
// TypeScript should catch this error and bubble it up
}
break;
}
break;
case "logic_operation":
attachPlaceholderIf(e, b, "A", pBoolean.type);
attachPlaceholderIf(e, b, "B", pBoolean.type);
break;
case "logic_negate":
attachPlaceholderIf(e, b, "BOOL", pBoolean.type);
break;
case "controls_if":
for (let i = 0; i <= (<Blockly.IfBlock>b).elseifCount_; ++i)
attachPlaceholderIf(e, b, "IF" + i, pBoolean.type);
break;
case "pxt_controls_for":
case "controls_simple_for":
unionParam(e, b, "TO", ground(pNumber.type));
break;
case "pxt_controls_for_of":
case "controls_for_of":
const listTp = returnType(e, getInputTargetBlock(b, "LIST"));
const elementTp = lookup(e, b, getLoopVariableField(b).getField("VAR").getText()).type;
genericLink(listTp, elementTp);
break;
case "variables_set":
case "variables_change":
let p1 = lookup(e, b, b.getField("VAR").getText()).type;
attachPlaceholderIf(e, b, "VALUE");
let rhs = getInputTargetBlock(b, "VALUE");
if (rhs) {
// Get the inheritance chain for this type and check to see if the existing
// type shows up in it somewhere
let tr = returnTypeWithInheritance(e, rhs);
const t1 = find(p1);
if (t1.type && tr.slice(1).some(p => p.type === t1.type)) {
// If it does, we want to take the most narrow type (which will always be in 0)
p1.link = find(tr[0]);
}
else {
try {
union(p1, tr[0]);
} catch (e) {
// TypeScript should catch this error and bubble it up
}
}
}
break;
case "controls_repeat_ext":
unionParam(e, b, "TIMES", ground(pNumber.type));
break;
case "device_while":
attachPlaceholderIf(e, b, "COND", pBoolean.type);
break;
case "lists_index_get":
unionParam(e, b, "LIST", ground("Array"));
unionParam(e, b, "INDEX", ground(pNumber.type));
const listType = returnType(e, getInputTargetBlock(b, "LIST"));
const ret = returnType(e, b);
genericLink(listType, ret);
break;
case "lists_index_set":
unionParam(e, b, "LIST", ground("Array"));
attachPlaceholderIf(e, b, "VALUE");
handleGenericType(b, "LIST");
unionParam(e, b, "INDEX", ground(pNumber.type));
break;
case 'function_definition':
getReturnTypeOfFunction(e, b.getField("function_name",).getText());
break;
case 'function_call':
case 'function_call_output':
(b as Blockly.FunctionCallBlock).getArguments().forEach(arg => {
unionParam(e, b, arg.id, ground(arg.type));
});
break;
case pxtc.TS_RETURN_STATEMENT_TYPE:
attachPlaceholderIf(e, b, "RETURN_VALUE");
break;
case pxtc.PAUSE_UNTIL_TYPE:
unionParam(e, b, "PREDICATE", pBoolean);
break;
default:
if (b.type in e.stdCallTable) {
const call = e.stdCallTable[b.type];
if (call.attrs.shim === "ENUM_GET" || call.attrs.shim === "KIND_GET") return;
visibleParams(call, countOptionals(b)).forEach((p, i) => {
const isInstance = call.isExtensionMethod && i === 0;
if (p.definitionName && !b.getFieldValue(p.definitionName)) {
let i = b.inputList.find((i: Blockly.Input) => i.name == p.definitionName);
if (i && i.connection && i.connection.check_) {
if (isInstance && connectionCheck(i) === "Array") {
let gen = handleGenericType(b, p.definitionName);
if (gen) {
return;
}
}
// All of our injected blocks have single output checks, but the builtin
// blockly ones like string.length and array.length might have multiple
for (let j = 0; j < i.connection.check_.length; j++) {
try {
let t = i.connection.check_[j];
unionParam(e, b, p.definitionName, ground(t));
break;
}
catch (e) {
// Ignore type checking errors in the blocks...
}
}
}
}
});
}
}
} catch (err) {
const be = ((<any>err).block as Blockly.Block) || b;
be.setWarningText(err + "");
e.errors.push(be);
}
});
// Last pass: if some variable has no type (because it was never used or
// assigned to), just unify it with int...
e.allVariables.forEach((v: VarInfo) => {
if (getConcreteType(v.type).type == null)
union(v.type, ground(v.type.isArrayType ? "number[]" : pNumber.type));
});
function connectionCheck(i: Blockly.Input) {
return i.name ? i.connection && i.connection.check_ && i.connection.check_.length ? i.connection.check_[0] : "T" : undefined;
}
function handleGenericType(b: Blockly.Block, name: string) {
let genericArgs = b.inputList.filter((input: Blockly.Input) => connectionCheck(input) === "T");
if (genericArgs.length) {
const gen = getInputTargetBlock(b, genericArgs[0].name);
if (gen) {
const arg = returnType(e, gen);
const arrayType = arg.type ? ground(returnType(e, gen).type + "[]") : ground(null);
genericLink(arrayType, arg);
unionParam(e, b, name, arrayType);
return true;
}
}
return false;
}
}