diff --git a/lib/steep/ast/types/factory.rb b/lib/steep/ast/types/factory.rb index 330cb09e..ee10e992 100644 --- a/lib/steep/ast/types/factory.rb +++ b/lib/steep/ast/types/factory.rb @@ -245,7 +245,7 @@ def function_1(func) required_positionals: params.required.map {|type| RBS::Types::Function::Param.new(name: nil, type: type_1(type)) }, optional_positionals: params.optional.map {|type| RBS::Types::Function::Param.new(name: nil, type: type_1(type)) }, rest_positionals: params.rest&.yield_self {|type| RBS::Types::Function::Param.new(name: nil, type: type_1(type)) }, - trailing_positionals: [], + trailing_positionals: params.trailing.map {|type| RBS::Types::Function::Param.new(name: nil, type: type_1(type)) }, required_keywords: params.required_keywords.transform_values {|type| RBS::Types::Function::Param.new(name: nil, type: type_1(type)) }, optional_keywords: params.optional_keywords.transform_values {|type| RBS::Types::Function::Param.new(name: nil, type: type_1(type)) }, rest_keywords: params.rest_keywords&.yield_self {|type| RBS::Types::Function::Param.new(name: nil, type: type_1(type)) }, @@ -263,6 +263,7 @@ def params(type) required: type.required_positionals.map {|param| type(param.type) }, optional: type.optional_positionals.map {|param| type(param.type) }, rest: type.rest_positionals&.yield_self {|param| type(param.type) }, + trailing: type.trailing_positionals.map {|param| type(param.type) }, required_keywords: type.required_keywords.transform_values {|param| type(param.type) }, optional_keywords: type.optional_keywords.transform_values {|param| type(param.type) }, rest_keywords: type.rest_keywords&.yield_self {|param| type(param.type) } diff --git a/lib/steep/interface/function.rb b/lib/steep/interface/function.rb index 8068ad48..1062d82a 100644 --- a/lib/steep/interface/function.rb +++ b/lib/steep/interface/function.rb @@ -145,8 +145,11 @@ def size 1 + (tail&.size || 0) end - def self.build(required:, optional:, rest:) - params = rest ? self.rest(rest) : nil + def self.build(required:, optional:, rest:, trailing:) + # @type var params: Interface::Function::Params::PositionalParams? + params = nil + params = trailing.reverse_each.inject(params) {|params, type| self.required(type, params) } + params = self.rest(rest, params) if rest params = optional.reverse_each.inject(params) {|params, type| self.optional(type, params) } params = required.reverse_each.inject(params) {|params, type| self.required(type, params) } @@ -769,11 +772,27 @@ def rest nil end + def trailing + array = [] #: Array[AST::Types::t] + trailing = false + + positional_params&.each do |param| + case param + when PositionalParams::Required + array << param.type if trailing + when PositionalParams::Optional, PositionalParams::Rest + trailing = true + end + end + + array + end + attr_reader :positional_params attr_reader :keyword_params - def self.build(required: [], optional: [], rest: nil, required_keywords: {}, optional_keywords: {}, rest_keywords: nil) - positional_params = PositionalParams.build(required: required, optional: optional, rest: rest) + def self.build(required: [], optional: [], rest: nil, trailing: [], required_keywords: {}, optional_keywords: {}, rest_keywords: nil) + positional_params = PositionalParams.build(required: required, optional: optional, rest: rest, trailing: trailing) keyword_params = KeywordParams.new(requireds: required_keywords, optionals: optional_keywords, rest: rest_keywords) new(positional_params: positional_params, keyword_params: keyword_params) end diff --git a/sig/steep/interface/function.rbs b/sig/steep/interface/function.rbs index 85ba9f96..2c9762d5 100644 --- a/sig/steep/interface/function.rbs +++ b/sig/steep/interface/function.rbs @@ -76,7 +76,7 @@ module Steep def size: () -> Integer - def self.build: (required: Array[AST::Types::t], optional: Array[AST::Types::t], rest: AST::Types::t?) -> PositionalParams? + def self.build: (required: Array[AST::Types::t], optional: Array[AST::Types::t], rest: AST::Types::t?, trailing: Array[AST::Types::t]) -> PositionalParams? extend Utils @@ -145,11 +145,13 @@ module Steep %a{pure} def rest: () -> AST::Types::t? + def trailing: () -> Array[AST::Types::t] + attr_reader positional_params: PositionalParams? attr_reader keyword_params: KeywordParams - def self.build: (?required: Array[AST::Types::t], ?optional: Array[AST::Types::t], ?rest: AST::Types::t?, ?required_keywords: ::Hash[Symbol, AST::Types::t], ?optional_keywords: ::Hash[Symbol, AST::Types::t], ?rest_keywords: AST::Types::t?) -> Params + def self.build: (?required: Array[AST::Types::t], ?optional: Array[AST::Types::t], ?rest: AST::Types::t?, ?trailing: Array[AST::Types::t], ?required_keywords: ::Hash[Symbol, AST::Types::t], ?optional_keywords: ::Hash[Symbol, AST::Types::t], ?rest_keywords: AST::Types::t?) -> Params def initialize: (positional_params: PositionalParams?, keyword_params: KeywordParams) -> void diff --git a/test/type_check_test.rb b/test/type_check_test.rb index 3365f4da..dcadd2f9 100644 --- a/test/type_check_test.rb +++ b/test/type_check_test.rb @@ -2054,6 +2054,32 @@ def f(x, y, z) ) end + def test_method_def__trailing + run_type_check_test( + signatures: { + "a.rbs" => <<~RBS + class Hello + def f: (?untyped a, untyped b) -> void + end + RBS + }, + code: { + "a.rb" => <<~RUBY + class Hello + def f(a = nil, b) + a.to_i + b + end + end + RUBY + }, + expectations: <<~YAML + --- + - file: a.rb + diagnostics: [] + YAML + ) + end + def test_method_yield__untyped run_type_check_test( signatures: {