1# Copyright 2019 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Routines to check miscellaneous constraints on the IR.""" 16 17from compiler.front_end import attributes 18from compiler.util import error 19from compiler.util import ir_data 20from compiler.util import ir_data_utils 21from compiler.util import ir_util 22from compiler.util import resources 23from compiler.util import traverse_ir 24 25 26def _render_type(type_ir, ir): 27 """Returns the human-readable notation of the given type.""" 28 assert type_ir.HasField("atomic_type"), ( 29 "TODO(bolms): Implement _render_type for array types.") 30 if type_ir.HasField("size_in_bits"): 31 return _render_atomic_type_name( 32 type_ir, 33 ir, 34 suffix=":" + str(ir_util.constant_value(type_ir.size_in_bits))) 35 else: 36 return _render_atomic_type_name(type_ir, ir) 37 38 39def _render_atomic_type_name(type_ir, ir, suffix=None): 40 assert type_ir.HasField("atomic_type"), ( 41 "_render_atomic_type_name() requires an atomic type") 42 if not suffix: 43 suffix = "" 44 type_definition = ir_util.find_object(type_ir.atomic_type.reference, ir) 45 if type_definition.name.is_anonymous: 46 return "anonymous type" 47 else: 48 return "type '{}{}'".format(type_definition.name.name.text, suffix) 49 50 51def _check_that_inner_array_dimensions_are_constant( 52 type_ir, source_file_name, errors): 53 """Checks that inner array dimensions are constant.""" 54 if type_ir.WhichOneof("size") == "automatic": 55 errors.append([error.error( 56 source_file_name, 57 ir_data_utils.reader(type_ir).element_count.source_location, 58 "Array dimensions can only be omitted for the outermost dimension.")]) 59 elif type_ir.WhichOneof("size") == "element_count": 60 if not ir_util.is_constant(type_ir.element_count): 61 errors.append([error.error(source_file_name, 62 type_ir.element_count.source_location, 63 "Inner array dimensions must be constant.")]) 64 else: 65 assert False, 'Expected "element_count" or "automatic" array size.' 66 67 68def _check_that_array_base_types_are_fixed_size(type_ir, source_file_name, 69 errors, ir): 70 """Checks that the sizes of array elements are known at compile time.""" 71 if type_ir.base_type.HasField("array_type"): 72 # An array is fixed size if its base_type is fixed size and its array 73 # dimension is constant. This function will be called again on the inner 74 # array, and we do not want to cascade errors if the inner array's base_type 75 # is not fixed size. The array dimensions are separately checked by 76 # _check_that_inner_array_dimensions_are_constant, which will provide an 77 # appropriate error message for that case. 78 return 79 assert type_ir.base_type.HasField("atomic_type") 80 if type_ir.base_type.HasField("size_in_bits"): 81 # If the base_type has a size_in_bits, then it is fixed size. 82 return 83 base_type = ir_util.find_object(type_ir.base_type.atomic_type.reference, ir) 84 base_type_fixed_size = ir_util.get_integer_attribute( 85 base_type.attribute, attributes.FIXED_SIZE) 86 if base_type_fixed_size is None: 87 errors.append([error.error(source_file_name, 88 type_ir.base_type.atomic_type.source_location, 89 "Array elements must be fixed size.")]) 90 91 92def _check_that_array_base_types_in_structs_are_multiples_of_bytes( 93 type_ir, type_definition, source_file_name, errors, ir): 94 # TODO(bolms): Remove this limitation. 95 """Checks that the sizes of array elements are multiples of 8 bits.""" 96 if type_ir.base_type.HasField("array_type"): 97 # Only check the innermost array for multidimensional arrays. 98 return 99 assert type_ir.base_type.HasField("atomic_type") 100 if type_ir.base_type.HasField("size_in_bits"): 101 assert ir_util.is_constant(type_ir.base_type.size_in_bits) 102 base_type_size = ir_util.constant_value(type_ir.base_type.size_in_bits) 103 else: 104 fixed_size = ir_util.fixed_size_of_type_in_bits(type_ir.base_type, ir) 105 if fixed_size is None: 106 # Variable-sized elements are checked elsewhere. 107 return 108 base_type_size = fixed_size 109 if base_type_size % type_definition.addressable_unit != 0: 110 assert type_definition.addressable_unit == ir_data.AddressableUnit.BYTE 111 errors.append([error.error(source_file_name, 112 type_ir.base_type.source_location, 113 "Array elements in structs must have sizes " 114 "which are a multiple of 8 bits.")]) 115 116 117def _check_constancy_of_constant_references(expression, source_file_name, 118 errors, ir): 119 """Checks that constant_references are constant.""" 120 if expression.WhichOneof("expression") != "constant_reference": 121 return 122 # This is a bit of a hack: really, we want to know that the referred-to object 123 # has no dependencies on any instance variables of its parent structure; i.e., 124 # that its value does not depend on having a view of the structure. 125 if not ir_util.is_constant_type(expression.type): 126 referred_name = expression.constant_reference.canonical_name 127 referred_object = ir_util.find_object(referred_name, ir) 128 errors.append([ 129 error.error( 130 source_file_name, expression.source_location, 131 "Static references must refer to constants."), 132 error.note( 133 referred_name.module_file, referred_object.source_location, 134 "{} is not constant.".format(referred_name.object_path[-1])) 135 ]) 136 137 138def _check_that_enum_values_are_representable(enum_type, type_definition, 139 source_file_name, errors): 140 """Checks that enumeration values can fit in their specified int type.""" 141 values = [] 142 max_enum_size = ir_util.get_integer_attribute( 143 type_definition.attribute, attributes.ENUM_MAXIMUM_BITS) 144 is_signed = ir_util.get_boolean_attribute( 145 type_definition.attribute, attributes.IS_SIGNED) 146 if is_signed: 147 enum_range = (-(2**(max_enum_size-1)), 2**(max_enum_size-1)-1) 148 else: 149 enum_range = (0, 2**max_enum_size-1) 150 for value in enum_type.value: 151 values.append((ir_util.constant_value(value.value), value)) 152 out_of_range = [v for v in values 153 if not enum_range[0] <= v[0] <= enum_range[1]] 154 # If all values are in range, this loop will have zero iterations. 155 for value in out_of_range: 156 errors.append([ 157 error.error( 158 source_file_name, value[1].value.source_location, 159 "Value {} is out of range for {}-bit {} enumeration.".format( 160 value[0], max_enum_size, "signed" if is_signed else "unsigned")) 161 ]) 162 163 164def _field_size(field, type_definition): 165 """Calculates the size of the given field in bits, if it is constant.""" 166 size = ir_util.constant_value(field.location.size) 167 if size is None: 168 return None 169 return size * type_definition.addressable_unit 170 171 172def _check_type_requirements_for_field(type_ir, type_definition, field, ir, 173 source_file_name, errors): 174 """Checks that the `requires` attribute of each field's type is fulfilled.""" 175 if not type_ir.HasField("atomic_type"): 176 return 177 178 if field.type.HasField("atomic_type"): 179 field_min_size = (int(field.location.size.type.integer.minimum_value) * 180 type_definition.addressable_unit) 181 field_max_size = (int(field.location.size.type.integer.maximum_value) * 182 type_definition.addressable_unit) 183 field_is_atomic = True 184 else: 185 field_is_atomic = False 186 187 if type_ir.HasField("size_in_bits"): 188 element_size = ir_util.constant_value(type_ir.size_in_bits) 189 else: 190 element_size = None 191 192 referenced_type_definition = ir_util.find_object( 193 type_ir.atomic_type.reference, ir) 194 type_is_anonymous = referenced_type_definition.name.is_anonymous 195 type_size_attr = ir_util.get_attribute( 196 referenced_type_definition.attribute, attributes.FIXED_SIZE) 197 if type_size_attr: 198 type_size = ir_util.constant_value(type_size_attr.expression) 199 else: 200 type_size = None 201 202 if (element_size is not None and type_size is not None and 203 element_size != type_size): 204 errors.append([ 205 error.error( 206 source_file_name, type_ir.size_in_bits.source_location, 207 "Explicit size of {} bits does not match fixed size ({} bits) of " 208 "{}.".format(element_size, type_size, 209 _render_atomic_type_name(type_ir, ir))), 210 error.note( 211 type_ir.atomic_type.reference.canonical_name.module_file, 212 type_size_attr.source_location, 213 "Size specified here.") 214 ]) 215 return 216 217 # If the type had no size specifier (the ':32' in 'UInt:32'), but the type is 218 # fixed size, then continue as if the type's size were explicitly stated. 219 if element_size is None: 220 element_size = type_size 221 222 # TODO(bolms): When the full dynamic size expression for types is generated, 223 # add a check that dynamically-sized types can, at least potentially, fit in 224 # their fields. 225 226 if field_is_atomic and element_size is not None: 227 # If the field has a fixed size, and the (atomic) type contained therein is 228 # also fixed size, then the sizes should match. 229 # 230 # TODO(bolms): Maybe change the case where the field is bigger than 231 # necessary into a warning? 232 if (field_max_size == field_min_size and 233 (element_size > field_max_size or 234 (element_size < field_min_size and not type_is_anonymous))): 235 errors.append([ 236 error.error( 237 source_file_name, type_ir.source_location, 238 "Fixed-size {} cannot be placed in field of size {} bits; " 239 "requires {} bits.".format( 240 _render_type(type_ir, ir), field_max_size, element_size)) 241 ]) 242 return 243 elif element_size > field_max_size: 244 errors.append([ 245 error.error( 246 source_file_name, type_ir.source_location, 247 "Field of maximum size {} bits cannot hold fixed-size {}, which " 248 "requires {} bits.".format( 249 field_max_size, _render_type(type_ir, ir), element_size)) 250 ]) 251 return 252 253 # If we're here, then field/type sizes are consistent. 254 if (element_size is None and field_is_atomic and 255 field_min_size == field_max_size): 256 # From here down, we just use element_size. 257 element_size = field_min_size 258 259 errors.extend(_check_physical_type_requirements( 260 type_ir, field.source_location, element_size, ir, source_file_name)) 261 262 263def _check_type_requirements_for_parameter_type( 264 runtime_parameter, ir, source_file_name, errors): 265 """Checks that the type of a parameter is valid.""" 266 physical_type = runtime_parameter.physical_type_alias 267 logical_type = runtime_parameter.type 268 size = ir_util.constant_value(physical_type.size_in_bits) 269 if logical_type.WhichOneof("type") == "integer": 270 integer_errors = _integer_bounds_errors( 271 logical_type.integer, "parameter", source_file_name, 272 physical_type.source_location) 273 if integer_errors: 274 errors.extend(integer_errors) 275 return 276 errors.extend(_check_physical_type_requirements( 277 physical_type, runtime_parameter.source_location, 278 size, ir, source_file_name)) 279 elif logical_type.WhichOneof("type") == "enumeration": 280 if physical_type.HasField("size_in_bits"): 281 # This seems a little weird: for `UInt`, `Int`, etc., the explicit size is 282 # required, but for enums it is banned. This is because enums have a 283 # "native" 64-bit size in expressions, so the physical size is just 284 # ignored. 285 errors.extend([[ 286 error.error( 287 source_file_name, physical_type.size_in_bits.source_location, 288 "Parameters with enum type may not have explicit size.") 289 290 ]]) 291 else: 292 assert False, "Non-integer/enum parameters should have been caught earlier." 293 294 295def _check_physical_type_requirements( 296 type_ir, usage_source_location, size, ir, source_file_name): 297 """Checks that the given atomic `type_ir` is allowed to be `size` bits.""" 298 referenced_type_definition = ir_util.find_object( 299 type_ir.atomic_type.reference, ir) 300 if referenced_type_definition.HasField("enumeration"): 301 if size is None: 302 return [[ 303 error.error( 304 source_file_name, type_ir.source_location, 305 "Enumeration {} cannot be placed in a dynamically-sized " 306 "field.".format(_render_type(type_ir, ir))) 307 ]] 308 else: 309 max_enum_size = ir_util.get_integer_attribute( 310 referenced_type_definition.attribute, attributes.ENUM_MAXIMUM_BITS) 311 if size < 1 or size > max_enum_size: 312 return [[ 313 error.error( 314 source_file_name, type_ir.source_location, 315 "Enumeration {} cannot be {} bits; {} must be between " 316 "1 and {} bits, inclusive.".format( 317 _render_atomic_type_name(type_ir, ir), size, 318 _render_atomic_type_name(type_ir, ir), max_enum_size)) 319 ]] 320 321 if size is None: 322 bindings = {"$is_statically_sized": False} 323 else: 324 bindings = { 325 "$is_statically_sized": True, 326 "$static_size_in_bits": size 327 } 328 requires_attr = ir_util.get_attribute( 329 referenced_type_definition.attribute, attributes.STATIC_REQUIREMENTS) 330 if requires_attr and not ir_util.constant_value(requires_attr.expression, 331 bindings): 332 # TODO(bolms): Figure out a better way to build this error message. 333 # The "Requirements specified here." message should print out the actual 334 # source text of the requires attribute, so that should help, but it's still 335 # a bit generic and unfriendly. 336 return [[ 337 error.error( 338 source_file_name, usage_source_location, 339 "Requirements of {} not met.".format( 340 type_ir.atomic_type.reference.canonical_name.object_path[-1])), 341 error.note( 342 type_ir.atomic_type.reference.canonical_name.module_file, 343 requires_attr.source_location, 344 "Requirements specified here.") 345 ]] 346 return [] 347 348 349def _check_allowed_in_bits(type_ir, type_definition, source_file_name, ir, 350 errors): 351 if not type_ir.HasField("atomic_type"): 352 return 353 referenced_type_definition = ir_util.find_object( 354 type_ir.atomic_type.reference, ir) 355 if (type_definition.addressable_unit % 356 referenced_type_definition.addressable_unit != 0): 357 assert type_definition.addressable_unit == ir_data.AddressableUnit.BIT 358 assert (referenced_type_definition.addressable_unit == 359 ir_data.AddressableUnit.BYTE) 360 errors.append([ 361 error.error(source_file_name, type_ir.source_location, 362 "Byte-oriented {} cannot be used in a bits field.".format( 363 _render_type(type_ir, ir))) 364 ]) 365 366 367def _check_size_of_bits(type_ir, type_definition, source_file_name, errors): 368 """Checks that `bits` types are fixed size, less than 64 bits.""" 369 del type_ir # Unused 370 if type_definition.addressable_unit != ir_data.AddressableUnit.BIT: 371 return 372 fixed_size = ir_util.get_integer_attribute( 373 type_definition.attribute, attributes.FIXED_SIZE) 374 if fixed_size is None: 375 errors.append([error.error(source_file_name, 376 type_definition.source_location, 377 "`bits` types must be fixed size.")]) 378 return 379 if fixed_size > 64: 380 errors.append([error.error(source_file_name, 381 type_definition.source_location, 382 "`bits` types must be 64 bits or smaller.")]) 383 384 385_RESERVED_WORDS = None 386 387 388def get_reserved_word_list(): 389 if _RESERVED_WORDS is None: 390 _initialize_reserved_word_list() 391 return _RESERVED_WORDS 392 393 394def _initialize_reserved_word_list(): 395 global _RESERVED_WORDS 396 _RESERVED_WORDS = {} 397 language = None 398 for line in resources.load( 399 "compiler.front_end", "reserved_words").splitlines(): 400 stripped_line = line.partition("#")[0].strip() 401 if not stripped_line: 402 continue 403 if stripped_line.startswith("--"): 404 language = stripped_line.partition("--")[2].strip() 405 else: 406 # For brevity's sake, only use the first language for error messages. 407 if stripped_line not in _RESERVED_WORDS: 408 _RESERVED_WORDS[stripped_line] = language 409 410 411def _check_name_for_reserved_words(obj, source_file_name, errors, context_name): 412 if obj.name.name.text in get_reserved_word_list(): 413 errors.append([ 414 error.error( 415 source_file_name, obj.name.name.source_location, 416 "{} reserved word may not be used as {}.".format( 417 get_reserved_word_list()[obj.name.name.text], 418 context_name)) 419 ]) 420 421 422def _check_field_name_for_reserved_words(field, source_file_name, errors): 423 return _check_name_for_reserved_words(field, source_file_name, errors, 424 "a field name") 425 426 427def _check_enum_name_for_reserved_words(enum, source_file_name, errors): 428 return _check_name_for_reserved_words(enum, source_file_name, errors, 429 "an enum name") 430 431 432def _check_type_name_for_reserved_words(type_definition, source_file_name, 433 errors): 434 return _check_name_for_reserved_words( 435 type_definition, source_file_name, errors, "a type name") 436 437 438def _bounds_can_fit_64_bit_unsigned(minimum, maximum): 439 return minimum >= 0 and maximum <= 2**64 - 1 440 441 442def _bounds_can_fit_64_bit_signed(minimum, maximum): 443 return minimum >= -(2**63) and maximum <= 2**63 - 1 444 445 446def _bounds_can_fit_any_64_bit_integer_type(minimum, maximum): 447 return (_bounds_can_fit_64_bit_unsigned(minimum, maximum) or 448 _bounds_can_fit_64_bit_signed(minimum, maximum)) 449 450 451def _integer_bounds_errors_for_expression(expression, source_file_name): 452 """Checks that `expression` is in range for int64_t or uint64_t.""" 453 # Only check non-constant subexpressions. 454 if (expression.WhichOneof("expression") == "function" and 455 not ir_util.is_constant_type(expression.type)): 456 errors = [] 457 for arg in expression.function.args: 458 errors += _integer_bounds_errors_for_expression(arg, source_file_name) 459 if errors: 460 # Don't cascade bounds errors: report them at the lowest level they 461 # appear. 462 return errors 463 if expression.type.WhichOneof("type") == "integer": 464 errors = _integer_bounds_errors(expression.type.integer, "expression", 465 source_file_name, 466 expression.source_location) 467 if errors: 468 return errors 469 if (expression.WhichOneof("expression") == "function" and 470 not ir_util.is_constant_type(expression.type)): 471 int64_only_clauses = [] 472 uint64_only_clauses = [] 473 for clause in [expression] + list(expression.function.args): 474 if clause.type.WhichOneof("type") == "integer": 475 arg_minimum = int(clause.type.integer.minimum_value) 476 arg_maximum = int(clause.type.integer.maximum_value) 477 if not _bounds_can_fit_64_bit_signed(arg_minimum, arg_maximum): 478 uint64_only_clauses.append(clause) 479 elif not _bounds_can_fit_64_bit_unsigned(arg_minimum, arg_maximum): 480 int64_only_clauses.append(clause) 481 if int64_only_clauses and uint64_only_clauses: 482 error_set = [ 483 error.error( 484 source_file_name, expression.source_location, 485 "Either all arguments to '{}' and its result must fit in a " 486 "64-bit unsigned integer, or all must fit in a 64-bit signed " 487 "integer.".format(expression.function.function_name.text)) 488 ] 489 for signedness, clause_list in (("unsigned", uint64_only_clauses), 490 ("signed", int64_only_clauses)): 491 for clause in clause_list: 492 error_set.append(error.note( 493 source_file_name, clause.source_location, 494 "Requires {} 64-bit integer.".format(signedness))) 495 return [error_set] 496 return [] 497 498 499def _integer_bounds_errors(bounds, name, source_file_name, 500 error_source_location): 501 """Returns appropriate errors, if any, for the given integer bounds.""" 502 assert bounds.minimum_value, "{}".format(bounds) 503 assert bounds.maximum_value, "{}".format(bounds) 504 if (bounds.minimum_value == "-infinity" or 505 bounds.maximum_value == "infinity"): 506 return [[ 507 error.error( 508 source_file_name, error_source_location, 509 "Integer range of {} must not be unbounded; it must fit " 510 "in a 64-bit signed or unsigned integer.".format(name)) 511 ]] 512 if not _bounds_can_fit_any_64_bit_integer_type(int(bounds.minimum_value), 513 int(bounds.maximum_value)): 514 if int(bounds.minimum_value) == int(bounds.maximum_value): 515 return [[ 516 error.error( 517 source_file_name, error_source_location, 518 "Constant value {} of {} cannot fit in a 64-bit signed or " 519 "unsigned integer.".format(bounds.minimum_value, name)) 520 ]] 521 else: 522 return [[ 523 error.error( 524 source_file_name, error_source_location, 525 "Potential range of {} is {} to {}, which cannot fit " 526 "in a 64-bit signed or unsigned integer.".format( 527 name, bounds.minimum_value, bounds.maximum_value)) 528 ]] 529 return [] 530 531 532def _check_bounds_on_runtime_integer_expressions(expression, source_file_name, 533 in_attribute, errors): 534 if in_attribute and in_attribute.name.text == attributes.STATIC_REQUIREMENTS: 535 # [static_requirements] is never evaluated at runtime, and $size_in_bits is 536 # unbounded, so it should not be checked. 537 return 538 # The logic for gathering errors and suppressing cascades is simpler if 539 # errors are just returned, rather than appended to a shared list. 540 errors += _integer_bounds_errors_for_expression(expression, source_file_name) 541 542def _attribute_in_attribute_action(a): 543 return {"in_attribute": a} 544 545def check_constraints(ir): 546 """Checks miscellaneous validity constraints in ir. 547 548 Checks that auto array sizes are only used for the outermost size of 549 multidimensional arrays. That is, Type[3][] is OK, but Type[][3] is not. 550 551 Checks that fixed-size fields are a correct size to hold statically-sized 552 types. 553 554 Checks that inner array dimensions are constant. 555 556 Checks that only constant-size types are used in arrays. 557 558 Arguments: 559 ir: An ir_data.EmbossIr object to check. 560 561 Returns: 562 A list of ConstraintViolations, or an empty list if there are none. 563 """ 564 errors = [] 565 traverse_ir.fast_traverse_ir_top_down( 566 ir, [ir_data.Structure, ir_data.Type], _check_allowed_in_bits, 567 parameters={"errors": errors}) 568 traverse_ir.fast_traverse_ir_top_down( 569 # TODO(bolms): look for [ir_data.ArrayType], [ir_data.AtomicType], and 570 # simplify _check_that_array_base_types_are_fixed_size. 571 ir, [ir_data.ArrayType], _check_that_array_base_types_are_fixed_size, 572 parameters={"errors": errors}) 573 traverse_ir.fast_traverse_ir_top_down( 574 ir, [ir_data.Structure, ir_data.ArrayType], 575 _check_that_array_base_types_in_structs_are_multiples_of_bytes, 576 parameters={"errors": errors}) 577 traverse_ir.fast_traverse_ir_top_down( 578 ir, [ir_data.ArrayType, ir_data.ArrayType], 579 _check_that_inner_array_dimensions_are_constant, 580 parameters={"errors": errors}) 581 traverse_ir.fast_traverse_ir_top_down( 582 ir, [ir_data.Structure], _check_size_of_bits, 583 parameters={"errors": errors}) 584 traverse_ir.fast_traverse_ir_top_down( 585 ir, [ir_data.Structure, ir_data.Type], _check_type_requirements_for_field, 586 parameters={"errors": errors}) 587 traverse_ir.fast_traverse_ir_top_down( 588 ir, [ir_data.Field], _check_field_name_for_reserved_words, 589 parameters={"errors": errors}) 590 traverse_ir.fast_traverse_ir_top_down( 591 ir, [ir_data.EnumValue], _check_enum_name_for_reserved_words, 592 parameters={"errors": errors}) 593 traverse_ir.fast_traverse_ir_top_down( 594 ir, [ir_data.TypeDefinition], _check_type_name_for_reserved_words, 595 parameters={"errors": errors}) 596 traverse_ir.fast_traverse_ir_top_down( 597 ir, [ir_data.Expression], _check_constancy_of_constant_references, 598 parameters={"errors": errors}) 599 traverse_ir.fast_traverse_ir_top_down( 600 ir, [ir_data.Enum], _check_that_enum_values_are_representable, 601 parameters={"errors": errors}) 602 traverse_ir.fast_traverse_ir_top_down( 603 ir, [ir_data.Expression], _check_bounds_on_runtime_integer_expressions, 604 incidental_actions={ir_data.Attribute: _attribute_in_attribute_action}, 605 skip_descendants_of={ir_data.EnumValue, ir_data.Expression}, 606 parameters={"errors": errors, "in_attribute": None}) 607 traverse_ir.fast_traverse_ir_top_down( 608 ir, [ir_data.RuntimeParameter], 609 _check_type_requirements_for_parameter_type, 610 parameters={"errors": errors}) 611 return errors 612