Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions frontend/blocks/mrc_class_method_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Order } from 'blockly/python';

import { MRC_STYLE_FUNCTIONS } from '../themes/styles';
import { createFieldNonEditableText } from '../fields/FieldNonEditableText'
import { createParameterFieldFlydown } from '../fields/field_flydown';
import { createParameterFieldFlydown, createReturnFieldFlydown } from '../fields/field_flydown';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import * as storageModuleContent from '../storage/module_content';
import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_function'
Expand Down Expand Up @@ -38,7 +38,7 @@ const UNTYPED_RETURN_VALUE = '';
const INPUT_TITLE = 'TITLE';
const FIELD_METHOD_NAME = 'NAME';
const FIELD_PARAM_PREFIX = 'PARAM_';
const INPUT_STACK = 'STACK';
export const INPUT_STACK = 'STACK';
export const INPUT_RETURN = 'RETURN';

export interface Parameter {
Expand Down Expand Up @@ -266,7 +266,7 @@ const CLASS_METHOD_DEF = {
if (this.mrcReturnType !== undefined && this.mrcReturnType !== NO_RETURN_VALUE) {
this.appendValueInput(INPUT_RETURN)
.setAlign(Blockly.inputs.Align.RIGHT)
.appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
.appendField(createReturnFieldFlydown());
// Move the return input to be after the statement input
this.moveInputBefore(INPUT_STACK, INPUT_RETURN);
}
Expand Down
109 changes: 109 additions & 0 deletions frontend/blocks/mrc_return.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) FIRST and other WPILib contributors.

/**
* @fileoverview This is a block that returns a value from a method.
* @author alan@porpoiseful.com (Alan Smith)
*/
import * as Blockly from 'blockly';
import { Order } from 'blockly/python';

import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import { MRC_STYLE_FUNCTIONS } from '../themes/styles';
import { BLOCK_NAME as MRC_CLASS_METHOD_DEF, INPUT_RETURN as METHOD_INPUT_RETURN, INPUT_STACK as METHOD_INPUT_STACK } from './mrc_class_method_def';
import { findConnectedBlocksOfType } from './utils/find_connected_blocks';
import { getValueType } from './utils/variable_types';

export const BLOCK_NAME = 'mrc_return';

const INPUT_VALUE = 'VALUE';

const WARNING_ID_NOT_IN_METHOD = 'MRC_RETURN_NOT_IN_METHOD';
const WARNING_ID_DIFFERENT_TYPES = 'MRC_RETURN_DIFFERENT_TYPES';

const RETURN_BLOCK = {
/**
* Block initialization.
*/
init: function (this: Blockly.Block): void {
this.setStyle(MRC_STYLE_FUNCTIONS);
this.appendValueInput(INPUT_VALUE)
.appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
this.setPreviousStatement(true, null);
this.setNextStatement(false);
},
};

export const setup = function () {
Blockly.Blocks[BLOCK_NAME] = RETURN_BLOCK;
};

export const pythonFromBlock = function (
block: Blockly.Block,
generator: ExtendedPythonGenerator,
) {
const value = generator.valueToCode(block, INPUT_VALUE, Order.NONE) || 'None';
return 'return ' + value + '\n';
};

/**
* Returns true if the given block is a method definition that returns a value.
*/
function isMethodWithReturn(block: Blockly.Block): boolean {
return block.type === MRC_CLASS_METHOD_DEF && block.getInput(METHOD_INPUT_RETURN) !== null;
}

/**
* Puts a warning on each return block that isn't in a method that returns a value. For each
* method, if the values returned (by its return blocks and by the return input of the method
* block) have different types, puts a warning on each of the blocks that returns a value with a
* type. Values that don't have a type are ignored, since they might be of any type.
*/
export function updateReturnWarnings(workspace: Blockly.Workspace): void {
if (workspace.isFlyout) {
return;
}
const differentTypesWarnings = new Map<Blockly.Block, string>();
const methodBlocks = workspace.getBlocksByType(MRC_CLASS_METHOD_DEF, false);
for (const methodBlock of methodBlocks) {
// Each block that returns a value, with the input that holds the value.
const returningBlocks: [Blockly.Block, string][] = [];
if (isMethodWithReturn(methodBlock)) {
returningBlocks.push([methodBlock, METHOD_INPUT_RETURN]);
}
const stackBlock = methodBlock.getInputTargetBlock(METHOD_INPUT_STACK);
if (stackBlock) {
findConnectedBlocksOfType(stackBlock, BLOCK_NAME).forEach(returnBlock => {
returningBlocks.push([returnBlock, INPUT_VALUE]);
});
}

const typedBlocks: Blockly.Block[] = [];
const types = new Set<string>();
returningBlocks.forEach(([block, inputName]) => {
const valueBlock = block.getInputTargetBlock(inputName);
const type = valueBlock ? getValueType(valueBlock) : '';
if (type) {
typedBlocks.push(block);
types.add(type);
}
});
if (types.size > 1) {
const warning = Blockly.Msg.WARNING_RETURN_DIFFERENT_TYPES
.replace('{{types}}', [...types].join(', '));
typedBlocks.forEach(block => differentTypesWarnings.set(block, warning));
}
}

for (const methodBlock of methodBlocks) {
methodBlock.setWarningText(
differentTypesWarnings.get(methodBlock) ?? null, WARNING_ID_DIFFERENT_TYPES);
}
for (const block of workspace.getBlocksByType(BLOCK_NAME, false)) {
block.setWarningText(
isMethodWithReturn(block.getRootBlock())
? null : Blockly.Msg.RETURN_CAN_ONLY_GO_IN_METHOD_WITH_RETURN,
WARNING_ID_NOT_IN_METHOD);
block.setWarningText(
differentTypesWarnings.get(block) ?? null, WARNING_ID_DIFFERENT_TYPES);
}
}
2 changes: 2 additions & 0 deletions frontend/blocks/setup_custom_blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import * as OpModeDetails from './mrc_opmode_details';
import * as OutputContainer from './mrc_output_container';
import * as ParamContainer from './mrc_param_container'
import * as Port from './mrc_port';
import * as Return from './mrc_return';
import * as SetPythonVariable from './mrc_set_python_variable';
import * as Steps from './mrc_steps';
import * as When from './mrc_when';
Expand Down Expand Up @@ -54,6 +55,7 @@ const customBlocks = [
OutputContainer,
ParamContainer,
Port,
Return,
SetPythonVariable,
Steps,
When,
Expand Down
3 changes: 3 additions & 0 deletions frontend/blocks/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
t('BLOCKLY.PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER'),
NO_PARAMETER_SELECTED:
t('BLOCKLY.NO_PARAMETER_SELECTED'),
RETURN_CAN_ONLY_GO_IN_METHOD_WITH_RETURN:
t('BLOCKLY.RETURN_CAN_ONLY_GO_IN_METHOD_WITH_RETURN'),
CANNOT_MOVE_BLOCKS_TO_OTHER_TAB:
t('BLOCKLY.CANNOT_MOVE_BLOCKS_TO_OTHER_TAB'),
CANNOT_MOVE_METHOD_OVERRIDE_TO_OTHER_TAB:
Expand Down Expand Up @@ -196,6 +198,7 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
WARNING_CALL_INSTANCE_METHOD_MISSING_CLASS: t('BLOCKLY.WARNING.CALL_INSTANCE_METHOD_MISSING_CLASS'),
WARNING_CALL_INSTANCE_VARIABLE_WRONG_TYPE: t('BLOCKLY.WARNING.CALL_INSTANCE_VARIABLE_WRONG_TYPE'),
WARNING_SET_VARIABLE_DIFFERENT_TYPE: t('BLOCKLY.WARNING.SET_VARIABLE_DIFFERENT_TYPE'),
WARNING_RETURN_DIFFERENT_TYPES: t('BLOCKLY.WARNING.RETURN_DIFFERENT_TYPES'),
WARNING_CALL_INSTANCE_METHOD_MISSING_METHOD: t('BLOCKLY.WARNING.CALL_INSTANCE_METHOD_MISSING_METHOD'),
WARNING_CALL_INSTANCE_WITHIN_METHOD_MISSING_METHOD: t('BLOCKLY.WARNING.CALL_INSTANCE_WITHIN_METHOD_MISSING_METHOD'),
WARNING_CALL_COMPONENT_INSTANCE_METHOD_PRIVATE_COMPONENT: t('BLOCKLY.WARNING.CALL_COMPONENT_INSTANCE_METHOD_PRIVATE_COMPONENT'),
Expand Down
2 changes: 1 addition & 1 deletion frontend/blocks/utils/variable_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export function getDifferentTypeWarning(block: Blockly.Block): string | null {
}

/** Returns the type of the value of the given block, or '' if it doesn't have a type. */
function getValueType(block: Blockly.Block): string {
export function getValueType(block: Blockly.Block): string {
if (block.type === BLOCK_TYPE_VARIABLES_GET) {
const field = block.getField(FIELD_VAR) as Blockly.FieldVariable | null;
return field?.getVariable()?.getType() ?? '';
Expand Down
4 changes: 4 additions & 0 deletions frontend/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as blockSteps from '../blocks/mrc_steps';
import * as mechanismComponentHolder from '../blocks/mrc_mechanism_component_holder';
import * as portConflicts from '../blocks/utils/port_conflicts';
import { canChangeVariableTypes, updateVariableTypes } from '../blocks/utils/variable_types';
import { updateReturnWarnings } from '../blocks/mrc_return';
import * as workspaces from '../blocks/utils/workspaces';
//import { testAllBlocksInToolbox } from '../toolbox/toolbox_tests';
import { applyExpandedCategories, getToolboxJSON } from '../toolbox/toolbox';
Expand Down Expand Up @@ -134,6 +135,7 @@ export class Editor {

// Variables in projects saved before variables had types get their types now.
updateVariableTypes(this.blocklyWorkspace);
updateReturnWarnings(this.blocklyWorkspace);
}

private onChangeAfterLoading(event: Blockly.Events.Abstract) {
Expand All @@ -159,6 +161,8 @@ export class Editor {
// Variables get their types from the values assigned to them.
if (canChangeVariableTypes(event)) {
updateVariableTypes(this.blocklyWorkspace);
// Return values can have different types after blocks move or variable types change.
updateReturnWarnings(this.blocklyWorkspace);
}

if (event instanceof Blockly.Events.VarTypeChange) {
Expand Down
18 changes: 17 additions & 1 deletion frontend/fields/field_flydown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import * as Blockly from 'blockly';
import {BLOCK_NAME as JUMP_TO_STEP} from '../blocks/mrc_jump_to_step';
import {BLOCK_NAME as GET_PARAMETER} from '../blocks/mrc_get_parameter';
import {BLOCK_NAME as RETURN} from '../blocks/mrc_return';
import {matchBorderToText} from './FieldNonEditableText';

const NON_EDITABLE_CSS_CLASS_NAME = 'mrcNonEditableFlydownField';
Expand Down Expand Up @@ -406,6 +407,17 @@ function createParameterBlock(paramName: string): Blockly.utils.toolbox.FlyoutDe
};
}

function createReturnBlock(_text: string): Blockly.utils.toolbox.FlyoutDefinition {
return {
contents: [
{
kind: 'block',
type: RETURN,
},
]
};
}

function createJumpToStepBlock(stepName: string): Blockly.utils.toolbox.FlyoutDefinition {
return {
contents: [
Expand Down Expand Up @@ -434,4 +446,8 @@ export function createParameterFieldFlydown(paramName: string, isEditable: boole

export function createStepFieldFlydown(stepName: string, isEditable: boolean): Blockly.Field {
return new FieldFlydown(stepName, isEditable, createJumpToStepBlock);
}
}

export function createReturnFieldFlydown(): Blockly.Field {
return new FieldFlydown(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN, false, createReturnBlock);
}
2 changes: 2 additions & 0 deletions frontend/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@
"PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER": "Parameter \"%1\" does not exist in this event handler.",
"NO_PARAMETER_SELECTED": "No parameter selected.",
"NO_PARAMETERS": "(no parameters)",
"RETURN_CAN_ONLY_GO_IN_METHOD_WITH_RETURN": "Return can only go in a method that returns a value",
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "Jump can only go in their step's block",
"STEP_DOES_NOT_EXIST_IN_STEPS": "Step \"%1\" does not exist in this steps block.",
"NO_STEP_SELECTED": "No step selected.",
Expand Down Expand Up @@ -577,6 +578,7 @@
"CALL_INSTANCE_METHOD_MISSING_CLASS": "This block calls a method in a class that no longer exists.",
"CALL_INSTANCE_VARIABLE_WRONG_TYPE": "The variable {{variableName}} doesn't always hold a {{className}}, so this block might not work.",
"SET_VARIABLE_DIFFERENT_TYPE": "This sets {{variableName}} to a value of type {{valueType}}, but elsewhere it is set to a value of type {{variableType}}.",
"RETURN_DIFFERENT_TYPES": "The values returned by this method have different types: {{types}}.",
"CALL_INSTANCE_METHOD_MISSING_METHOD": "This block calls a method that no longer exists.",
"CALL_INSTANCE_WITHIN_METHOD_MISSING_METHOD": "This block calls a method that does not exist in this module.",
"CALL_COMPONENT_INSTANCE_METHOD_PRIVATE_COMPONENT": "This blocks calls a method on a private component in the {{mechanismClassName}} mechanism.",
Expand Down
2 changes: 2 additions & 0 deletions frontend/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@
"PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER": "El parámetro \"%1\" no existe en este controlador de eventos.",
"NO_PARAMETER_SELECTED": "No se ha seleccionado ningún parámetro.",
"NO_PARAMETERS": "(sin parámetros)",
"RETURN_CAN_ONLY_GO_IN_METHOD_WITH_RETURN": "Devolver solo puede ir en un método que devuelve un valor",
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "El salto solo puede ir en el bloque de su paso",
"STEP_DOES_NOT_EXIST_IN_STEPS": "El paso \"%1\" no existe en este bloque de pasos.",
"NO_STEP_SELECTED": "No se ha seleccionado ningún paso.",
Expand Down Expand Up @@ -576,6 +577,7 @@
"CALL_INSTANCE_METHOD_MISSING_CLASS": "Este bloque llama a un método de una clase que ya no existe.",
"CALL_INSTANCE_VARIABLE_WRONG_TYPE": "La variable {{variableName}} no siempre contiene un {{className}}, por lo que este bloque podría no funcionar.",
"SET_VARIABLE_DIFFERENT_TYPE": "Esto asigna a {{variableName}} un valor de tipo {{valueType}}, pero en otro lugar se le asigna un valor de tipo {{variableType}}.",
"RETURN_DIFFERENT_TYPES": "Los valores devueltos por este método tienen tipos diferentes: {{types}}.",
"CALL_INSTANCE_METHOD_MISSING_METHOD": "Este bloque llama a un método que ya no existe.",
"CALL_INSTANCE_WITHIN_METHOD_MISSING_METHOD": "Este bloque llama a un método que no existe en este módulo.",
"CALL_COMPONENT_INSTANCE_METHOD_PRIVATE_COMPONENT": "Este bloque llama a un método en un componente privado en el mecanismo {{mechanismClassName}}.",
Expand Down
2 changes: 2 additions & 0 deletions frontend/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@
"PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER": "Le paramètre \"%1\" n'existe pas dans ce gestionnaire d'événements.",
"NO_PARAMETER_SELECTED": "Aucun paramètre sélectionné.",
"NO_PARAMETERS": "(aucun paramètre)",
"RETURN_CAN_ONLY_GO_IN_METHOD_WITH_RETURN": "Retourner ne peut aller que dans une méthode qui retourne une valeur",
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "Le saut ne peut aller que dans le bloc d'étapes correspondant",
"STEP_DOES_NOT_EXIST_IN_STEPS": "L'étape \"%1\" n'existe pas dans ce bloc d'étapes.",
"NO_STEP_SELECTED": "Aucune étape sélectionnée.",
Expand Down Expand Up @@ -578,6 +579,7 @@
"CALL_INSTANCE_METHOD_MISSING_CLASS": "Ce bloc appelle une méthode dans une classe qui n'existe plus.",
"CALL_INSTANCE_VARIABLE_WRONG_TYPE": "La variable {{variableName}} ne contient pas toujours un {{className}}, donc ce bloc risque de ne pas fonctionner.",
"SET_VARIABLE_DIFFERENT_TYPE": "Ceci donne à {{variableName}} une valeur de type {{valueType}}, mais ailleurs elle reçoit une valeur de type {{variableType}}.",
"RETURN_DIFFERENT_TYPES": "Les valeurs retournées par cette méthode ont des types différents : {{types}}.",
"CALL_INSTANCE_METHOD_MISSING_METHOD": "Ce bloc appelle une méthode qui n'existe plus.",
"CALL_INSTANCE_WITHIN_METHOD_MISSING_METHOD": "Ce bloc appelle une méthode qui n'existe pas dans ce module.",
"CALL_COMPONENT_INSTANCE_METHOD_PRIVATE_COMPONENT": "Ce bloc appelle une méthode sur un composant privé dans le mécanisme {{mechanismClassName}}.",
Expand Down
2 changes: 2 additions & 0 deletions frontend/i18n/locales/he.json
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@
"PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER": "הפרמטר \"%1\" לא קיים במטפל אירועים זה.",
"NO_PARAMETER_SELECTED": "לא נבחר פרמטר.",
"NO_PARAMETERS": "(אין פרמטרים)",
"RETURN_CAN_ONLY_GO_IN_METHOD_WITH_RETURN": "החזרה יכולה להופיע רק בשיטה שמחזירה ערך",
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "קפיצה יכולה ללכת רק בבלוק הצעד שלה",
"STEP_DOES_NOT_EXIST_IN_STEPS": "הצעד \"%1\" לא קיים בבלוק הצעדים הזה.",
"NO_STEP_SELECTED": "לא נבחר צעד.",
Expand Down Expand Up @@ -575,6 +576,7 @@
"CALL_INSTANCE_METHOD_MISSING_CLASS": "בלוק זה קורא למתודה במחלקה שכבר לא קיימת.",
"CALL_INSTANCE_VARIABLE_WRONG_TYPE": "המשתנה {{variableName}} לא תמיד מכיל {{className}}, ולכן ייתכן שבלוק זה לא יעבוד.",
"SET_VARIABLE_DIFFERENT_TYPE": "בלוק זה מציב ב-{{variableName}} ערך מסוג {{valueType}}, אך במקום אחר מוצב בו ערך מסוג {{variableType}}.",
"RETURN_DIFFERENT_TYPES": "לערכים שמוחזרים על ידי שיטה זו יש סוגים שונים: {{types}}.",
"CALL_INSTANCE_METHOD_MISSING_METHOD": "בלוק זה קורא למתודה שכבר לא קיימת.",
"CALL_INSTANCE_WITHIN_METHOD_MISSING_METHOD": "בלוק זה קורא למתודה שאינה קיימת במודול זה.",
"CALL_COMPONENT_INSTANCE_METHOD_PRIVATE_COMPONENT": "בלוק זה קורא למתודה על רכיב פרטי במנגנון {{mechanismClassName}}.",
Expand Down
Loading