Connection checks

Connection checks restrict which connections (and therefor blocks) can connect to each other.

Connection checks are useful for modeling types. For example, the following three blocks have no business being connected, because they represent code that returns different types:

An empty list block, connected to a square root block, connected to an
uppercase block

Connection checks can be used to prevent these blocks from connecting. This gives users instantaneous feedback and prevents many simple mistakes.

How they work

Every connection can be associated with a "connection check" which is a nullable array of strings.

Two connections can connect if:

  1. They are compatible types (e.g. an output connecting to an input).
  2. They have at least one string in their connection check in common.

For example, the following two checks could connect, because they share the 'apple' string:

['apple', 'ball', 'cat']
['apple', 'bear', 'caterpillar']

But these two checks couldn't connect, because they don't share any strings:

['apple', 'ball', 'cat']
['ape', 'bear', 'caterpillar']

There is one other special case. If either array is null, then the two connections can also connect. This lets you define connections that can connect to anything.

null
['ape', 'bear', 'caterpillar]

Set checks

By default, all connections have a null connection-check, meaning they can connect to anything. Connection checks need to be assigned manually.

How you assign connection checks to connections is different depending on whether you're using JSON block definitions, or JavaScript block definitions.

JSON

For top-level connections, you assign the check directly to the property that defines the connection. The value you assign can be null, a string (which becomes the only entry in the connection check), or an array of strings.

{
  'type': 'custom_block',

  'output': null,
  'nextStatement': 'a connection check entry',
  'previousStatement': ['four', 'connection', 'check', 'entries']
}

For inputs, you can assign the check to a check property of the input definition. If the check property doesn't exist, the check is considered null. The value you assign can be a string, or an array of strings.

{
  'type': 'custom_block',
  'message0': '%1 %2',

  'args0': [
    {
      'type': 'input_value',
      'check': 'a connection check entry'
    },
    {
      'type': 'input_statement',
      'check': ['four', 'connection', 'check', 'entries']
    }
  ]
}

JavaScript

For top-level connections, you can pass the check directly to the method that defines the connection. If you don't pass a value, the check is considered null. The value you pass can be a string (which becomes the only entry in the connection check), or an array of strings.

Blockly.Blocks['custom_block'] = {
  init: function() {
    this.setOutput(true); // null check
    this.setNextStatement(true, 'a connection check entry');
    this.setPreviousStatement(true, ['four', 'connection', 'check', 'entries']);
  }
}

For inputs, you can pass the check to the setCheck method, after you have defined the input. If the setCheck method isn't called, the check is considered null. The value you pass can be a string, or an array of strings.

Blockly.Blocks['custom_block'] = {
  init: function() {
    this.appendValueInput('NAME')
        .setCheck('a connection check entry');
    this.appendStatementInput('NAME')
        .setCheck(['four', 'connection', 'check', 'entries']);
  }
}

Built-in check strings

The built-in blocks have connection checks with the values 'Array', 'Boolean', 'Colour', 'Number', and 'String'. If you want your blocks to interoperate with the built-in blocks, you can use these values to make them compatible.

Value examples

When you are defining connection checks for inputs and outputs, usually you should think of the checks as representing types.

Inputs' checks should include every "type" they accept, and outputs' checks should include exactly what they "return".

Accept a single type

In the most basic case where you want create a block that "accepts" or "returns" one type, you need to include that type in the connection's connection check.

a value block that accepts a single type

Accept multiple types

To create a block that "accepts" multiple types, you need to include every accepted type in the input's connection check.

a value block that accepts multiple types

By convention, if an output can sometimes be accepted in multiple situations (e.g. if you allow numbers to sometimes be used as strings) the output should be more restrictive, and the input(s) should be more permissive. This convention makes sure that outputs don't connect where they're not supported.

Accept any type

To create a block that "accepts" any type, you need to set the input's connection check to null.

a value block that accepts any type

Return subtypes

To create a block that "returns" a subtype, you need to include both the type and the supertype in the output's connection check.

a value block that returns its type and its supertype

In the case of subtypes, it is okay to have multiple checks in an output check, because the block always "returns" both types.

Return parameterized types

To create a block that "returns" a parameterized type, you need to include both the parameterized version and the unparameterized version in the output's connection check.

Depending on how strict you want your block language to be, you may also want to include the type's variance(s).

a value block that returns its parameterized type and its unparameterized
type

Just like with subtypes, it is okay to have multiple checks in an output check in this case, because the block always "returns" both types.

Stack or statement examples

There are a few common ways developers define checks for previous and next connections. Usually you think of these as restricting the ordering of blocks.

Next connections should include which blocks should follow the current one, and previous connections include what the current block "is".

Keep blocks in order

To create a set of blocks that connect in a defined order, you need to include which blocks should follow the current one in the next connection check, and what the current block "is" in the previous connection check.

statement blocks that have a forced order

Allow lots of middle blocks

To create a set of ordered blocks that allow lots of middle blocks, you need to include at least one entry from the middle block's previous connection check in the middle block's next connection check. This allows the block to be followed by more of itself.

statement blocks that allow lots of middle blocks

Allow no middle blocks

To create a set of ordered blocks where the middle blocks are optional, you need to include at least one entry from both the middle block's previous connection check, and the last block's previous connection check in the first block's next connection check. This allows the first block to be followed by either a middle block, or a last block.

statement blocks that allow no middle blocks

Either-or stacks

To create a block that can only be followed by blocks from one group, or blocks from another (and not both), you need to do two things:

  1. You need to include at least one entry from both of the groups previous connection checks in the first block's next connection check.

  2. You need to define the groups' next connection checks to only include values which are in their previous connection checks (so they can only be followed by blocks of the same group).

statement blocks that can be followed by multiple of one type of block, or
multiple of another, but not both

Limitations

This system is quite robust and can solve many use-cases, but it does have a few limitations.

Restrict the greater context

This system does not, by itself, support restricting the "greater context" in which a connection is allowed to connect. For example, you cannot say that a break block is only allowed to exist inside of a loop block. The connection checking system only considers the immediate two connections being connected.

You can support this by using the event system to listen to block move events and check if the block is incorrectly positioned.

Blockly.Blocks['custom_block'] = {
  init: function() { }

  onchange: function(e) {
    if (this.workspace.isDragging()) return;
    if (e.type !== Blockly.Events.BlockMove) return;
    if (!this.getSurroundLoop()) this.outputConnection.disconnect();
  }

  loopTypes: new Set(); // Your valid *block types* (not connection checks).

  getSurroundLoop: function () {
    let block = this.getSurroundParent();
    do {
      if (loopTypes.has(block.type)) return block;
      block = block.getSurroundParent();
    } while (block);
    return null;
  },
}

Generic types

This system does not, by itself, support defining generic types. For example, you cannot create an "Identity" block, that "returns" whatever its input is.

You can somewhat support this by actively changing the connection check on the block's output to match its input. Which you can do using the event system to listen to block move events.

Blockly.Blocks['custom_block'] = {
  init: function() { }

  onchange: function(e) {
    if (e.type !== Blockly.Events.BlockMove) return;
    this.setOutput(
        true, this.getInputTargetBlock()?.outputConnection.getCheck());
  }
}

But if the connected block is also generic, then this doesn't work correctly. There is no good work around for this case.

Connection checkers

If this system doesn't work for your use case, you can also change how the connection checks are compared by creating a custom connection checker.

For example, if you wanted to create a more advanced system that handles some of the limititations of this one, you can create a custom connection checker.