steep 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1aa31e85a07f6ac87650d401444230861764800154bfe367724f5cd9f0813ea0
4
- data.tar.gz: b7b38997f73d6ac3cf030e1114fb9f6d2c20afb9635a922fe86256ee6e5359b5
3
+ metadata.gz: 0c1966d8fc74abea789649fe945211c56d81da2fc655860d22849aacc7feae9f
4
+ data.tar.gz: f4b9793cb25421d5a9e031b78df1c29f1350f9d7cd184f12c772065607664afc
5
5
  SHA512:
6
- metadata.gz: 805a6bcc465e951a41d7742fb3ff82f6c8ccedc137aa3be4969539f4610086c6949f6fc04ca461533847aa745464827f763fb1c1943d0f13e5f309e0414fd64a
7
- data.tar.gz: 8677c727c5f7ffc4bb9994cc7eff5bf06c240df29d7fbbd8796a58666ac261a0137e206a397d5c0af0a7210ed12ef391019a0a209497520dc04a98d81948a63c
6
+ metadata.gz: 4d3309eb83780ad4f157a7c7cc9b4bfb1763aac6ff6a2f905838b5f9bebb462ff7fb7bb2d381af5dc6698287b57ee097301d60143e9385804ca2a4700b5f3466
7
+ data.tar.gz: 1e142225c3dfe0d2ed47831f97840ded1a24c5348e734b3767ce1784546a71bcf5bbb1fd57bfc0baad9ec51b6a3002dacc41535ae451a06b8d6a6e4931ec7e9d
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.5.0
4
+ - 2.5.3
5
5
  before_install: gem install bundler -v 1.13.7
6
6
  script: bundle exec rake test smoke
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.7.1 (2018-10-22)
6
+
7
+ * Rename *hash type* to *record type* (#60)
8
+ * Fix keyword typing (#59)
9
+
5
10
  ## 0.7.0 (2018-09-24)
6
11
 
7
12
  * Add some builtin
@@ -1,7 +1,7 @@
1
1
  module Steep
2
2
  module AST
3
3
  module Types
4
- class Hash
4
+ class Record
5
5
  attr_reader :location
6
6
  attr_reader :elements
7
7
 
@@ -11,7 +11,7 @@ module Steep
11
11
  end
12
12
 
13
13
  def ==(other)
14
- other.is_a?(Hash) && other.elements == elements
14
+ other.is_a?(Record) && other.elements == elements
15
15
  end
16
16
 
17
17
  def hash
data/lib/steep/cli.rb CHANGED
@@ -16,7 +16,7 @@ module Steep
16
16
  end
17
17
 
18
18
  def self.available_commands
19
- [:check, :validate, :annotations, :scaffold, :interface]
19
+ [:check, :validate, :annotations, :scaffold, :interface, :version]
20
20
  end
21
21
 
22
22
  def setup_global_options
@@ -127,5 +127,9 @@ module Steep
127
127
 
128
128
  Drivers::PrintInterface.new(type_name: argv.first, signature_dirs: signature_dirs, stdout: stdout, stderr: stderr).run
129
129
  end
130
+
131
+ def process_version
132
+ stdout.puts Steep::VERSION
133
+ end
130
134
  end
131
135
  end
@@ -65,8 +65,6 @@ module Steep
65
65
  Steep.logger.debug "Typechecking..."
66
66
  annotations = source.annotations(block: source.node, builder: check.builder, current_module: AST::Namespace.root)
67
67
 
68
- pp annotations if verbose
69
-
70
68
  const_env = TypeInference::ConstantEnv.new(builder: check.builder, context: nil)
71
69
  type_env = TypeInference::TypeEnv.build(annotations: annotations,
72
70
  subtyping: check,
@@ -29,7 +29,7 @@ module Steep
29
29
  builder = Interface::Builder.new(signatures: env)
30
30
  check = Subtyping::Check.new(builder: builder)
31
31
 
32
- interface = check.resolve(type, with_initialize: true)
32
+ interface = check.resolve(type)
33
33
 
34
34
  stdout.puts "#{type}"
35
35
  stdout.puts "- Instance variables:"
data/lib/steep/errors.rb CHANGED
@@ -503,5 +503,31 @@ module Steep
503
503
  "#{location_to_str}: IncompatibleTuple: expected_tuple=#{expected_tuple}"
504
504
  end
505
505
  end
506
+
507
+ class UnexpectedKeyword < Base
508
+ attr_reader :unexpected_keywords
509
+
510
+ def initialize(node:, unexpected_keywords:)
511
+ super(node: node)
512
+ @unexpected_keywords = unexpected_keywords
513
+ end
514
+
515
+ def to_s
516
+ "#{location_to_str}: UnexpectedKeyword: #{unexpected_keywords.join(", ")}"
517
+ end
518
+ end
519
+
520
+ class MissingKeyword < Base
521
+ attr_reader :missing_keywords
522
+
523
+ def initialize(node:, missing_keywords:)
524
+ super(node: node)
525
+ @missing_keywords = missing_keywords
526
+ end
527
+
528
+ def to_s
529
+ "#{location_to_str}: MissingKeyword: #{missing_keywords.join(", ")}"
530
+ end
531
+ end
506
532
  end
507
533
  end
@@ -85,8 +85,8 @@ module Steep
85
85
  return_type: absolute_type(type.return_type, current: current),
86
86
  location: type.location
87
87
  )
88
- when AST::Types::Hash
89
- AST::Types::Hash.new(
88
+ when AST::Types::Record
89
+ AST::Types::Record.new(
90
90
  elements: type.elements.transform_values {|ty| absolute_type(ty, current: current) },
91
91
  location: type.location
92
92
  )
@@ -62,6 +62,35 @@ module Steep
62
62
  !required_keywords.empty? || !optional_keywords.empty? || rest_keywords
63
63
  end
64
64
 
65
+ def without_keywords
66
+ self.class.new(
67
+ required: required,
68
+ optional: optional,
69
+ rest: rest,
70
+ required_keywords: {},
71
+ optional_keywords: {},
72
+ rest_keywords: nil
73
+ )
74
+ end
75
+
76
+ def drop_first
77
+ case
78
+ when required.any? || optional.any? || rest
79
+ self.class.new(
80
+ required: required.any? ? required.drop(1) : [],
81
+ optional: required.empty? && optional.any? ? optional.drop(1) : optional,
82
+ rest: required.empty? && optional.empty? ? nil : rest,
83
+ required_keywords: required_keywords,
84
+ optional_keywords: optional_keywords,
85
+ rest_keywords: rest_keywords
86
+ )
87
+ when has_keywords?
88
+ without_keywords
89
+ else
90
+ raise "Cannot drop from empty params"
91
+ end
92
+ end
93
+
65
94
  def each_missing_argument(args)
66
95
  required.size.times do |index|
67
96
  if index >= args.size
@@ -160,10 +189,6 @@ module Steep
160
189
  required.all?(&:closed?) && optional.all?(&:closed?) && (!rest || rest.closed?) && required_keywords.values.all?(&:closed?) && optional_keywords.values.all?(&:closed?) && (!rest_keywords || rest_keywords.closed?)
161
190
  end
162
191
 
163
- def has_keyword?
164
- required_keywords.any? || optional_keywords.any? || rest_keywords
165
- end
166
-
167
192
  def subst(s)
168
193
  self.class.new(
169
194
  required: required.map {|t| t.subst(s) },
@@ -199,6 +224,10 @@ module Steep
199
224
  rest_keywords: rest_keywords && yield(rest_keywords)
200
225
  )
201
226
  end
227
+
228
+ def empty?
229
+ required.empty? && optional.empty? && !rest && !has_keywords?
230
+ end
202
231
  end
203
232
 
204
233
  class Block
data/lib/steep/parser.rb CHANGED
@@ -1846,7 +1846,7 @@ module_eval(<<'.,.,', 'parser.y', 399)
1846
1846
  module_eval(<<'.,.,', 'parser.y', 403)
1847
1847
  def _reduce_76(val, _values, result)
1848
1848
  location = val[0].location + val[2].location
1849
- result = AST::Types::Hash.new(elements: val[1], location: location)
1849
+ result = AST::Types::Record.new(elements: val[1], location: location)
1850
1850
 
1851
1851
  result
1852
1852
  end
data/lib/steep/parser.y CHANGED
@@ -402,7 +402,7 @@ rule
402
402
  | tLBRACE hash_elements tRBRACE
403
403
  {
404
404
  location = val[0].location + val[2].location
405
- result = AST::Types::Hash.new(elements: val[1], location: location)
405
+ result = AST::Types::Record.new(elements: val[1], location: location)
406
406
  }
407
407
  | simple_type
408
408
 
@@ -225,6 +225,25 @@ module Steep
225
225
  trace: trace,
226
226
  constraints: constraints)
227
227
 
228
+ when relation.sub_type.is_a?(AST::Types::Record) && relation.super_type.is_a?(AST::Types::Record)
229
+ if Set.new(relation.sub_type.elements.keys).superset?(Set.new(relation.super_type.elements.keys))
230
+ keys = relation.super_type.elements.keys
231
+ type_pairs = keys.map {|key| [relation.sub_type.elements[key], relation.super_type.elements[key]] }
232
+ results = type_pairs.flat_map do |t1, t2|
233
+ relation = Relation.new(sub_type: t1, super_type: t2)
234
+ [check0(relation, assumption: assumption, trace: trace, constraints: constraints),
235
+ check0(relation.flip, assumption: assumption, trace: trace, constraints: constraints)]
236
+ end
237
+
238
+ if results.all?(&:success?)
239
+ success(constraints: constraints)
240
+ else
241
+ results.find(&:failure?)
242
+ end
243
+ else
244
+ failure(error: Result::Failure::UnknownPairError.new(relation: relation),
245
+ trace: trace)
246
+ end
228
247
  else
229
248
  failure(error: Result::Failure::UnknownPairError.new(relation: relation),
230
249
  trace: trace)
@@ -575,51 +594,6 @@ module Steep
575
594
  end
576
595
  end
577
596
 
578
- def module_type(type)
579
- case type.name
580
- when TypeName::Instance
581
- case
582
- when builder.signatures.class_name?(type.name.name)
583
- type.class_type(constructor: nil)
584
- when builder.signatures.module_name?(type.name.name)
585
- type.module_type
586
- end
587
- else
588
- nil
589
- end
590
- end
591
-
592
- def compact(types)
593
- types = types.reject {|type| type.is_a?(AST::Types::Any) }
594
-
595
- if types.empty?
596
- [AST::Types::Any.new]
597
- else
598
- compact0(types)
599
- end
600
- end
601
-
602
- def compact0(types)
603
- if types.size == 1
604
- types
605
- else
606
- type, *types_ = types
607
- compacted = compact0(types_)
608
- compacted.flat_map do |type_|
609
- case
610
- when type == type_
611
- [type]
612
- when check(Relation.new(sub_type: type_, super_type: type), constraints: Constraints.empty).success?
613
- [type]
614
- when check(Relation.new(sub_type: type, super_type: type_), constraints: Constraints.empty).success?
615
- [type_]
616
- else
617
- [type, type_]
618
- end
619
- end.uniq
620
- end
621
- end
622
-
623
597
  class CannotResolveError < StandardError
624
598
  attr_reader :type
625
599
 
@@ -892,7 +866,7 @@ module Steep
892
866
  array_interface
893
867
  end
894
868
 
895
- when AST::Types::Hash
869
+ when AST::Types::Record
896
870
  yield_self do
897
871
  key_type = AST::Types::Union.build(types: type.elements.keys.map {|val| AST::Types::Literal.new(value: val) })
898
872
  value_type = AST::Types::Union.build(types: type.elements.values)
@@ -1555,13 +1555,13 @@ module Steep
1555
1555
  end
1556
1556
  end
1557
1557
 
1558
- def check(node, type)
1558
+ def check(node, type, constraints: Subtyping::Constraints.empty)
1559
1559
  type_ = synthesize(node, hint: type)
1560
1560
 
1561
1561
  result = checker.check(
1562
1562
  Subtyping::Relation.new(sub_type: type_,
1563
1563
  super_type: type),
1564
- constraints: Subtyping::Constraints.empty
1564
+ constraints: constraints
1565
1565
  )
1566
1566
  if result.failure?
1567
1567
  yield(type, type_, result)
@@ -1881,24 +1881,36 @@ module Steep
1881
1881
  def type_method_call(node, receiver_type:, method:, args:, block_params:, block_body:)
1882
1882
  results = method.types.map do |method_type|
1883
1883
  Steep.logger.tagged method_type.location&.source do
1884
- child_typing = typing.new_child
1885
- arg_pairs = args.zip(method_type.params, method_type.block&.type)
1886
-
1887
- type_or_error = if arg_pairs
1888
- self.with_new_typing(child_typing).try_method_type(
1889
- node,
1890
- receiver_type: receiver_type,
1891
- method_type: method_type,
1892
- args: args,
1893
- arg_pairs: arg_pairs,
1894
- block_params: block_params,
1895
- block_body: block_body,
1896
- child_typing: child_typing
1897
- )
1898
- else
1899
- Steep.logger.debug(node.inspect)
1900
- Errors::IncompatibleArguments.new(node: node, receiver_type: receiver_type, method_type: method_type)
1901
- end
1884
+ zips = args.zips(method_type.params, method_type.block&.type)
1885
+
1886
+ type_or_error, child_typing = if zips.any?
1887
+ zips.map do |arg_pairs|
1888
+ child_typing = typing.new_child
1889
+
1890
+ result = self.with_new_typing(child_typing).try_method_type(
1891
+ node,
1892
+ receiver_type: receiver_type,
1893
+ method_type: method_type,
1894
+ args: args,
1895
+ arg_pairs: arg_pairs,
1896
+ block_params: block_params,
1897
+ block_body: block_body,
1898
+ child_typing: child_typing
1899
+ )
1900
+
1901
+ unless result.is_a?(Errors::Base)
1902
+ break [[result, child_typing]]
1903
+ end
1904
+
1905
+ [result, child_typing]
1906
+ end.first
1907
+ else
1908
+ Steep.logger.debug(node.inspect)
1909
+ [
1910
+ Errors::IncompatibleArguments.new(node: node, receiver_type: receiver_type, method_type: method_type),
1911
+ typing.new_child
1912
+ ]
1913
+ end
1902
1914
 
1903
1915
  [child_typing, type_or_error, method_type]
1904
1916
  end
@@ -1922,6 +1934,139 @@ module Steep
1922
1934
  end
1923
1935
  end
1924
1936
 
1937
+ def check_keyword_arg(receiver_type:, node:, method_type:, constraints:)
1938
+ params = method_type.params
1939
+
1940
+ case node.type
1941
+ when :hash
1942
+ keyword_hash_type = AST::Builtin::Hash.instance_type(AST::Builtin::Symbol.instance_type,
1943
+ AST::Builtin.any_type)
1944
+ typing.add_typing node, keyword_hash_type
1945
+
1946
+ given_keys = Set.new()
1947
+
1948
+ node.children.each do |element|
1949
+ case element.type
1950
+ when :pair
1951
+ key_node, value_node = element.children
1952
+
1953
+ case key_node.type
1954
+ when :sym
1955
+ key_symbol = key_node.children[0]
1956
+ keyword_type = case
1957
+ when params.required_keywords.key?(key_symbol)
1958
+ params.required_keywords[key_symbol]
1959
+ when params.optional_keywords.key?(key_symbol)
1960
+ AST::Types::Union.build(
1961
+ types: [params.optional_keywords[key_symbol],
1962
+ AST::Builtin.nil_type]
1963
+ )
1964
+ when params.rest_keywords
1965
+ params.rest_keywords
1966
+ end
1967
+
1968
+ typing.add_typing key_node, AST::Builtin::Symbol.instance_type
1969
+
1970
+ given_keys << key_symbol
1971
+
1972
+ if keyword_type
1973
+ check(value_node, keyword_type, constraints: constraints) do |expected, actual, result|
1974
+ return Errors::IncompatibleAssignment.new(
1975
+ node: value_node,
1976
+ lhs_type: expected,
1977
+ rhs_type: actual,
1978
+ result: result
1979
+ )
1980
+ end
1981
+ else
1982
+ synthesize(value_node)
1983
+ end
1984
+
1985
+ else
1986
+ check(key_node, AST::Builtin::Symbol.instance_type, constraints: constraints) do |expected, actual, result|
1987
+ return Errors::IncompatibleAssignment.new(
1988
+ node: key_node,
1989
+ lhs_type: expected,
1990
+ rhs_type: actual,
1991
+ result: result
1992
+ )
1993
+ end
1994
+ end
1995
+
1996
+ when :kwsplat
1997
+ Steep.logger.warn("Keyword arg with kwsplat(**) node are not supported.")
1998
+
1999
+ check(element.children[0], keyword_hash_type, constraints: constraints) do |expected, actual, result|
2000
+ return Errors::IncompatibleAssignment.new(
2001
+ node: node,
2002
+ lhs_type: expected,
2003
+ rhs_type: actual,
2004
+ result: result
2005
+ )
2006
+ end
2007
+
2008
+ given_keys = true
2009
+ end
2010
+ end
2011
+
2012
+ case given_keys
2013
+ when Set
2014
+ missing_keywords = Set.new(params.required_keywords.keys) - given_keys
2015
+ unless missing_keywords.empty?
2016
+ return Errors::MissingKeyword.new(node: node,
2017
+ missing_keywords: missing_keywords)
2018
+ end
2019
+
2020
+ extra_keywords = given_keys - Set.new(params.required_keywords.keys) - Set.new(params.optional_keywords.keys)
2021
+ if extra_keywords.any? && !params.rest_keywords
2022
+ return Errors::UnexpectedKeyword.new(node: node,
2023
+ unexpected_keywords: extra_keywords)
2024
+ end
2025
+ end
2026
+ else
2027
+ if params.rest_keywords
2028
+ Steep.logger.warn("Method call with rest keywords type is detected. Rough approximation to be improved.")
2029
+
2030
+ value_types = params.required_keywords.values +
2031
+ params.optional_keywords.values.map {|type| AST::Types::Union.build(types: [type, AST::Builtin.nil_type]) } +
2032
+ [params.rest_keywords]
2033
+
2034
+ hash_type = AST::Builtin::Hash.instance_type(
2035
+ AST::Builtin::Symbol.instance_type,
2036
+ AST::Types::Union.build(types: value_types,
2037
+ location: method_type.location)
2038
+ )
2039
+ else
2040
+ hash_elements = params.required_keywords.merge(
2041
+ method_type.optional_keywords.transform_values do |type|
2042
+ AST::Types::Union.build(types: [type, AST::Builtin.nil_type],
2043
+ location: method_type.location)
2044
+ end
2045
+ )
2046
+
2047
+ hash_type = AST::Types::Record.new(elements: hash_elements)
2048
+ end
2049
+
2050
+ node_type = synthesize(node, hint: hash_type)
2051
+
2052
+ relation = Subtyping::Relation.new(
2053
+ sub_type: node_type,
2054
+ super_type: hash_type
2055
+ )
2056
+
2057
+ checker.check(relation, constraints: constraints).else do
2058
+ return Errors::ArgumentTypeMismatch.new(
2059
+ node: node,
2060
+ receiver_type: receiver_type,
2061
+ expected: relation.super_type,
2062
+ actual: relation.sub_type
2063
+ )
2064
+ end
2065
+ end
2066
+
2067
+ nil
2068
+ end
2069
+
1925
2070
  def try_method_type(node, receiver_type:, method_type:, args:, arg_pairs:, block_params:, block_body:, child_typing:)
1926
2071
  fresh_types = method_type.type_params.map {|x| AST::Types::Var.fresh(x) }
1927
2072
  fresh_vars = Set.new(fresh_types.map(&:name))
@@ -1945,28 +2090,43 @@ module Steep
1945
2090
  variance = Subtyping::VariableVariance.from_method_type(method_type)
1946
2091
  occurence = Subtyping::VariableOccurence.from_method_type(method_type)
1947
2092
 
1948
- arg_pairs.each do |(arg_node, param_type)|
1949
- param_type = param_type.subst(instantiation)
2093
+ arg_pairs.each do |pair|
2094
+ case pair
2095
+ when Array
2096
+ (arg_node, param_type) = pair
1950
2097
 
1951
- arg_type = if arg_node.type == :splat
1952
- type = construction.synthesize(arg_node.children[0])
1953
- child_typing.add_typing(arg_node, type)
1954
- else
1955
- construction.synthesize(arg_node, hint: param_type)
1956
- end
2098
+ param_type = param_type.subst(instantiation)
1957
2099
 
1958
- relation = Subtyping::Relation.new(
1959
- sub_type: arg_type,
1960
- super_type: param_type
1961
- )
2100
+ arg_type = if arg_node.type == :splat
2101
+ type = construction.synthesize(arg_node.children[0])
2102
+ child_typing.add_typing(arg_node, type)
2103
+ else
2104
+ construction.synthesize(arg_node, hint: param_type)
2105
+ end
1962
2106
 
1963
- checker.check(relation, constraints: constraints).else do |result|
1964
- return Errors::ArgumentTypeMismatch.new(
1965
- node: arg_node,
1966
- receiver_type: receiver_type,
1967
- expected: relation.super_type,
1968
- actual: relation.sub_type
2107
+ relation = Subtyping::Relation.new(
2108
+ sub_type: arg_type,
2109
+ super_type: param_type
1969
2110
  )
2111
+
2112
+ checker.check(relation, constraints: constraints).else do |result|
2113
+ return Errors::ArgumentTypeMismatch.new(
2114
+ node: arg_node,
2115
+ receiver_type: receiver_type,
2116
+ expected: relation.super_type,
2117
+ actual: relation.sub_type
2118
+ )
2119
+ end
2120
+ else
2121
+ # keyword
2122
+ result = check_keyword_arg(receiver_type: receiver_type,
2123
+ node: pair,
2124
+ method_type: method_type,
2125
+ constraints: constraints)
2126
+
2127
+ if result.is_a?(Errors::Base)
2128
+ return result
2129
+ end
1970
2130
  end
1971
2131
  end
1972
2132
 
@@ -2496,10 +2656,10 @@ module Steep
2496
2656
  end
2497
2657
 
2498
2658
  def try_hash_type(node, hint)
2499
- if hint.is_a?(AST::Types::Hash)
2659
+ if hint.is_a?(AST::Types::Record)
2500
2660
  typing.new_child do |child_typing|
2501
2661
  new_construction = with_new_typing(child_typing)
2502
- elem_types = hint.elements.dup
2662
+ elements = {}
2503
2663
 
2504
2664
  each_child_node(node) do |child|
2505
2665
  case child.type
@@ -2513,22 +2673,28 @@ module Steep
2513
2673
  return nil
2514
2674
  end
2515
2675
 
2516
- return nil unless elem_types.key?(key_value)
2517
-
2518
2676
  key_hint = AST::Types::Literal.new(value: key_value)
2519
- value_hint = elem_types.delete(key_value)
2677
+ value_hint = hint.elements[key_value]
2678
+
2679
+ value_type = new_construction.synthesize(value, hint: value_hint)
2680
+
2681
+ if value_hint
2682
+ relation = Subtyping::Relation.new(sub_type: value_type, super_type: value_hint)
2683
+ if checker.check(relation, constraints: Subtyping::Constraints.empty).success?
2684
+ value_type = value_hint
2685
+ end
2686
+ end
2520
2687
 
2521
- new_construction.check(key, key_hint) { return nil }
2522
- new_construction.check(value, value_hint) { return nil }
2688
+ elements[key_value] = value_type
2523
2689
  else
2524
2690
  return nil
2525
2691
  end
2526
2692
  end
2527
2693
 
2528
- return nil unless elem_types.empty?
2529
-
2530
2694
  child_typing.save!
2531
- return typing.add_typing(node, hint)
2695
+
2696
+ hash = AST::Types::Record.new(elements: elements)
2697
+ typing.add_typing(node, hash)
2532
2698
  end
2533
2699
  end
2534
2700
  end
@@ -2,12 +2,10 @@ module Steep
2
2
  module TypeInference
3
3
  class SendArgs
4
4
  attr_reader :args
5
- attr_reader :kw_args
6
5
  attr_reader :block_pass_arg
7
6
 
8
- def initialize(args:, kw_args:, block_pass_arg:)
7
+ def initialize(args:, block_pass_arg:)
9
8
  @args = args
10
- @kw_args = kw_args
11
9
  @block_pass_arg = block_pass_arg
12
10
  end
13
11
 
@@ -15,7 +13,6 @@ module Steep
15
13
  nodes = nodes.dup
16
14
 
17
15
  args = []
18
- last_hash = nil
19
16
  block_pass_arg = nil
20
17
 
21
18
  if nodes.last&.type == :block_pass
@@ -23,38 +20,20 @@ module Steep
23
20
  end
24
21
 
25
22
  nodes.each do |node|
26
- if last_hash
27
- args << last_hash
28
- last_hash = nil
29
- end
30
-
31
- case node.type
32
- when :hash
33
- last_hash = node
34
- else
35
- args << node
36
- end
23
+ args << node
37
24
  end
38
25
 
39
- if last_hash
40
- unless kw_args?(last_hash)
41
- args << last_hash
42
- last_hash = nil
43
- end
44
- end
26
+ new(args: args, block_pass_arg: block_pass_arg)
27
+ end
45
28
 
46
- new(args: args, kw_args: last_hash, block_pass_arg: block_pass_arg)
29
+ def drop_first
30
+ raise "Cannot drop first from empty args" if args.empty?
31
+ self.class.new(args: args.drop(1), block_pass_arg: block_pass_arg)
47
32
  end
48
33
 
49
- def self.kw_args?(node)
50
- node.children.all? do |child|
51
- case child.type
52
- when :pair
53
- child.children[0].type == :sym
54
- when :kwsplat
55
- true
56
- end
57
- end
34
+ def drop_last
35
+ raise "Cannot drop last from empty args" if args.empty?
36
+ self.class.new(args: args.take(args.size - 1), block_pass_arg: block_pass_arg)
58
37
  end
59
38
 
60
39
  def each_keyword_arg
@@ -81,146 +60,135 @@ module Steep
81
60
  end
82
61
  end
83
62
 
84
- def zip(params, block)
85
- Set.new(
86
- [].tap do |pairs|
87
- consumed_keywords = Set.new
88
- rest_types = []
89
-
90
- params.required_keywords.each do |name, type|
91
- if (node = each_keyword_arg.find {|pair| pair.children[0].children[0] == name })
92
- pairs << [node.children[1], type]
93
- consumed_keywords << name
94
- else
95
- if kwsplat_nodes.any?
96
- rest_types << type
97
- else
98
- return
99
- end
100
- end
101
- end
63
+ def zips(params, block_type)
64
+ zip0(params, block_type).map do |pairs|
65
+ group_pairs(pairs)
66
+ end
67
+ end
102
68
 
103
- params.optional_keywords.each do |name, type|
104
- if (node = each_keyword_arg.find {|pair| pair.children[0].children[0] == name })
105
- pairs << [node.children[1], type]
106
- consumed_keywords << name
107
- else
108
- if kwsplat_nodes.any?
109
- rest_types << type
110
- end
111
- end
112
- end
69
+ def group_pairs(pairs)
70
+ types = pairs.each_with_object({}) do |pair, hash|
71
+ case pair
72
+ when Array
73
+ node, type = pair
74
+ hash[node.__id__] ||= [node]
75
+ hash[node.__id__] << type
76
+ else
77
+ hash[node.__id__] = pair
78
+ end
79
+ end
113
80
 
114
- if params.rest_keywords
115
- each_keyword_arg do |pair|
116
- name = pair.children[0].children[0]
117
- node = pair.children[1]
81
+ types.map do |_, array|
82
+ case array
83
+ when Array
84
+ node, *types_ = array
85
+ [node, AST::Types::Intersection.build(types: types_)]
86
+ else
87
+ array
88
+ end
89
+ end
90
+ end
118
91
 
119
- unless consumed_keywords.include?(name)
120
- pairs << [node, params.rest_keywords]
121
- end
122
- end
92
+ def add_pair(pairs, pair)
93
+ pairs.map do |ps|
94
+ if block_given?
95
+ yield ps, pair
96
+ else
97
+ [pair] + ps
98
+ end
99
+ end
100
+ end
123
101
 
124
- if kwsplat_nodes.any?
125
- pairs << [kw_args,
126
- AST::Builtin::Hash.instance_type(
127
- AST::Builtin::Symbol.instance_type,
128
- AST::Types::Union.build(types: rest_types + [params.rest_keywords])
129
- )]
130
- end
131
- end
102
+ def zip0(params, block_type)
103
+ case
104
+ when params.empty? && args.empty?
105
+ [[]]
132
106
 
133
- if params.has_keyword?
134
- if !params.rest_keywords
135
- if kwsplat_nodes.empty?
136
- if each_keyword_arg.any? {|pair| !consumed_keywords.include?(pair.children[0].children[0]) }
137
- return
138
- end
139
- end
140
- end
141
- end
107
+ when params.required.any?
108
+ if args.any?
109
+ first_arg = args[0]
142
110
 
143
- args = self.args.dup
144
- unless params.has_keyword?
145
- args << kw_args if kw_args
111
+ case first_arg.type
112
+ when :splat
113
+ []
114
+ else
115
+ rest = drop_first.zip0(params.drop_first, block_type)
116
+ pair = [first_arg, params.required[0]]
117
+
118
+ add_pair(rest, pair)
146
119
  end
120
+ else
121
+ []
122
+ end
147
123
 
148
- arg_types = {}
124
+ when params.has_keywords? && params.required_keywords.any?
125
+ if args.any?
126
+ rest = drop_last.zip0(params.without_keywords, block_type)
127
+ last_arg = args.last
149
128
 
150
- params.required.each do |param|
151
- if args.any?
152
- next_arg(args) do |arg|
153
- save_arg_type(arg, param, arg_types)
154
- end
155
- consume_arg(args)
156
- else
157
- return
158
- end
159
- end
129
+ return [] if last_arg.type == :splat
160
130
 
161
- params.optional.each do |param|
162
- next_arg(args) do |arg|
163
- save_arg_type(arg, param, arg_types)
164
- end
165
- consume_arg(args)
131
+ add_pair(rest, last_arg) do |ps, p|
132
+ ps + [p]
166
133
  end
134
+ else
135
+ []
136
+ end
167
137
 
168
- if args.any?
169
- if params.rest
170
- args.each do |arg|
171
- save_arg_type(arg, params.rest, arg_types)
172
- end
173
- else
174
- if args.none? {|arg| arg.type == :splat }
175
- return
176
- end
177
- end
178
- end
138
+ when params.has_keywords? && params.required_keywords.empty?
139
+ if args.any?
140
+ rest = drop_last.zip0(params.without_keywords, block_type)
141
+ last_arg = args.last
179
142
 
180
- (self.args + [kw_args].compact).each do |arg|
181
- types = arg_types[arg.object_id]
143
+ no_keyword = zip0(params.without_keywords, block_type)
182
144
 
183
- if types
184
- if arg.type == :splat
185
- type = AST::Builtin::Array.instance_type(AST::Types::Union.build(types: types))
186
- else
187
- type = AST::Types::Union.build(types: types)
188
- end
189
- pairs << [arg, type]
190
- end
145
+ if last_arg.type == :splat
146
+ no_keyword
147
+ else
148
+ add_pair(rest, last_arg) do |ps, p|
149
+ ps + [p]
150
+ end + no_keyword
191
151
  end
152
+ else
153
+ zip0(params.without_keywords, block_type)
192
154
  end
193
- )
194
- end
195
155
 
196
- def save_arg_type(arg, type, hash)
197
- if hash.key?(arg.object_id)
198
- types = hash[arg.object_id]
199
- else
200
- types = hash[arg.object_id] = []
201
- end
202
-
203
- types << type
204
- end
205
-
206
- def next_arg(args)
207
- if args.any?
208
- case args[0].type
209
- when :splat
210
- args.each do |arg|
211
- yield arg
156
+ when params.optional.any?
157
+ if args.any?
158
+ first_arg = args[0]
159
+
160
+ case first_arg.type
161
+ when :splat
162
+ rest = zip0(params.drop_first, block_type)
163
+ pair = [args[0], AST::Builtin::Array.instance_type(params.optional[0])]
164
+ else
165
+ rest = drop_first.zip0(params.drop_first, block_type)
166
+ pair = [args[0], params.optional[0]]
212
167
  end
168
+
169
+ add_pair(rest, pair)
213
170
  else
214
- yield args[0]
171
+ zip0(params.drop_first, block_type)
215
172
  end
216
- end
217
- end
218
173
 
219
- def consume_arg(args)
220
- if args.any?
221
- unless args[0].type == :splat
222
- args.shift
174
+ when params.rest
175
+ if args.any?
176
+ rest = drop_first.zip0(params, block_type)
177
+ first_arg = args[0]
178
+
179
+ case first_arg.type
180
+ when :splat
181
+ pair = [first_arg, AST::Builtin::Array.instance_type(params.rest)]
182
+ else
183
+ pair = [first_arg, params.rest]
184
+ end
185
+
186
+ add_pair(rest, pair)
187
+ else
188
+ zip0(params.drop_first, block_type)
223
189
  end
190
+ else
191
+ []
224
192
  end
225
193
  end
226
194
  end
data/lib/steep/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Steep
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.1"
3
3
  end
data/lib/steep.rb CHANGED
@@ -28,7 +28,7 @@ require "steep/ast/types/literal"
28
28
  require "steep/ast/types/boolean"
29
29
  require "steep/ast/types/tuple"
30
30
  require "steep/ast/types/proc"
31
- require "steep/ast/types/hash"
31
+ require "steep/ast/types/record"
32
32
  require "steep/ast/method_type"
33
33
  require "steep/ast/type_params"
34
34
  require "steep/ast/signature/class"
data/smoke/hash/d.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  params = { id: 30, name: "Matz" }
4
4
 
5
- # !expects IncompatibleAssignment: lhs_type={ :name => ::String, :id => ::Integer }, rhs_type=::Hash<::Symbol, ::String>
5
+ # !expects IncompatibleAssignment: lhs_type={ :name => ::String, :id => ::Integer }, rhs_type={ :id => ::String, :name => ::String, :email => ::String }
6
6
  params = { id: "30", name: "foo", email: "matsumoto@soutaro.com" }
data/smoke/hash/e.rb ADDED
@@ -0,0 +1,2 @@
1
+ # !expects NoMethodError: type=::Integer, method=fffffffffffff
2
+ Foo.new.get({ foo: 3 }).fffffffffffff
data/smoke/hash/e.rbi ADDED
@@ -0,0 +1,3 @@
1
+ class Foo
2
+ def get: <'a> ({ foo: 'a }) -> 'a
3
+ end
data/smoke/literal/b.rb CHANGED
@@ -5,5 +5,5 @@ l.foo(3)
5
5
  l.foo(4)
6
6
 
7
7
  l.bar(foo: :foo)
8
- # !expects ArgumentTypeMismatch: receiver=::LiteralMethods, expected=:foo, actual=::Symbol
8
+ # !expects IncompatibleAssignment: lhs_type=:foo, rhs_type=::Symbol
9
9
  l.bar(foo: :bar)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: steep
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soutaro Matsumoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-23 00:00:00.000000000 Z
11
+ date: 2018-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -169,7 +169,6 @@ files:
169
169
  - lib/steep/ast/types/boolean.rb
170
170
  - lib/steep/ast/types/bot.rb
171
171
  - lib/steep/ast/types/class.rb
172
- - lib/steep/ast/types/hash.rb
173
172
  - lib/steep/ast/types/helper.rb
174
173
  - lib/steep/ast/types/instance.rb
175
174
  - lib/steep/ast/types/intersection.rb
@@ -177,6 +176,7 @@ files:
177
176
  - lib/steep/ast/types/name.rb
178
177
  - lib/steep/ast/types/nil.rb
179
178
  - lib/steep/ast/types/proc.rb
179
+ - lib/steep/ast/types/record.rb
180
180
  - lib/steep/ast/types/self.rb
181
181
  - lib/steep/ast/types/top.rb
182
182
  - lib/steep/ast/types/tuple.rb
@@ -264,6 +264,8 @@ files:
264
264
  - smoke/hash/b.rb
265
265
  - smoke/hash/c.rb
266
266
  - smoke/hash/d.rb
267
+ - smoke/hash/e.rb
268
+ - smoke/hash/e.rbi
267
269
  - smoke/hello/hello.rb
268
270
  - smoke/hello/hello.rbi
269
271
  - smoke/if/a.rb