More work on connection type checks

This commit is contained in:
Rachel Fenichel
2020-07-14 13:49:58 -06:00
parent bb8348befd
commit 450aed0aa2
6 changed files with 163 additions and 139 deletions

View File

@@ -456,7 +456,8 @@ Blockly.Block.prototype.unplugFromRow_ = function(opt_healStack) {
// Disconnect the child block.
childConnection.disconnect();
// Connect child to the parent if possible, otherwise bump away.
if (childConnection.checkType(parentConnection)) {
if (this.workspace.connectionTypeChecker.canConnect(
childConnection, parentConnection, false, false)) {
parentConnection.connect(childConnection);
} else {
childConnection.onFailedConnect(parentConnection);
@@ -508,7 +509,9 @@ Blockly.Block.prototype.unplugFromStack_ = function(opt_healStack) {
// Disconnect the next statement.
var nextTarget = this.nextConnection.targetConnection;
nextTarget.disconnect();
if (previousTarget && previousTarget.checkType(nextTarget)) {
if (previousTarget &&
this.workspace.connectionTypeChecker.canConnect(
previousTarget, nextTarget, false, false)) {
// Attach the next statement to the previous statement.
previousTarget.connect(nextTarget);
}

View File

@@ -147,10 +147,10 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
if (nextBlock && !nextBlock.isShadow()) {
newBlock = nextBlock;
} else {
if (orphanBlock.workspace.connectionTypeChecker.checkType(
orphanBlock.previousConnection, newBlock.nextConnection)) {
//orphanBlock.previousConnection.checkType(
//newBlock.nextConnection)) {
var typeChecker = orphanBlock.workspace.connectionTypeChecker;
if (typeChecker.canConnect(
orphanBlock.previousConnection, newBlock.nextConnection,
false, false)) {
newBlock.nextConnection.connect(orphanBlock.previousConnection);
orphanBlock = null;
}
@@ -249,6 +249,7 @@ Blockly.Connection.prototype.isConnected = function() {
* @param {Blockly.Connection} target Connection to check compatibility with.
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
* an error code otherwise.
* @deprecated July 2020
*/
Blockly.Connection.prototype.canConnectWithReason = function(target) {
// TODO: deprecation warning with date, plus tests.
@@ -261,6 +262,7 @@ Blockly.Connection.prototype.canConnectWithReason = function(target) {
* @param {Blockly.Connection} target The connection to check compatibility
* with.
* @package
* @deprecated July 2020
*/
Blockly.Connection.prototype.checkConnection = function(target) {
// TODO: Add deprecation warning notices *and* add tests to make sure these
@@ -269,50 +271,21 @@ Blockly.Connection.prototype.checkConnection = function(target) {
checker.canConnect(this, target, false, true);
};
/**
* Get the workspace's connection type checker object.
* @return {!Blockly.ConnectionTypeChecker} The connection type checker for the
* source block's workspace.
* @package
*/
Blockly.Connection.prototype.getConnectionTypeChecker = function() {
return this.sourceBlock_.workspace.connectionTypeChecker;
};
// /**
// * Check if the two connections can be dragged to connect to each other.
// * This is used by the connection database when searching for the closest
// * connection.
// * @param {!Blockly.Connection} candidate A nearby connection to check, which
// * must be a previous connection.
// * @return {boolean} True if the connection is allowed, false otherwise.
// * @private
// */
// Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) {
// if (this.targetConnection) {
// // This connection is already occupied.
// // A next connection will never disconnect itself mid-drag.
// return false;
// }
// // Don't let blocks try to connect to themselves or ones they nest.
// if (Blockly.draggingConnections.indexOf(candidate) != -1) {
// return false;
// }
// if (!candidate.targetConnection) {
// return true;
// }
// var targetBlock = candidate.targetBlock();
// // If it is connected to a real block, game over.
// if (!targetBlock.isInsertionMarker()) {
// return false;
// }
// // If it's connected to an insertion marker but that insertion marker
// // is the first block in a stack, it's still fine. If that insertion
// // marker is in the middle of a stack, it won't work.
// return !targetBlock.getPreviousBlock();
// };
/**
* Check if the two connections can be dragged to connect to each other.
* @param {!Blockly.Connection} candidate A nearby connection to check.
* @return {boolean} True if the connection is allowed, false otherwise.
* @deprecated July 2020
*/
Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
return this.getConnectionTypeChecker().canConnect(this, candidate, true, false);
@@ -339,22 +312,22 @@ Blockly.Connection.prototype.connect = function(otherConnection) {
}
var checker = this.getConnectionTypeChecker();
checker.canConnect(this, otherConnection, false, true);
var eventGroup = Blockly.Events.getGroup();
if (!eventGroup) {
Blockly.Events.setGroup(true);
}
// Determine which block is superior (higher in the source stack).
if (this.isSuperior()) {
// Superior block.
this.connect_(otherConnection);
} else {
// Inferior block.
otherConnection.connect_(this);
}
if (!eventGroup) {
Blockly.Events.setGroup(false);
if (checker.canConnect(this, otherConnection, false, true)) {
var eventGroup = Blockly.Events.getGroup();
if (!eventGroup) {
Blockly.Events.setGroup(true);
}
// Determine which block is superior (higher in the source stack).
if (this.isSuperior()) {
// Superior block.
this.connect_(otherConnection);
} else {
// Inferior block.
otherConnection.connect_(this);
}
if (!eventGroup) {
Blockly.Events.setGroup(false);
}
}
};
@@ -383,10 +356,12 @@ Blockly.Connection.connectReciprocally_ = function(first, second) {
*/
Blockly.Connection.singleConnection_ = function(block, orphanBlock) {
var connection = null;
var output = orphanBlock.outputConnection;
for (var i = 0; i < block.inputList.length; i++) {
var thisConnection = block.inputList[i].connection;
var typeChecker = output.getConnectionTypeChecker();
if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE &&
orphanBlock.outputConnection.checkType(thisConnection)) {
typeChecker.canConnect(output, thisConnection, false, false)) {
if (connection) {
return null; // More than one connection.
}
@@ -516,7 +491,7 @@ Blockly.Connection.prototype.targetBlock = function() {
* @return {boolean} True if the connections share a type.
*/
Blockly.Connection.prototype.checkType = function(otherConnection) {
return this.getConnectionTypeChecker().checkType(this, otherConnection);
return this.getConnectionTypeChecker().canConnect(this, otherConnection, false, false);
};
/**
@@ -541,7 +516,8 @@ Blockly.Connection.prototype.checkType_ = function(otherConnection) {
Blockly.Connection.prototype.onCheckChanged_ = function() {
// The new value type may not be compatible with the existing connection.
if (this.isConnected() && (!this.targetConnection ||
!this.checkType(this.targetConnection))) {
!this.getConnectionTypeChecker().canConnect(
this, this.targetConnection, false, false))) {
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
child.unplug();
}

View File

@@ -11,6 +11,94 @@ goog.requireType('Blockly.Connection');
Blockly.ConnectionTypeChecker = function() {
};
/**
* Check whether the current connection can connect with the target
* connection.
* @param {Blockly.Connection} one Connection to check compatibility with.
* @param {Blockly.Connection} two Connection to check compatibility with.
* @param {boolean} isDragging True if the connection is being made by dragging
* a block.
* @param {boolean} shouldThrow Whether to throw an error when a connection is
* invalid.
* @return {boolean} Whether the connection is legal.
*/
Blockly.ConnectionTypeChecker.prototype.canConnect = function(one, two,
isDragging, shouldThrow) {
if (this.passesSafetyChecks(one, two, shouldThrow)) {
if (this.passesTypeChecks(one, two, shouldThrow)) {
if (!isDragging || this.passesDragChecks(one, two, shouldThrow)) {
return true;
}
}
}
return false;
};
/**
* Check that connecting the given connections is safe, meaning that it would
* not break any of Blockly's basic assumptions--no self connections, etc.
* @param {!Blockly.Connection} one The first of the connections to check.
* @param {!Blockly.Connection} two The second of the connections to check.
* @param {boolean} shouldThrow Whether to throw an error if the connection is
* unsafe.
* @return {boolean} Whether the connection is safe.
* @package
*/
Blockly.ConnectionTypeChecker.prototype.passesSafetyChecks = function(one, two, shouldThrow) {
var safety = this.doSafetyChecks_(one, two);
if (safety == Blockly.Connection.CAN_CONNECT) {
return true;
}
if (shouldThrow) {
throw Error(this.getErrorMessage_(safety, one, two));
}
return false;
};
Blockly.ConnectionTypeChecker.prototype.passesTypeChecks = function(one, two,
shouldThrow) {
if (this.doTypeChecks_(one, two)) {
return true;
}
if (shouldThrow) {
throw Error(this.getErrorMessage_(
Blockly.Connection.REASON_CHECKS_FAILED, one, two));
}
return false;
};
Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two,
shouldThrow) {
if (this.doDragChecks_(one, two)) {
return true;
}
if (shouldThrow) {
throw Error(this.getErrorMessage_(
Blockly.Connection.REASON_DRAG_CHECKS_FAILED, one, two));
}
return false;
};
/**
* Checks whether the current connection can connect with the target
* connection.
* @param {Blockly.Connection} one Connection to check compatibility with.
* @param {Blockly.Connection} two Connection to check compatibility with.
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
* an error code otherwise.
*/
Blockly.ConnectionTypeChecker.prototype.canConnectWithReason = function(one, two) {
var safety = this.doSafetyChecks_(one, two);
if (safety != Blockly.Connection.CAN_CONNECT) {
return safety;
}
if (!this.doTypeChecks_(one, two)) {
return Blockly.Connection.REASON_CHECKS_FAILED;
}
return Blockly.Connection.CAN_CONNECT;
};
/**
* Helper method that translates a connection error code into a string.
* @param {number} errorCode The error code.
@@ -18,9 +106,9 @@ Blockly.ConnectionTypeChecker = function() {
* @param {!Blockly.Connection} two The second of the two connections being
* checked.
* @return {string} A developer-readable error string.
* @package
* @private
*/
Blockly.ConnectionTypeChecker.prototype.getErrorMessage = function(errorCode,
Blockly.ConnectionTypeChecker.prototype.getErrorMessage_ = function(errorCode,
one, two) {
switch (errorCode) {
case Blockly.Connection.REASON_SELF_CONNECTION:
@@ -39,32 +127,21 @@ Blockly.ConnectionTypeChecker.prototype.getErrorMessage = function(errorCode,
case Blockly.Connection.REASON_SHADOW_PARENT:
return 'Connecting non-shadow to shadow block.';
case Blockly.Connection.REASON_DRAG_CHECKS_FAILED:
return 'Drag checks failed.'
return 'Drag checks failed.';
default:
return 'Unknown connection failure: this should never happen!';
}
};
/**
* Checks whether the current connection can connect with the target
* connection.
* @param {Blockly.Connection} one Connection to check compatibility with.
* @param {Blockly.Connection} two Connection to check compatibility with.
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
* an error code otherwise.
* Check that connecting the given connections is safe, meaning that it would
* not break any of Blockly's basic assumptions--no self connections, etc.
* @param {!Blockly.Connection} one The first of the connections to check.
* @param {!Blockly.Connection} two The second of the connections to check.
* @return {boolean} True if making this connection is safe.
* @private
*/
Blockly.ConnectionTypeChecker.prototype.canConnectWithReason = function(one, two) {
var validity = this.doValidityChecks(one, two);
if (validity != Blockly.Connection.CAN_CONNECT) {
return validity;
}
if (!this.checkType(one, two)) {
return Blockly.Connection.REASON_CHECKS_FAILED;
}
return Blockly.Connection.CAN_CONNECT;
};
Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) {
Blockly.ConnectionTypeChecker.prototype.doSafetyChecks_ = function(one, two) {
if (!one || !two) {
return Blockly.Connection.REASON_TARGET_NULL;
}
@@ -75,8 +152,6 @@ Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) {
var blockB = one.getSourceBlock();
var blockA = two.getSourceBlock();
}
// TODO (fenichel): The null checks seem like they're only for making tests
// work better.
if (blockA == blockB) {
return Blockly.Connection.REASON_SELF_CONNECTION;
} else if (two.type != Blockly.OPPOSITE_TYPE[one.type]) {
@@ -90,53 +165,15 @@ Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) {
};
/**
* Checks whether the current connection can connect with the target
* connection.
* @param {Blockly.Connection} one Connection to check compatibility with.
* @param {Blockly.Connection} two Connection to check compatibility with.
* @return {boolean} Whether the connection is legal.
*/
Blockly.ConnectionTypeChecker.prototype.canConnect = function(one, two,
isDragging, shouldThrow) {
var validity = this.doValidityChecks(one, two);
if (validity != Blockly.Connection.CAN_CONNECT) {
if (shouldThrow) {
throw Error(this.getErrorMessage(validity, one, two));
}
return false;
}
var passesTypeChecks = this.checkType(one, two);
if (!passesTypeChecks) {
if (shouldThrow) {
throw Error(this.getErrorMessage(
Blockly.Connection.REASON_CHECKS_FAILED, one, two));
}
return false;
}
if (isDragging) {
var passesDragChecks = this.passesDragChecks(one, two);
if (!passesDragChecks) {
if (shouldThrow) {
throw Error(this.getErrorMessage(
Blockly.Connection.REASON_DRAG_CHECKS_FAILED, one, two));
}
return false;
}
}
return true;
};
/**
* Is this connection compatible with another connection with respect to the
* value type system. E.g. square_root("Hello") is not compatible.
* Check whether this connection is compatible with another connection with
* respect to the value type system. E.g. square_root("Hello") is not
* compatible.
* @param {!Blockly.Connection} one Connection to compare.
* @param {!Blockly.Connection} two Connection to compare against.
* @return {boolean} True if the connections share a type.
* @protected
*/
Blockly.ConnectionTypeChecker.prototype.checkType = function(one, two) {
Blockly.ConnectionTypeChecker.prototype.doTypeChecks_ = function(one, two) {
var checkArrayOne = one.getCheck();
var checkArrayTwo = two.getCheck();
@@ -154,7 +191,14 @@ Blockly.ConnectionTypeChecker.prototype.checkType = function(one, two) {
return false;
};
Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) {
/**
* Check whether this connection can be made by dragging.
* @param {!Blockly.Connection} one Connection to compare.
* @param {!Blockly.Connection} two Connection to compare against.
* @return {boolean} True if the connections share a type.
* @protected
*/
Blockly.ConnectionTypeChecker.prototype.doDragChecks_ = function(one, two) {
// Don't consider insertion markers.
if (two.sourceBlock_.isInsertionMarker()) {
return false;
@@ -198,7 +242,8 @@ Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) {
break;
}
default:
throw Error('Unknown connection type in passesDragChecks');
// Unexpected connection type.
return false;
}
// Don't let blocks try to connect to themselves or ones they nest.
@@ -210,15 +255,13 @@ Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) {
};
/**
* Check if the two connections can be dragged to connect to each other.
* This is used by the connection database when searching for the closest
* connection.
* Helper function for drag checking
* @param {!Blockly.Connection} one The connection to check, which must be a
* statement input or next connection.
* @param {!Blockly.Connection} two A nearby connection to check, which
* must be a previous connection.
* @return {boolean} True if the connection is allowed, false otherwise.
* @private
* @protected
*/
Blockly.ConnectionTypeChecker.prototype.canConnectToPrevious_ = function(one, two) {
if (one.targetConnection) {

View File

@@ -422,6 +422,7 @@ Blockly.RenderedConnection.prototype.startTrackingAll = function() {
* @param {number=} maxRadius The maximum radius allowed for connections, in
* workspace units.
* @return {boolean} True if the connection is allowed, false otherwise.
* @deprecated July 2020
*/
Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate,
maxRadius) {
@@ -551,7 +552,8 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) {
Blockly.RenderedConnection.prototype.onCheckChanged_ = function() {
// The new value type may not be compatible with the existing connection.
if (this.isConnected() && (!this.targetConnection ||
!this.checkType(this.targetConnection))) {
!this.getConnectionTypeChecker().canConnect(
this, this.targetConnection, false, false))) {
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
child.unplug();
// Bump away.

View File

@@ -254,7 +254,8 @@ Blockly.blockRendering.Renderer.prototype.orphanCanConnectAtEnd =
if (!lastConnection) {
return false;
}
return orphanConnection.checkType(lastConnection);
return orphanConnection.getConnectionTypeChecker().canConnect(
lastConnection, orphanConnection, false, false);
};
/**

View File

@@ -229,13 +229,12 @@ suite('Connection type checker', function() {
this.con2 = new Blockly.Connection({}, Blockly.NEXT_STATEMENT);
});
function assertCheckTypes(checker, one, two) {
chai.assert.isTrue(checker.checkType(one, two));
chai.assert.isTrue(checker.passesTypeChecks(one, two));
// Order should not matter.
chai.assert.isTrue(checker.checkType(one, two));
chai.assert.isTrue(checker.passesTypeChecks(one, two));
}
test('No Types', function() {
assertCheckTypes(this.checker, this.con1, this.con2);
chai.assert.isTrue(this.checker.checkType(this.con1, this.con2));
});
test('Same Type', function() {
this.con1.setCheck('type1');
@@ -259,7 +258,7 @@ suite('Connection type checker', function() {
test('No Compatible Types', function() {
this.con1.setCheck('type1');
this.con2.setCheck('type2');
chai.assert.isFalse(this.checker.checkType(this.con1, this.con2));
chai.assert.isFalse(this.checker.passesTypeChecks(this.con1, this.con2));
});
});
});