class Puppet::Pops::Types::TypeMismatchDescriber
@api private
Public Class Methods
# File lib/puppet/pops/types/type_mismatch_describer.rb 506 def self.describe_signatures(closure, signatures, args_tuple) 507 singleton.describe_signatures(closure, signatures, args_tuple) 508 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 510 def self.singleton 511 @singleton ||= new 512 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 502 def self.validate_default_parameter(subject, param_name, param_type, value) 503 singleton.validate_default_parameter(subject, param_name, param_type, value) 504 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 498 def self.validate_parameters(subject, params_struct, given_hash, missing_ok = false) 499 singleton.validate_parameters(subject, params_struct, given_hash, missing_ok) 500 end
Public Instance Methods
# File lib/puppet/pops/types/type_mismatch_describer.rb 969 def describe(expected, actual, path) 970 ures_finder = UnresolvedTypeFinder.new 971 expected.accept(ures_finder, nil) 972 unresolved = ures_finder.unresolved 973 if unresolved 974 [UnresolvedTypeReference.new(path, unresolved)] 975 else 976 internal_describe(expected.normalize, expected, actual, path) 977 end 978 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 949 def describe_PAnyType(expected, original, actual, path) 950 expected.assignable?(actual) ? EMPTY_ARRAY : [TypeMismatch.new(path, original, actual)] 951 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 772 def describe_PArrayType(expected, original, actual, path) 773 descriptions = [] 774 element_type = expected.element_type || PAnyType::DEFAULT 775 if actual.is_a?(PTupleType) 776 types = actual.types 777 expected_size = expected.size_type || PCollectionType::DEFAULT_SIZE 778 actual_size = actual.size_type || PIntegerType.new(types.size, types.size) 779 if expected_size.assignable?(actual_size) 780 types.each_with_index do |type, idx| 781 descriptions.concat(describe(element_type, type, path + [ArrayPathElement.new(idx)])) unless element_type.assignable?(type) 782 end 783 else 784 descriptions << SizeMismatch.new(path, expected_size, actual_size) 785 end 786 elsif actual.is_a?(PArrayType) 787 expected_size = expected.size_type 788 actual_size = actual.size_type || PCollectionType::DEFAULT_SIZE 789 if expected_size.nil? || expected_size.assignable?(actual_size) 790 descriptions << TypeMismatch.new(path, original, PArrayType.new(actual.element_type)) 791 else 792 descriptions << SizeMismatch.new(path, expected_size, actual_size) 793 end 794 else 795 descriptions << TypeMismatch.new(path, original, actual) 796 end 797 descriptions 798 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 915 def describe_PCallableType(expected, original, actual, path) 916 if actual.is_a?(PCallableType) 917 # nil param_types means, any other Callable is assignable 918 if expected.param_types.nil? && expected.return_type.nil? 919 EMPTY_ARRAY 920 else 921 # NOTE: these tests are made in reverse as it is calling the callable that is constrained 922 # (it's lower bound), not its upper bound 923 param_errors = describe_argument_tuple(expected.param_types, actual.param_types, path) 924 if param_errors.empty? 925 this_return_t = expected.return_type || PAnyType::DEFAULT 926 that_return_t = actual.return_type || PAnyType::DEFAULT 927 unless this_return_t.assignable?(that_return_t) 928 [TypeMismatch.new(path + [ReturnTypeElement.new], this_return_t, that_return_t)] 929 else 930 # names are ignored, they are just information 931 # Blocks must be compatible 932 this_block_t = expected.block_type || PUndefType::DEFAULT 933 that_block_t = actual.block_type || PUndefType::DEFAULT 934 if that_block_t.assignable?(this_block_t) 935 EMPTY_ARRAY 936 else 937 [TypeMismatch.new(path + [BlockPathElement.new], this_block_t, that_block_t)] 938 end 939 end 940 else 941 param_errors 942 end 943 end 944 else 945 [TypeMismatch.new(path, original, actual)] 946 end 947 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 760 def describe_PEnumType(expected, original, actual, path) 761 [PatternMismatch.new(path, original, actual)] 762 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 800 def describe_PHashType(expected, original, actual, path) 801 descriptions = [] 802 key_type = expected.key_type || PAnyType::DEFAULT 803 value_type = expected.value_type || PAnyType::DEFAULT 804 if actual.is_a?(PStructType) 805 elements = actual.elements 806 expected_size = expected.size_type || PCollectionType::DEFAULT_SIZE 807 actual_size = PIntegerType.new(elements.count { |a| !a.key_type.assignable?(PUndefType::DEFAULT) }, elements.size) 808 if expected_size.assignable?(actual_size) 809 elements.each do |a| 810 descriptions.concat(describe(key_type, a.key_type, path + [EntryKeyPathElement.new(a.name)])) unless key_type.assignable?(a.key_type) 811 descriptions.concat(describe(value_type, a.value_type, path + [EntryValuePathElement.new(a.name)])) unless value_type.assignable?(a.value_type) 812 end 813 else 814 descriptions << SizeMismatch.new(path, expected_size, actual_size) 815 end 816 elsif actual.is_a?(PHashType) 817 expected_size = expected.size_type 818 actual_size = actual.size_type || PCollectionType::DEFAULT_SIZE 819 if expected_size.nil? || expected_size.assignable?(actual_size) 820 descriptions << TypeMismatch.new(path, original, PHashType.new(actual.key_type, actual.value_type)) 821 else 822 descriptions << SizeMismatch.new(path, expected_size, actual_size) 823 end 824 else 825 descriptions << TypeMismatch.new(path, original, actual) 826 end 827 descriptions 828 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 755 def describe_POptionalType(expected, original, actual, path) 756 return EMPTY_ARRAY if actual.is_a?(PUndefType) || expected.optional_type.nil? 757 internal_describe(expected.optional_type, original.is_a?(PTypeAliasType) ? original : expected, actual, path) 758 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 764 def describe_PPatternType(expected, original, actual, path) 765 [PatternMismatch.new(path, original, actual)] 766 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 830 def describe_PStructType(expected, original, actual, path) 831 elements = expected.elements 832 descriptions = [] 833 if actual.is_a?(PStructType) 834 h2 = actual.hashed_elements.clone 835 elements.each do |e1| 836 key = e1.name 837 e2 = h2.delete(key) 838 if e2.nil? 839 descriptions << MissingKey.new(path, key) unless e1.key_type.assignable?(PUndefType::DEFAULT) 840 else 841 descriptions.concat(describe(e1.key_type, e2.key_type, path + [EntryKeyPathElement.new(key)])) unless e1.key_type.assignable?(e2.key_type) 842 descriptions.concat(describe(e1.value_type, e2.value_type, path + [EntryValuePathElement.new(key)])) unless e1.value_type.assignable?(e2.value_type) 843 end 844 end 845 h2.each_key { |key| descriptions << ExtraneousKey.new(path, key) } 846 elsif actual.is_a?(PHashType) 847 actual_size = actual.size_type || PCollectionType::DEFAULT_SIZE 848 expected_size = PIntegerType.new(elements.count { |e| !e.key_type.assignable?(PUndefType::DEFAULT) }, elements.size) 849 if expected_size.assignable?(actual_size) 850 descriptions << TypeMismatch.new(path, original, PHashType.new(actual.key_type, actual.value_type)) 851 else 852 descriptions << SizeMismatch.new(path, expected_size, actual_size) 853 end 854 else 855 descriptions << TypeMismatch.new(path, original, actual) 856 end 857 descriptions 858 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 860 def describe_PTupleType(expected, original, actual, path) 861 describe_tuple(expected, original, actual, path, SizeMismatch) 862 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 768 def describe_PTypeAliasType(expected, original, actual, path) 769 internal_describe(expected.resolved_type.normalize, expected, actual, path) 770 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 715 def describe_PVariantType(expected, original, actual, path) 716 variant_descriptions = [] 717 types = expected.types 718 types = [PUndefType::DEFAULT] + types if original.is_a?(POptionalType) 719 types.each_with_index do |vt, index| 720 d = describe(vt, actual, path + [VariantPathElement.new(index)]) 721 return EMPTY_ARRAY if d.empty? 722 variant_descriptions << d 723 end 724 descriptions = merge_descriptions(path.length, SizeMismatch, variant_descriptions) 725 if original.is_a?(PTypeAliasType) && descriptions.size == 1 726 # All variants failed in this alias so we report it as a mismatch on the alias 727 # rather than reporting individual failures of the variants 728 [TypeMismatch.new(path, original, actual)] 729 else 730 descriptions 731 end 732 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 864 def describe_argument_tuple(expected, actual, path) 865 describe_tuple(expected, expected, actual, path, CountMismatch) 866 end
Describe a confirmed mismatch using present tense
@param name [String] name of mismatch @param expected [PAnyType] expected type @param actual [PAnyType] actual type @param tense [Symbol] deprecated and ignored
# File lib/puppet/pops/types/type_mismatch_describer.rb 550 def describe_mismatch(name, expected, actual, tense = :ignored) 551 tense_deprecated unless tense == :ignored 552 errors = describe(expected, actual, [SubjectPathElement.new(name)]) 553 case errors.size 554 when 0 555 '' 556 when 1 557 errors[0].format.strip 558 else 559 errors.map { |error| error.format }.join("\n ") 560 end 561 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 696 def describe_no_block_arguments(signature, atypes, path, expected_size, actual_size, arg_count) 697 # not assignable if the number of types in actual is outside number of types in expected 698 if expected_size.assignable?(actual_size) 699 etypes = signature.type.param_types.types 700 enames = signature.parameter_names 701 arg_count.times do |index| 702 adx = index >= etypes.size ? etypes.size - 1 : index 703 etype = etypes[adx] 704 unless etype.assignable?(atypes[index]) 705 descriptions = describe(etype, atypes[index], path + [ParameterPathElement.new(enames[adx])]) 706 return descriptions unless descriptions.empty? 707 end 708 end 709 EMPTY_ARRAY 710 else 711 [CountMismatch.new(path, expected_size, actual_size)] 712 end 713 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 653 def describe_signature_arguments(signature, args_tuple, path) 654 params_tuple = signature.type.param_types 655 params_size_t = params_tuple.size_type || TypeFactory.range(*params_tuple.size_range) 656 657 if args_tuple.is_a?(PTupleType) 658 arg_types = args_tuple.types 659 elsif args_tuple.is_a?(PArrayType) 660 arg_types = Array.new(params_tuple.types.size, args_tuple.element_type || PUndefType::DEFAULT) 661 else 662 return [TypeMismatch.new(path, params_tuple, args_tuple)] 663 end 664 665 if arg_types.last.kind_of_callable? 666 # Check other arguments 667 arg_count = arg_types.size - 1 668 describe_no_block_arguments(signature, arg_types, path, params_size_t, TypeFactory.range(arg_count, arg_count), arg_count) 669 else 670 args_size_t = TypeFactory.range(*args_tuple.size_range) 671 describe_no_block_arguments(signature, arg_types, path, params_size_t, args_size_t, arg_types.size) 672 end 673 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 675 def describe_signature_block(signature, args_tuple, path) 676 param_block_t = signature.block_type 677 arg_block_t = args_tuple.is_a?(PTupleType) ? args_tuple.types.last : nil 678 if TypeCalculator.is_kind_of_callable?(arg_block_t) 679 # Can't pass a block to a callable that doesn't accept one 680 if param_block_t.nil? 681 [UnexpectedBlock.new(path)] 682 else 683 # Check that the block is of the right type 684 describe(param_block_t, arg_block_t, path + [BlockPathElement.new]) 685 end 686 else 687 # Check that the block is optional 688 if param_block_t.nil? || param_block_t.assignable?(PUndefType::DEFAULT) 689 EMPTY_ARRAY 690 else 691 [MissingRequiredBlock.new(path)] 692 end 693 end 694 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 609 def describe_signatures(closure, signatures, args_tuple, tense = :ignored) 610 tense_deprecated unless tense == :ignored 611 error_arrays = [] 612 signatures.each_with_index do |signature, index| 613 error_arrays << describe_signature_arguments(signature, args_tuple, [SignaturePathElement.new(index)]) 614 end 615 616 # Skip block checks if all signatures have argument errors 617 unless error_arrays.all? { |a| !a.empty? } 618 block_arrays = [] 619 signatures.each_with_index do |signature, index| 620 block_arrays << describe_signature_block(signature, args_tuple, [SignaturePathElement.new(index)]) 621 end 622 bc_count = block_arrays.count { |a| !a.empty? } 623 if bc_count == block_arrays.size 624 # Skip argument errors when all alternatives have block errors 625 error_arrays = block_arrays 626 elsif bc_count > 0 627 # Merge errors giving argument errors precedence over block errors 628 error_arrays.each_with_index { |a, index| error_arrays[index] = block_arrays[index] if a.empty? } 629 end 630 end 631 return nil if error_arrays.empty? 632 633 label = closure == 'lambda' ? 'block' : "'#{closure}'" 634 errors = merge_descriptions(0, CountMismatch, error_arrays) 635 if errors.size == 1 636 "#{label}#{errors[0].format}" 637 else 638 if signatures.size == 1 639 sig = signatures[0] 640 result = ["#{label} expects (#{signature_string(sig)})"] 641 result.concat(error_arrays[0].map { |e| " rejected:#{e.chop_path(0).format}" }) 642 else 643 result = ["The function #{label} was called with arguments it does not accept. It expects one of:"] 644 signatures.each_with_index do |sg, index| 645 result << " (#{signature_string(sg)})" 646 result.concat(error_arrays[index].map { |e| " rejected:#{e.chop_path(0).format}" }) 647 end 648 end 649 result.join("\n") 650 end 651 end
Validates that all entries in the param_hash exists in the given param_struct, that their type conforms with the corresponding param_struct element and that all required values are provided. An error message is created for each problem found.
@param params_struct [PStructType] Struct to use for validation @param param_hash [Hash<String,Object>] The parameters to validate @param missing_ok [Boolean] Do not generate errors on missing parameters @return [Array<Mismatch>] An array of found errors. An empty array indicates no errors.
# File lib/puppet/pops/types/type_mismatch_describer.rb 592 def describe_struct_signature(params_struct, param_hash, missing_ok = false) 593 param_type_hash = params_struct.hashed_elements 594 result = param_hash.each_key.reject { |name| param_type_hash.include?(name) }.map { |name| InvalidParameter.new(nil, name) } 595 596 params_struct.elements.each do |elem| 597 name = elem.name 598 value = param_hash[name] 599 value_type = elem.value_type 600 if param_hash.include?(name) 601 result << describe(value_type, TypeCalculator.singleton.infer_set(value), [ParameterPathElement.new(name)]) unless value_type.instance?(value) 602 else 603 result << MissingParameter.new(nil, name) unless missing_ok || elem.key_type.is_a?(POptionalType) 604 end 605 end 606 result 607 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 868 def describe_tuple(expected, original, actual, path, size_mismatch_class) 869 return EMPTY_ARRAY if expected == actual || expected.types.empty? && (actual.is_a?(PArrayType)) 870 expected_size = expected.size_type || TypeFactory.range(*expected.size_range) 871 872 if actual.is_a?(PTupleType) 873 actual_size = actual.size_type || TypeFactory.range(*actual.size_range) 874 875 # not assignable if the number of types in actual is outside number of types in expected 876 if expected_size.assignable?(actual_size) 877 etypes = expected.types 878 descriptions = [] 879 unless etypes.empty? 880 actual.types.each_with_index do |atype, index| 881 adx = index >= etypes.size ? etypes.size - 1 : index 882 descriptions.concat(describe(etypes[adx], atype, path + [ArrayPathElement.new(adx)])) 883 end 884 end 885 descriptions 886 else 887 [size_mismatch_class.new(path, expected_size, actual_size)] 888 end 889 elsif actual.is_a?(PArrayType) 890 t2_entry = actual.element_type 891 892 if t2_entry.nil? 893 # Array of anything can not be assigned (unless tuple is tuple of anything) - this case 894 # was handled at the top of this method. 895 # 896 [TypeMismatch.new(path, original, actual)] 897 else 898 expected_size = expected.size_type || TypeFactory.range(*expected.size_range) 899 actual_size = actual.size_type || PCollectionType::DEFAULT_SIZE 900 if expected_size.assignable?(actual_size) 901 descriptions = [] 902 expected.types.each_with_index do |etype, index| 903 descriptions.concat(describe(etype, actual.element_type, path + [ArrayPathElement.new(index)])) 904 end 905 descriptions 906 else 907 [size_mismatch_class.new(path, expected_size, actual_size)] 908 end 909 end 910 else 911 [TypeMismatch.new(path, original, actual)] 912 end 913 end
Why oh why Ruby do you not have a standard Math.max ? @api private
# File lib/puppet/pops/types/type_mismatch_describer.rb 1064 def max(a, b) 1065 a >= b ? a : b 1066 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 734 def merge_descriptions(varying_path_position, size_mismatch_class, variant_descriptions) 735 descriptions = variant_descriptions.flatten 736 [size_mismatch_class, MissingRequiredBlock, UnexpectedBlock, TypeMismatch].each do |mismatch_class| 737 mismatches = descriptions.select { |desc| desc.is_a?(mismatch_class) } 738 if mismatches.size == variant_descriptions.size 739 # If they all have the same canonical path, then we can compact this into one 740 generic_mismatch = mismatches.inject do |prev, curr| 741 break nil unless prev.canonical_path == curr.canonical_path 742 prev.merge(prev.path, curr) 743 end 744 unless generic_mismatch.nil? 745 # Report the generic mismatch and skip the rest 746 descriptions = [generic_mismatch] 747 break 748 end 749 end 750 end 751 descriptions = descriptions.uniq 752 descriptions.size == 1 ? [descriptions[0].chop_path(varying_path_position)] : descriptions 753 end
@api private
# File lib/puppet/pops/types/type_mismatch_describer.rb 1069 def optional(index, required_count) 1070 count = index + 1 1071 count > required_count 1072 end
Produces a string for the signature(s)
@api private
# File lib/puppet/pops/types/type_mismatch_describer.rb 1011 def signature_string(signature) 1012 param_types = signature.type.param_types 1013 param_names = signature.parameter_names 1014 1015 from, to = param_types.size_range 1016 if from == 0 && to == 0 1017 # No parameters function 1018 return '' 1019 end 1020 1021 required_count = from 1022 types = 1023 case param_types 1024 when PTupleType 1025 param_types.types 1026 when PArrayType 1027 [param_types.element_type] 1028 end 1029 1030 # join type with names (types are always present, names are optional) 1031 # separate entries with comma 1032 # 1033 param_names = Array.new(types.size, '') if param_names.empty? 1034 limit = param_names.size 1035 result = param_names.each_with_index.map do |name, index| 1036 type = types[index] || types[-1] 1037 indicator = '' 1038 if to == Float::INFINITY && index == limit - 1 1039 # Last is a repeated_param. 1040 indicator = from == param_names.size ? '+' : '*' 1041 elsif optional(index, required_count) 1042 indicator = '?' 1043 type = type.optional_type if type.is_a?(POptionalType) 1044 end 1045 "#{type} #{name}#{indicator}" 1046 end.join(', ') 1047 1048 # If there is a block, include it 1049 case signature.type.block_type 1050 when POptionalType 1051 result << ', ' unless result == '' 1052 result << "#{signature.type.block_type.optional_type} #{signature.block_name}?" 1053 when PCallableType 1054 result << ', ' unless result == '' 1055 result << "#{signature.type.block_type} #{signature.block_name}" 1056 when NilClass 1057 # nothing 1058 end 1059 result 1060 end
# File lib/puppet/pops/types/type_mismatch_describer.rb 514 def tense_deprecated 515 #TRANSLATORS TypeMismatchDescriber is a class name and 'tense' is a method name and should not be translated 516 message = _("Passing a 'tense' argument to the TypeMismatchDescriber is deprecated and ignored.") 517 message += ' ' + _("Everything is now reported using present tense") 518 Puppet.warn_once('deprecations', 'typemismatch#tense', message) 519 end
@param subject [String] string to be prepended to the exception message @param param_name [String] parameter name @param param_type [PAnyType] parameter type @param value [Object] value to be validated against the given type @param tense [Symbol] deprecated and ignored
# File lib/puppet/pops/types/type_mismatch_describer.rb 569 def validate_default_parameter(subject, param_name, param_type, value, tense = :ignored) 570 tense_deprecated unless tense == :ignored 571 unless param_type.instance?(value) 572 errors = describe(param_type, TypeCalculator.singleton.infer_set(value).generalize, [ParameterPathElement.new(param_name)]) 573 case errors.size 574 when 0 575 when 1 576 raise Puppet::ParseError.new("#{subject}:#{errors[0].format}") 577 else 578 errors_str = errors.map { |error| error.format }.join("\n ") 579 raise Puppet::ParseError.new("#{subject}:\n #{errors_str}") 580 end 581 end 582 end
Validates that all entries in the give_hash exists in the given param_struct, that their type conforms with the corresponding param_struct element and that all required values are provided.
@param subject [String] string to be prepended to the exception message @param params_struct [PStructType] Struct to use for validation @param given_hash [Hash<String,Object>] the parameters to validate @param missing_ok [Boolean] Do not generate errors on missing parameters @param tense [Symbol] deprecated and ignored
# File lib/puppet/pops/types/type_mismatch_describer.rb 530 def validate_parameters(subject, params_struct, given_hash, missing_ok = false, tense = :ignored) 531 tense_deprecated unless tense == :ignored 532 errors = describe_struct_signature(params_struct, given_hash, missing_ok).flatten 533 case errors.size 534 when 0 535 when 1 536 raise Puppet::ParseError.new("#{subject}:#{errors[0].format}") 537 else 538 errors_str = errors.map { |error| error.format }.join("\n ") 539 raise Puppet::ParseError.new("#{subject}:\n #{errors_str}") 540 end 541 end
Private Instance Methods
# File lib/puppet/pops/types/type_mismatch_describer.rb 980 def internal_describe(expected, original, actual, path) 981 case expected 982 when PVariantType 983 describe_PVariantType(expected, original, actual, path) 984 when PStructType 985 describe_PStructType(expected, original, actual, path) 986 when PHashType 987 describe_PHashType(expected, original, actual, path) 988 when PTupleType 989 describe_PTupleType(expected, original, actual, path) 990 when PArrayType 991 describe_PArrayType(expected, original, actual, path) 992 when PCallableType 993 describe_PCallableType(expected, original, actual, path) 994 when POptionalType 995 describe_POptionalType(expected, original, actual, path) 996 when PPatternType 997 describe_PPatternType(expected, original, actual, path) 998 when PEnumType 999 describe_PEnumType(expected, original, actual, path) 1000 when PTypeAliasType 1001 describe_PTypeAliasType(expected, original, actual, path) 1002 else 1003 describe_PAnyType(expected, original, actual, path) 1004 end 1005 end