Generating Code in a statement block fails when connecting blocks in a validator

531 views
Skip to first unread message

Finn Nicolaysen

unread,
Mar 23, 2023, 9:56:14 AM3/23/23
to Blockly
Hello,

I have a couple of blocks that use dynamically created shadow blocks. I create them in the workspace and connect them to my block in a validator method of a FieldNumber or the like. Everything works fine and code is being generated correctly. But when I use one of those blocks in a statement block (while loop, if, etc.) I get the following error:

TypeError: Expecting code from statement block: my_block_name

At first I thought the code generation for that block is faulty but even when I return an empty string I get the same error. I looked into the code that throws the error and its being thrown when the type of returned code isn't string, I logged the code that is being returned and it's 

Array(2)
  1. 0: "\"\""
  2. 1: 0


  1. So it is clearly the wrong code and not the code my block creates. Maybe blockly thinks that one of the shadow blocks is the code that need to be returned?

  1. When removing all the lines where I connect shadow blocks the generation works. I tried to play around with Blockly.Events.disable and enable but to no avail.





Here are the code excerpts, if you need more just tell me.
The Block:
Blockly.Blocks['initialize_list_with_values'] = {
  init: function () {
    this.appendDummyInput()
      .appendField(getTranslation('lists:initializeList'))
      .appendField(
        new Blockly.FieldVariable(
          null,
          function (variableId) {
            this.sourceBlock_.variableChanged_(variableId);
          },
          ['string[]', 'int[]', 'char[]', 'bool[]'],
          'string[]'
        ),
        'NAME'
      )
      .appendField(getTranslation('lists:withLength'))
      .appendField(
        new Blockly.FieldNumber(1, 1, 30, null, this.lengthChanged_.bind(this)),
        'LENGTH'
      )
      .appendField(getTranslation('lists:withValues'));
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(COLORS.text);
    this.setTooltip('');
    this.setHelpUrl('');
  },
  variableChanged_: function (variableId) {
    Blockly.Events.disable();

    var variable = Blockly.Variables.getVariable(this.workspace, variableId);
    var type = variable.type.slice(0, -2);

    this.setColour(COLORS[type]);

    var length = this.inputList.length - 1;
    variable.length = length;

    for (let i = 1; i <= length; i++) {
      var otherConnection = this.inputList[i]?.connection?.targetConnection;

      if (otherConnection) {
        this.inputList[i].connection.disconnect();
        this.inputList[i].setCheck(type);
        if (
          otherConnection.check_ &&
          this.inputList[i].connection.check_ &&
          otherConnection.check_[0] != this.inputList[i].connection.check_[0]
        ) {
          otherConnection.sourceBlock_.dispose();
          if (!otherConnection.sourceBlock_.isShadow()) {
            var shadowBlockDom = this.inputList[i].connection.getShadowDom();
            this.workspace.getBlockById(shadowBlockDom.id).dispose();
          }
          var shadowBlock = getShadowBlockByType(this.workspace, type);
          this.inputList[i].connection.connect(shadowBlock.outputConnection);
          continue;
        }

        this.inputList[i].connection.connect(otherConnection);
      } else {
        var shadowBlock = getShadowBlockByType(this.workspace, type);
        this.inputList[i].connection.connect(shadowBlock.outputConnection);
      }
    }

    Blockly.Events.enable();
  },
  lengthChanged_: function (length) {
    var currentConnections = [];

    var currentInputs = this.inputList.length;
    for (let i = 0; i < currentInputs; i++) {
      var otherConnection = this.inputList[i]?.connection?.targetConnection;
      if (otherConnection) {
        if (i > length) {
          otherConnection.sourceBlock_.dispose();
          continue;
        }
        this.inputList[i].connection.disconnect();
        currentConnections.push({ index: i - 1, connection: otherConnection });
      }
    }

    for (let i = 0; i < currentInputs; i++) {
      this.removeInput('VALUE' + (i - 1), true);
    }

    var variable = Blockly.Variables.getVariable(
      this.workspace,
      this.inputList[0].fieldRow[1].getValue()
    );

    var type = variable ? variable.type.slice(0, -2) : null;

    if (!type) {
      return;
    }

    this.setColour(COLORS[type]);
    if (variable) {
      variable.length = length;
    }

    for (let i = 0; i < length; i++) {
      this.appendValueInput('VALUE' + i)
        .appendField(i + ':')
        .setCheck(type);

      var existingConnection = currentConnections
        .map((x) => x.index)
        .indexOf(i);

      var shadowBlock = getShadowBlockByType(this.workspace, type);
      if (shadowBlock) {
When removing this line of code, code generation works.
        this.getInput('VALUE' + i).connection.connect(
          shadowBlock.outputConnection
        );

        if (existingConnection > -1) {
          this.inputList[existingConnection + 1].connection.connect(
            currentConnections[existingConnection].connection
          );
        }
      }
    }
  },
};

Creating shadow blocks:
export const getShadowBlock = (workspace, block) => {
  var shadowBlock = workspace.newBlock(block);
  shadowBlock.setShadow(true);
  shadowBlock.initSvg();
  shadowBlock.render();

  return shadowBlock;
};

export const getShadowBlockByType = (workspace, type) => {
  switch (type) {
    case 'int':
      return getShadowBlock(workspace, 'number');
    case 'char':
      return getShadowBlock(workspace, 'char');
    case 'string':
      return getShadowBlock(workspace, 'text');
    case 'bool':
      return getShadowBlock(workspace, 'false');
    default:
      break;
  }
};

I attached a gif illustrating the problem.
(In the gif the error occurs on the second try, so sometimes it works. And it ALWAYS works when the list block isn't the first block inside the statement block, so having another block in front of it solves the issue...)
Any help is appreciated, I spent way too much time on it already.
initialize_list_with_values.gif

Beka Westberg

unread,
Mar 23, 2023, 11:56:09 AM3/23/23
to blo...@googlegroups.com
Hello! Thank you for your question :D

So I think there are actually a few things here:

Firstly, I'm guessing the error you're running into is `TypeError: Expecting string from statement block: initialize_list_with_values`. Statement blocks (ones with previous connections) are expected to return a code string, while value blocks (ones with output connections) are expected to return an array of [code string, operator precedence]. I can't see your generator, but I'm guessing that when your block has shadows, you're returning an array rather than a string directly.

If this is not the case, it would be very helpful for you to post your code generator so we can give it a look!

Secondly, rather than creating the shadow blocks yourself and attaching them, you should use `myConnection.setShadowState(myShadowState)`. setShadow is internal, and not meant to be used by external developers :/ Relatedly, if you connect the shadow blocks directly, they won't be recreated if you overwrite them with a real block and then remove the real block.

I hope that helps! If you have any further questions please reply =)
--Beka

--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/10000212-141c-4c8b-9bea-ffa20437cecbn%40googlegroups.com.

Finn Nicolaysen

unread,
Mar 24, 2023, 7:16:28 AM3/24/23
to Blockly
Thanks for the quick response. Which version of blockly adds the setShadowState method? I am using blockly in react, so I use the react-blockly package, which has a dependency on version >= 3.20200024.0 being resolved to version 5.20210325.1 

So then I tried using the newest version of blockly which results in blockly not knowing my custom blocks anymore. (And an error with the useBlocklyWorkspace hook)

This seems to have appeared elsewhere but I couldn't find a fix, did the way blocks are being created change between version 5 and 9? I'm creating all of them with JS like the 'initialize_list_with_values' block and use a JSON toolbox. 

The error is "TypeError: Expecting code from  statement block: initialize_list_with_values", you can see it at the end of the gif I attached. I don't think the problem is in the generator of my block as I get an error even when returning an empty string. 

But here is my generator code: 
Blockly.Code['initialize_list_with_values'] = function (block) {
  var length = block.getFieldValue('LENGTH');
  var list = Blockly.Code.variableDB_.getName(
    block.getFieldValue('NAME'),
    Blockly.VARIABLE_CATEGORY_NAME
  );

  var code = `${list} = { `;

  for (let i = 0; i < length; i++) {
    code += Blockly.Code.valueToCode(
      block,
      'VALUE' + i,
      Blockly.Code.ORDER_NONE
    );

    if (i != length - 1) {
      code += ', ';
    }
  }

  code += ' };\n';

  return code;
}; 

Beka Westberg

unread,
Mar 24, 2023, 8:18:12 PM3/24/23
to Blockly
Hello again!

> The error is "TypeError: Expecting code from  statement block: initialize_list_with_values", you can see it at the end of the gif I attached.

Haha appologies, my eyes + github search failed me.

That asside, this problem is very strange. I have no idea why this would be happening. The language generator only does exactly what you tell it to (e.g. it only converts a value block to code when you call `valueToCode`). So there's not even a place for it to "think" it should generate the code for shadow blocks rather than your statement block. Because your `if` block tells it exactly what block to trigger code generation for. Would it be possible for you to post a link to your repository so we could poke at what's happening?

I'm also kind of curious what `Blockly.Code` is. Is this a custom language generator you're using?

With regard to `setShadowState` that was added in v7, but you can use `setShadowDom` for v3-v7, which takes in XML rather than JSON =)

Hopefully with a bit more information we can get this sorted out!
--Beka

Finn Nicolaysen

unread,
Mar 27, 2023, 5:58:24 AM3/27/23
to Blockly
Hi,

I've created a repository on github with the code needed to reproduce the error. The error occurs reliably when dragging the initialize_list_with_values block out of the statement block. 

Yes, Blockly.Code is a custom language generator. 

For now I'll just use normal blocks instead of shadow blocks, I'll tackle that problem later ^^' 

Beka Westberg

unread,
Mar 27, 2023, 2:56:00 PM3/27/23
to blo...@googlegroups.com
Ah ok I think I figured out the problem! I believe the issue is that the code is appending shadow blocks to the insertion-marker version of the `initialize_list_with_values` block.

We try to skip past insertion markers when generating code, because it often produces erroneous results. And usually this works because insertion markers only have one child. But the shadows are messing this up :/ So when the generator is skipping past your insertion marker it's generating the code for your shadow blocks. And because they're value blocks instead of statement blocks, it's giving you that error!

So you should be able to check if your block is an insertion marker and skip appending the shadows if so, and that should fix the problem!

Best wishes,
--Beka 

Finn Nicolaysen

unread,
Mar 28, 2023, 7:57:41 AM3/28/23
to Blockly
Wow, thank you so much, works like a charm now. I wouldn't have been able to solve this on my own :D 

Great work and thanks again, cheers! 

Reply all
Reply to author
Forward
0 new messages