pxt-core 13.2.4 → 13.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/built/cli.js CHANGED
@@ -1859,8 +1859,8 @@ function saveThemeJson(cfg, localDir, packaged) {
1859
1859
  const url = card.url || card.learnMoreUrl || card.buyUrl || (card.youTubeId && `https://youtu.be/${card.youTubeId}`);
1860
1860
  tocmd += ` * [${card.name || card.title}](${url})
1861
1861
  `;
1862
- if (card.tags)
1863
- card.tags.forEach(tag => targetStrings[tag] = tag);
1862
+ for (const term of (card.tags || []).concat(card.searchTerms || []))
1863
+ targetStrings[term] = term;
1864
1864
  }));
1865
1865
  });
1866
1866
  nodeutil.writeFileSync(path.join(docsRoot, "docs/projects/SUMMARY.md"), tocmd, { encoding: "utf8" });
package/built/pxt.js CHANGED
@@ -108094,7 +108094,7 @@ var pxt;
108094
108094
  if (cards && !Array.isArray(cards))
108095
108095
  cards = [cards];
108096
108096
  if (cards === null || cards === void 0 ? void 0 : cards.length)
108097
- return cards;
108097
+ return cards.map(normalizeCodeCardStringArrays);
108098
108098
  // not json, try parsing as sequence of key,value pairs, with line splits
108099
108099
  cards = md.split(/^---$/gm)
108100
108100
  .filter(cmd => !!cmd)
@@ -108120,7 +108120,7 @@ var pxt;
108120
108120
  })
108121
108121
  .filter(cc => !!cc);
108122
108122
  if (cards === null || cards === void 0 ? void 0 : cards.length)
108123
- return cards;
108123
+ return cards.map(normalizeCodeCardStringArrays);
108124
108124
  return undefined;
108125
108125
  }
108126
108126
  gallery_1.parseCodeCards = parseCodeCards;
@@ -108161,9 +108161,18 @@ var pxt;
108161
108161
  // legacy JSON format
108162
108162
  cards = pxt.Util.jsonTryParse(el.textContent);
108163
108163
  }
108164
- return !!(cards === null || cards === void 0 ? void 0 : cards.length) && cards;
108164
+ return !!(cards === null || cards === void 0 ? void 0 : cards.length) && cards.map(normalizeCodeCardStringArrays);
108165
108165
  }
108166
108166
  gallery_1.parseCodeCardsHtml = parseCodeCardsHtml;
108167
+ function normalizeCodeCardStringArrays(card) {
108168
+ ["tags", "searchTerms"].forEach(field => {
108169
+ const value = card[field];
108170
+ if (typeof value === "string") {
108171
+ card[field] = value.split(',').map(term => term.trim()).filter(term => !!term);
108172
+ }
108173
+ });
108174
+ return card;
108175
+ }
108167
108176
  function parseGalleryMardown(md) {
108168
108177
  if (!md)
108169
108178
  return [];
@@ -116497,18 +116506,25 @@ var ts;
116497
116506
  pxt.debug(`Localized block has extra or missing parameters`);
116498
116507
  return false;
116499
116508
  }
116509
+ if (!!a.thisParameter != !!b.thisParameter
116510
+ || (a.thisParameter && !isEquivalentParameter(a.thisParameter, b.thisParameter))) {
116511
+ pxt.debug(`Localized block's this parameter shadow block or definition name does not match`);
116512
+ return false;
116513
+ }
116500
116514
  for (const aParam of a.parameters) {
116501
116515
  const bParam = b.actualNameToParam[aParam.actualName];
116502
- if (!bParam
116503
- || aParam.type != bParam.type
116504
- || aParam.shadowBlockId != bParam.shadowBlockId
116505
- || aParam.definitionName != bParam.definitionName) {
116516
+ if (!bParam || !isEquivalentParameter(aParam, bParam)) {
116506
116517
  pxt.debug(`Parameter ${aParam.actualName} type, shadow block, or definition name does not match after localization`);
116507
116518
  return false;
116508
116519
  }
116509
116520
  }
116510
116521
  return true;
116511
116522
  }
116523
+ function isEquivalentParameter(a, b) {
116524
+ return a.type == b.type
116525
+ && a.shadowBlockId == b.shadowBlockId
116526
+ && a.definitionName == b.definitionName;
116527
+ }
116512
116528
  function emptyExtInfo() {
116513
116529
  let cs = pxt.appTarget.compileService;
116514
116530
  if (!cs)
@@ -135162,6 +135178,7 @@ var ts;
135162
135178
  let currUsingContext = null;
135163
135179
  let needsUsingInfo = false;
135164
135180
  let pendingFunctionDefinitions = [];
135181
+ const loopBlockFunctionDefinitions = {};
135165
135182
  currNodeWave++;
135166
135183
  if (opts.target.isNative) {
135167
135184
  if (!opts.extinfo || !opts.extinfo.hexinfo) {
@@ -136462,10 +136479,14 @@ ${lbl}: .short 0xffff
136462
136479
  args.push(irToNode(expr));
136463
136480
  }
136464
136481
  else {
136465
- if (!opts.unfetteredInitializers && !isNumericLiteral(prm.initializer)) {
136466
- userError(9212, lf("only numbers, null, true and false supported as default arguments"));
136482
+ // Emitted functions apply their own defaults, including on
136483
+ // virtual/interface calls where the implementation may differ.
136484
+ // Shims and helpers may have no emitted parameter prologue.
136485
+ const needsCallerDefault = attrs.helper || attrs.shim;
136486
+ if (needsCallerDefault && !opts.unfetteredInitializers && !isNumericLiteral(prm.initializer)) {
136487
+ userError(9212, lf("only numbers, null, true and false supported as default arguments for shims and helpers"));
136467
136488
  }
136468
- args.push(prm.initializer);
136489
+ args.push(needsCallerDefault ? prm.initializer : irToNode(emitLit(undefined)));
136469
136490
  }
136470
136491
  }
136471
136492
  else {
@@ -137204,12 +137225,8 @@ ${lbl}: .short 0xffff
137204
137225
  }
137205
137226
  }
137206
137227
  }
137207
- const destructuredParameters = [];
137208
137228
  const fieldAssignmentParameters = [];
137209
137229
  proc.args = getParameters(node).map((p, i) => {
137210
- if (p.name.kind === pxtc.SK.ObjectBindingPattern) {
137211
- destructuredParameters.push(p);
137212
- }
137213
137230
  if (node.kind == pxtc.SK.Constructor && isCtorField(p)) {
137214
137231
  fieldAssignmentParameters.push(p);
137215
137232
  }
@@ -137227,7 +137244,34 @@ ${lbl}: .short 0xffff
137227
137244
  proc.emitExpr(l.storeDirect(tmp));
137228
137245
  }
137229
137246
  });
137230
- destructuredParameters.forEach(dp => emitVariableDeclaration(dp));
137247
+ // Arity wrappers pad omitted arguments with undefined. Keep defaults in
137248
+ // the shared body so direct, virtual, interface and action calls all run
137249
+ // them, including native entries which can skip the arity wrapper.
137250
+ const attrs = parseComments(node);
137251
+ proc.args.forEach(l => {
137252
+ const parameter = l.def;
137253
+ // Simulator shim dummies must match the native caller-side behavior.
137254
+ if (parameter.initializer && !attrs.shim && !attrs.helper) {
137255
+ const supplied = proc.mkLabel("defaultarg");
137256
+ if (isThumb()) {
137257
+ // Tagged undefined is 0, so the guard is a bare
137258
+ // compare against zero rather than a runtime call.
137259
+ proc.emitJmp(supplied, l.load(), pxtc.ir.JmpMode.IfNotZero);
137260
+ }
137261
+ else {
137262
+ // The simulator holds real JS values; a raw zero test
137263
+ // would treat a supplied 0/false/null as omitted.
137264
+ proc.emitJmpZ(supplied, pxtc.ir.rtcall("pxt::eqq_bool", [l.load(), emitLit(undefined)]));
137265
+ }
137266
+ proc.emitExpr(l.storeByRef(emitEscapedExpression(parameter.initializer, "initializer")));
137267
+ proc.emitLbl(supplied);
137268
+ proc.stackEmpty();
137269
+ }
137270
+ if (parameter.name.kind === pxtc.SK.ObjectBindingPattern) {
137271
+ // Destructure the resolved argument, not the initializer again.
137272
+ emitVarOrParam(parameter, l.load(), typeOf(parameter));
137273
+ }
137274
+ });
137231
137275
  // for constructor(public foo:number) generate this.foo = foo;
137232
137276
  for (let p of fieldAssignmentParameters) {
137233
137277
  let idx = fieldIndexCore(proc.classInfo, getFieldInfo(proc.classInfo, getName(p)), false);
@@ -137270,11 +137314,25 @@ ${lbl}: .short 0xffff
137270
137314
  if (info.capturedVars.length &&
137271
137315
  info.usedBeforeDecl &&
137272
137316
  node.kind == pxtc.SK.FunctionDeclaration && !bin.finalPass) {
137273
- info.capturedVars.sort((a, b) => b.pos - a.pos);
137274
- const vinfo = getVarInfo(info.capturedVars[0]);
137275
- if (!vinfo.functionsToDefine)
137276
- vinfo.functionsToDefine = [];
137277
- vinfo.functionsToDefine.push(node);
137317
+ // Synthetic captures (such as thisParameter) are available before
137318
+ // source declarations, but may not have a source position.
137319
+ const capturePos = (capture) => capture.pos == null ? -1 : capture.pos;
137320
+ info.capturedVars.sort((a, b) => capturePos(b) - capturePos(a));
137321
+ const scope = node.parent;
137322
+ if (scope.kind === pxtc.SK.Block && inLoop(scope) && info.capturedVars.every(capture => capturePos(capture) < scope.pos)) {
137323
+ // All captures precede this loop block. Defining the function at
137324
+ // the last captured variable would hoist it out of the iteration.
137325
+ const id = getNodeId(scope);
137326
+ const definitions = loopBlockFunctionDefinitions[id] || (loopBlockFunctionDefinitions[id] = []);
137327
+ if (definitions.indexOf(node) < 0)
137328
+ definitions.push(node);
137329
+ }
137330
+ else {
137331
+ const vinfo = getVarInfo(info.capturedVars[0]);
137332
+ if (!vinfo.functionsToDefine)
137333
+ vinfo.functionsToDefine = [];
137334
+ vinfo.functionsToDefine.push(node);
137335
+ }
137278
137336
  }
137279
137337
  // nothing should be on work list in final pass - everything should be already marked as used
137280
137338
  pxtc.assert(!bin.finalPass || usedWorkList.length == 0, "!bin.finalPass || usedWorkList.length == 0");
@@ -138231,6 +138289,11 @@ ${lbl}: .short 0xffff
138231
138289
  function emitSpreadElementExpression(node) { }
138232
138290
  function emitYieldExpression(node) { }
138233
138291
  function emitBlock(node) {
138292
+ const definitions = bin.finalPass && loopBlockFunctionDefinitions[getNodeId(node)];
138293
+ if (definitions) {
138294
+ pxtc.U.pushRange(pendingFunctionDefinitions, definitions);
138295
+ flushHoistedFunctionDefinitions();
138296
+ }
138234
138297
  node.statements.forEach(emit);
138235
138298
  }
138236
138299
  function checkForLetOrConst(declList) {
@@ -138416,16 +138479,45 @@ ${lbl}: .short 0xffff
138416
138479
  proc.emitExpr(v);
138417
138480
  proc.stackEmpty();
138418
138481
  }
138482
+ function emitForIterationBindings(declList) {
138483
+ // Only `let` loop headers create copied bindings. `const` and variables
138484
+ // declared outside the loop retain their existing binding semantics.
138485
+ if (!declList || !(declList.flags & ts.NodeFlags.Let))
138486
+ return;
138487
+ const copyBinding = (decl) => {
138488
+ if (decl.name.kind === pxtc.SK.ObjectBindingPattern || decl.name.kind === pxtc.SK.ArrayBindingPattern) {
138489
+ decl.name.elements.forEach(element => {
138490
+ if (element.kind === pxtc.SK.BindingElement)
138491
+ copyBinding(element);
138492
+ });
138493
+ return;
138494
+ }
138495
+ const loc = proc.localIndex(decl);
138496
+ if (!loc || !loc.isByRefLocal())
138497
+ return;
138498
+ // Populate the new box before replacing the old cell. Closures already
138499
+ // created keep the old box; sibling closures in this iteration share the new one.
138500
+ const next = pxtc.ir.shared(pxtc.ir.rtcall("pxtrt::mklocRef", []));
138501
+ proc.emitExpr(pxtc.ir.rtcall("pxtrt::stlocRef", [next, loc.load()]));
138502
+ proc.emitExpr(loc.storeDirect(next));
138503
+ proc.stackEmpty();
138504
+ };
138505
+ declList.declarations.forEach(copyBinding);
138506
+ }
138419
138507
  function emitForStatement(node) {
138508
+ let declList;
138420
138509
  if (node.initializer && node.initializer.kind == pxtc.SK.VariableDeclarationList) {
138421
- checkForLetOrConst(node.initializer);
138422
- node.initializer.declarations.forEach(emit);
138510
+ declList = node.initializer;
138511
+ checkForLetOrConst(declList);
138512
+ declList.declarations.forEach(emit);
138423
138513
  }
138424
138514
  else {
138425
138515
  emitExprAsStmt(node.initializer);
138426
138516
  }
138427
138517
  emitBrk(node);
138428
138518
  let l = getLabels(node);
138519
+ // Initializer closures keep their own binding, separate from the first condition/body.
138520
+ emitForIterationBindings(declList);
138429
138521
  proc.emitLblDirect(l.fortop);
138430
138522
  if (node.condition) {
138431
138523
  emitBrk(node.condition);
@@ -138433,6 +138525,8 @@ ${lbl}: .short 0xffff
138433
138525
  }
138434
138526
  emit(node.statement);
138435
138527
  proc.emitLblDirect(l.cont);
138528
+ // A continue must also create the next binding BEFORE evaluating the incrementor.
138529
+ emitForIterationBindings(declList);
138436
138530
  emitExprAsStmt(node.incrementor);
138437
138531
  proc.emitJmp(l.fortop);
138438
138532
  proc.emitLblDirect(l.brk);
@@ -138492,6 +138586,11 @@ ${lbl}: .short 0xffff
138492
138586
  };
138493
138587
  // c = a[i]
138494
138588
  if (iterVar) {
138589
+ // A mutable captured for-of variable needs a fresh box for each element,
138590
+ // rather than overwriting the box retained by closures from earlier elements.
138591
+ if (iterVar.isByRefLocal()) {
138592
+ proc.emitExpr(iterVar.storeDirect(pxtc.ir.rtcall("pxtrt::mklocRef", [])));
138593
+ }
138495
138594
  proc.emitExpr(iterVar.storeByRef(pxtc.ir.rtcall(indexer, [collectionVar.loadCore(), toInt(intVarIter.loadCore())])));
138496
138595
  emitBrk(node.initializer);
138497
138596
  }
@@ -138605,7 +138704,10 @@ ${lbl}: .short 0xffff
138605
138704
  function emitLabeledStatement(node) {
138606
138705
  let l = getLabels(node.statement);
138607
138706
  emit(node.statement);
138608
- proc.emitLblDirect(l.brk);
138707
+ // Loops and switches define their own break target. Emitting it twice
138708
+ // is tolerated by simjs but rejected by the native assembler.
138709
+ if (!ts.isIterationStatement(node.statement, false) && node.statement.kind !== pxtc.SK.SwitchStatement)
138710
+ proc.emitLblDirect(l.brk);
138609
138711
  }
138610
138712
  function emitThrowStatement(node) {
138611
138713
  emitBrk(node);
@@ -166649,8 +166751,8 @@ function saveThemeJson(cfg, localDir, packaged) {
166649
166751
  const url = card.url || card.learnMoreUrl || card.buyUrl || (card.youTubeId && `https://youtu.be/${card.youTubeId}`);
166650
166752
  tocmd += ` * [${card.name || card.title}](${url})
166651
166753
  `;
166652
- if (card.tags)
166653
- card.tags.forEach(tag => targetStrings[tag] = tag);
166754
+ for (const term of (card.tags || []).concat(card.searchTerms || []))
166755
+ targetStrings[term] = term;
166654
166756
  }));
166655
166757
  });
166656
166758
  nodeutil.writeFileSync(path.join(docsRoot, "docs/projects/SUMMARY.md"), tocmd, { encoding: "utf8" });
@@ -10178,6 +10178,7 @@ var ts;
10178
10178
  let currUsingContext = null;
10179
10179
  let needsUsingInfo = false;
10180
10180
  let pendingFunctionDefinitions = [];
10181
+ const loopBlockFunctionDefinitions = {};
10181
10182
  currNodeWave++;
10182
10183
  if (opts.target.isNative) {
10183
10184
  if (!opts.extinfo || !opts.extinfo.hexinfo) {
@@ -11478,10 +11479,14 @@ ${lbl}: .short 0xffff
11478
11479
  args.push(irToNode(expr));
11479
11480
  }
11480
11481
  else {
11481
- if (!opts.unfetteredInitializers && !isNumericLiteral(prm.initializer)) {
11482
- userError(9212, lf("only numbers, null, true and false supported as default arguments"));
11482
+ // Emitted functions apply their own defaults, including on
11483
+ // virtual/interface calls where the implementation may differ.
11484
+ // Shims and helpers may have no emitted parameter prologue.
11485
+ const needsCallerDefault = attrs.helper || attrs.shim;
11486
+ if (needsCallerDefault && !opts.unfetteredInitializers && !isNumericLiteral(prm.initializer)) {
11487
+ userError(9212, lf("only numbers, null, true and false supported as default arguments for shims and helpers"));
11483
11488
  }
11484
- args.push(prm.initializer);
11489
+ args.push(needsCallerDefault ? prm.initializer : irToNode(emitLit(undefined)));
11485
11490
  }
11486
11491
  }
11487
11492
  else {
@@ -12220,12 +12225,8 @@ ${lbl}: .short 0xffff
12220
12225
  }
12221
12226
  }
12222
12227
  }
12223
- const destructuredParameters = [];
12224
12228
  const fieldAssignmentParameters = [];
12225
12229
  proc.args = getParameters(node).map((p, i) => {
12226
- if (p.name.kind === pxtc.SK.ObjectBindingPattern) {
12227
- destructuredParameters.push(p);
12228
- }
12229
12230
  if (node.kind == pxtc.SK.Constructor && isCtorField(p)) {
12230
12231
  fieldAssignmentParameters.push(p);
12231
12232
  }
@@ -12243,7 +12244,34 @@ ${lbl}: .short 0xffff
12243
12244
  proc.emitExpr(l.storeDirect(tmp));
12244
12245
  }
12245
12246
  });
12246
- destructuredParameters.forEach(dp => emitVariableDeclaration(dp));
12247
+ // Arity wrappers pad omitted arguments with undefined. Keep defaults in
12248
+ // the shared body so direct, virtual, interface and action calls all run
12249
+ // them, including native entries which can skip the arity wrapper.
12250
+ const attrs = parseComments(node);
12251
+ proc.args.forEach(l => {
12252
+ const parameter = l.def;
12253
+ // Simulator shim dummies must match the native caller-side behavior.
12254
+ if (parameter.initializer && !attrs.shim && !attrs.helper) {
12255
+ const supplied = proc.mkLabel("defaultarg");
12256
+ if (isThumb()) {
12257
+ // Tagged undefined is 0, so the guard is a bare
12258
+ // compare against zero rather than a runtime call.
12259
+ proc.emitJmp(supplied, l.load(), pxtc.ir.JmpMode.IfNotZero);
12260
+ }
12261
+ else {
12262
+ // The simulator holds real JS values; a raw zero test
12263
+ // would treat a supplied 0/false/null as omitted.
12264
+ proc.emitJmpZ(supplied, pxtc.ir.rtcall("pxt::eqq_bool", [l.load(), emitLit(undefined)]));
12265
+ }
12266
+ proc.emitExpr(l.storeByRef(emitEscapedExpression(parameter.initializer, "initializer")));
12267
+ proc.emitLbl(supplied);
12268
+ proc.stackEmpty();
12269
+ }
12270
+ if (parameter.name.kind === pxtc.SK.ObjectBindingPattern) {
12271
+ // Destructure the resolved argument, not the initializer again.
12272
+ emitVarOrParam(parameter, l.load(), typeOf(parameter));
12273
+ }
12274
+ });
12247
12275
  // for constructor(public foo:number) generate this.foo = foo;
12248
12276
  for (let p of fieldAssignmentParameters) {
12249
12277
  let idx = fieldIndexCore(proc.classInfo, getFieldInfo(proc.classInfo, getName(p)), false);
@@ -12286,11 +12314,25 @@ ${lbl}: .short 0xffff
12286
12314
  if (info.capturedVars.length &&
12287
12315
  info.usedBeforeDecl &&
12288
12316
  node.kind == pxtc.SK.FunctionDeclaration && !bin.finalPass) {
12289
- info.capturedVars.sort((a, b) => b.pos - a.pos);
12290
- const vinfo = getVarInfo(info.capturedVars[0]);
12291
- if (!vinfo.functionsToDefine)
12292
- vinfo.functionsToDefine = [];
12293
- vinfo.functionsToDefine.push(node);
12317
+ // Synthetic captures (such as thisParameter) are available before
12318
+ // source declarations, but may not have a source position.
12319
+ const capturePos = (capture) => capture.pos == null ? -1 : capture.pos;
12320
+ info.capturedVars.sort((a, b) => capturePos(b) - capturePos(a));
12321
+ const scope = node.parent;
12322
+ if (scope.kind === pxtc.SK.Block && inLoop(scope) && info.capturedVars.every(capture => capturePos(capture) < scope.pos)) {
12323
+ // All captures precede this loop block. Defining the function at
12324
+ // the last captured variable would hoist it out of the iteration.
12325
+ const id = getNodeId(scope);
12326
+ const definitions = loopBlockFunctionDefinitions[id] || (loopBlockFunctionDefinitions[id] = []);
12327
+ if (definitions.indexOf(node) < 0)
12328
+ definitions.push(node);
12329
+ }
12330
+ else {
12331
+ const vinfo = getVarInfo(info.capturedVars[0]);
12332
+ if (!vinfo.functionsToDefine)
12333
+ vinfo.functionsToDefine = [];
12334
+ vinfo.functionsToDefine.push(node);
12335
+ }
12294
12336
  }
12295
12337
  // nothing should be on work list in final pass - everything should be already marked as used
12296
12338
  pxtc.assert(!bin.finalPass || usedWorkList.length == 0, "!bin.finalPass || usedWorkList.length == 0");
@@ -13247,6 +13289,11 @@ ${lbl}: .short 0xffff
13247
13289
  function emitSpreadElementExpression(node) { }
13248
13290
  function emitYieldExpression(node) { }
13249
13291
  function emitBlock(node) {
13292
+ const definitions = bin.finalPass && loopBlockFunctionDefinitions[getNodeId(node)];
13293
+ if (definitions) {
13294
+ pxtc.U.pushRange(pendingFunctionDefinitions, definitions);
13295
+ flushHoistedFunctionDefinitions();
13296
+ }
13250
13297
  node.statements.forEach(emit);
13251
13298
  }
13252
13299
  function checkForLetOrConst(declList) {
@@ -13432,16 +13479,45 @@ ${lbl}: .short 0xffff
13432
13479
  proc.emitExpr(v);
13433
13480
  proc.stackEmpty();
13434
13481
  }
13482
+ function emitForIterationBindings(declList) {
13483
+ // Only `let` loop headers create copied bindings. `const` and variables
13484
+ // declared outside the loop retain their existing binding semantics.
13485
+ if (!declList || !(declList.flags & ts.NodeFlags.Let))
13486
+ return;
13487
+ const copyBinding = (decl) => {
13488
+ if (decl.name.kind === pxtc.SK.ObjectBindingPattern || decl.name.kind === pxtc.SK.ArrayBindingPattern) {
13489
+ decl.name.elements.forEach(element => {
13490
+ if (element.kind === pxtc.SK.BindingElement)
13491
+ copyBinding(element);
13492
+ });
13493
+ return;
13494
+ }
13495
+ const loc = proc.localIndex(decl);
13496
+ if (!loc || !loc.isByRefLocal())
13497
+ return;
13498
+ // Populate the new box before replacing the old cell. Closures already
13499
+ // created keep the old box; sibling closures in this iteration share the new one.
13500
+ const next = pxtc.ir.shared(pxtc.ir.rtcall("pxtrt::mklocRef", []));
13501
+ proc.emitExpr(pxtc.ir.rtcall("pxtrt::stlocRef", [next, loc.load()]));
13502
+ proc.emitExpr(loc.storeDirect(next));
13503
+ proc.stackEmpty();
13504
+ };
13505
+ declList.declarations.forEach(copyBinding);
13506
+ }
13435
13507
  function emitForStatement(node) {
13508
+ let declList;
13436
13509
  if (node.initializer && node.initializer.kind == pxtc.SK.VariableDeclarationList) {
13437
- checkForLetOrConst(node.initializer);
13438
- node.initializer.declarations.forEach(emit);
13510
+ declList = node.initializer;
13511
+ checkForLetOrConst(declList);
13512
+ declList.declarations.forEach(emit);
13439
13513
  }
13440
13514
  else {
13441
13515
  emitExprAsStmt(node.initializer);
13442
13516
  }
13443
13517
  emitBrk(node);
13444
13518
  let l = getLabels(node);
13519
+ // Initializer closures keep their own binding, separate from the first condition/body.
13520
+ emitForIterationBindings(declList);
13445
13521
  proc.emitLblDirect(l.fortop);
13446
13522
  if (node.condition) {
13447
13523
  emitBrk(node.condition);
@@ -13449,6 +13525,8 @@ ${lbl}: .short 0xffff
13449
13525
  }
13450
13526
  emit(node.statement);
13451
13527
  proc.emitLblDirect(l.cont);
13528
+ // A continue must also create the next binding BEFORE evaluating the incrementor.
13529
+ emitForIterationBindings(declList);
13452
13530
  emitExprAsStmt(node.incrementor);
13453
13531
  proc.emitJmp(l.fortop);
13454
13532
  proc.emitLblDirect(l.brk);
@@ -13508,6 +13586,11 @@ ${lbl}: .short 0xffff
13508
13586
  };
13509
13587
  // c = a[i]
13510
13588
  if (iterVar) {
13589
+ // A mutable captured for-of variable needs a fresh box for each element,
13590
+ // rather than overwriting the box retained by closures from earlier elements.
13591
+ if (iterVar.isByRefLocal()) {
13592
+ proc.emitExpr(iterVar.storeDirect(pxtc.ir.rtcall("pxtrt::mklocRef", [])));
13593
+ }
13511
13594
  proc.emitExpr(iterVar.storeByRef(pxtc.ir.rtcall(indexer, [collectionVar.loadCore(), toInt(intVarIter.loadCore())])));
13512
13595
  emitBrk(node.initializer);
13513
13596
  }
@@ -13621,7 +13704,10 @@ ${lbl}: .short 0xffff
13621
13704
  function emitLabeledStatement(node) {
13622
13705
  let l = getLabels(node.statement);
13623
13706
  emit(node.statement);
13624
- proc.emitLblDirect(l.brk);
13707
+ // Loops and switches define their own break target. Emitting it twice
13708
+ // is tolerated by simjs but rejected by the native assembler.
13709
+ if (!ts.isIterationStatement(node.statement, false) && node.statement.kind !== pxtc.SK.SwitchStatement)
13710
+ proc.emitLblDirect(l.brk);
13625
13711
  }
13626
13712
  function emitThrowStatement(node) {
13627
13713
  emitBrk(node);
package/built/pxtlib.js CHANGED
@@ -10373,7 +10373,7 @@ var pxt;
10373
10373
  if (cards && !Array.isArray(cards))
10374
10374
  cards = [cards];
10375
10375
  if (cards === null || cards === void 0 ? void 0 : cards.length)
10376
- return cards;
10376
+ return cards.map(normalizeCodeCardStringArrays);
10377
10377
  // not json, try parsing as sequence of key,value pairs, with line splits
10378
10378
  cards = md.split(/^---$/gm)
10379
10379
  .filter(cmd => !!cmd)
@@ -10399,7 +10399,7 @@ var pxt;
10399
10399
  })
10400
10400
  .filter(cc => !!cc);
10401
10401
  if (cards === null || cards === void 0 ? void 0 : cards.length)
10402
- return cards;
10402
+ return cards.map(normalizeCodeCardStringArrays);
10403
10403
  return undefined;
10404
10404
  }
10405
10405
  gallery_1.parseCodeCards = parseCodeCards;
@@ -10440,9 +10440,18 @@ var pxt;
10440
10440
  // legacy JSON format
10441
10441
  cards = pxt.Util.jsonTryParse(el.textContent);
10442
10442
  }
10443
- return !!(cards === null || cards === void 0 ? void 0 : cards.length) && cards;
10443
+ return !!(cards === null || cards === void 0 ? void 0 : cards.length) && cards.map(normalizeCodeCardStringArrays);
10444
10444
  }
10445
10445
  gallery_1.parseCodeCardsHtml = parseCodeCardsHtml;
10446
+ function normalizeCodeCardStringArrays(card) {
10447
+ ["tags", "searchTerms"].forEach(field => {
10448
+ const value = card[field];
10449
+ if (typeof value === "string") {
10450
+ card[field] = value.split(',').map(term => term.trim()).filter(term => !!term);
10451
+ }
10452
+ });
10453
+ return card;
10454
+ }
10446
10455
  function parseGalleryMardown(md) {
10447
10456
  if (!md)
10448
10457
  return [];
@@ -18776,18 +18785,25 @@ var ts;
18776
18785
  pxt.debug(`Localized block has extra or missing parameters`);
18777
18786
  return false;
18778
18787
  }
18788
+ if (!!a.thisParameter != !!b.thisParameter
18789
+ || (a.thisParameter && !isEquivalentParameter(a.thisParameter, b.thisParameter))) {
18790
+ pxt.debug(`Localized block's this parameter shadow block or definition name does not match`);
18791
+ return false;
18792
+ }
18779
18793
  for (const aParam of a.parameters) {
18780
18794
  const bParam = b.actualNameToParam[aParam.actualName];
18781
- if (!bParam
18782
- || aParam.type != bParam.type
18783
- || aParam.shadowBlockId != bParam.shadowBlockId
18784
- || aParam.definitionName != bParam.definitionName) {
18795
+ if (!bParam || !isEquivalentParameter(aParam, bParam)) {
18785
18796
  pxt.debug(`Parameter ${aParam.actualName} type, shadow block, or definition name does not match after localization`);
18786
18797
  return false;
18787
18798
  }
18788
18799
  }
18789
18800
  return true;
18790
18801
  }
18802
+ function isEquivalentParameter(a, b) {
18803
+ return a.type == b.type
18804
+ && a.shadowBlockId == b.shadowBlockId
18805
+ && a.definitionName == b.definitionName;
18806
+ }
18791
18807
  function emptyExtInfo() {
18792
18808
  let cs = pxt.appTarget.compileService;
18793
18809
  if (!cs)