ConstantResolver resolves a scope's own name over a same-named nested constant
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 72/100
Hướng nghiên cứu
Start with lib/rbs/resolver/constant_resolver.rb, especially load_context_constants and the three methods that merge ancestor, lexical, and scope constants. Read test/rbs/resolver/constant_resolver_test.rb, including test_reference_constant_context_self, then run that test file. Done means the reproduction resolves C to ::M::C::C, while the existing self-reference test still passes.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
RBS::Resolver::ConstantResolver#resolve resolves the innermost scope's own name to the scope itself, even when that scope has a nested constant of the same name. Ruby resolves the name to the nested constant, so type checkers built on the resolver report a false UnknownConstant.
Ruby behavior
module M
class C
class C
CONST = 1
end
def run
C::CONST
end
end
end
p M::C.new.run #=> 1
Inside def run, C refers to M::C::C: Ruby searches the constant table of each lexical scope from the innermost outward, and M::C's table has C.
Steep behavior
Steep 2.1.0 reports an error for the same code (checked with inline: true in the Steepfile, so the Ruby file also provides the declarations):
sample.rb:8:9: [error] Cannot find the declaration of constant: `CONST`
Renaming the inner class to D (and C::CONST to D::CONST) makes the error go away, so the name collision is the only trigger.
Reproduction with rbs alone
require "rbs"
source = <<~RBS
module M
class C
class C
CONST: Integer
end
end
end
RBS
loader = RBS::EnvironmentLoader.new
env = RBS::Environment.from_loader(loader)
buffer = RBS::Buffer.new(name: "a.rbs", content: source)
_, directives, decls = RBS::Parser.parse_signature(buffer)
env.add_source(RBS::Source::RBS.new(buffer, directives, decls))
env = env.resolve_type_names
resolver = RBS::Resolver::ConstantResolver.new(builder: RBS::DefinitionBuilder.new(env: env))
# Lexical context of a method body inside `module M; class C; ...; end; end`
context = [[nil, RBS::TypeName.parse("::M")], RBS::TypeName.parse("::M::C")]
puts resolver.resolve(:C, context: context).name
Expected: ::M::C::C
Actual: ::M::C
Cause
load_context_constants merges three layers into one hash, and a later merge wins:
constants_from_ancestors(last, constants: consts) # 1
constants_from_context(context, constants: consts) # 2
constants_itself(context, constants: consts) # 3
After step 2 the hash already holds the right answer, because the children of ::M::C contain C => ::M::C::C. Step 3 then overwrites it with C => ::M::C:
Possible fix
Run constants_itself between the ancestor layer and the lexical layer:
def load_context_constants(context)
consts = {}
if last = context&.[](1)
constants_from_ancestors(last, constants: consts)
else
constants_from_ancestors(BuiltinNames::Object.name, constants: consts)
end
constants_itself(context, constants: consts)
constants_from_context(context, constants: consts) or return
context_constants_cache[context] = consts
end
With this order:
- The reproduction above resolves
Cto::M::C::C. - The scope's own name still wins over an ancestor constant.
test_reference_constant_context_self(class Foo::Barwith a top-level::BarresolvesBarto::Foo::Bar) keeps passing. test/rbs/resolver/constant_resolver_test.rbpasses, and the rest oftest/rbsshows no new failures.
I checked this by prepending the patched method onto ConstantResolver, not by editing the gem.
A simpler change, constants[typename.name] ||= constant in constants_itself, fixes the reproduction but breaks test_reference_constant_context_self, because the top-level ::Bar comes in through the ancestor layer before step 3 runs.
Note: the compact-style case differs from Ruby too
test_reference_constant_context_self asserts behavior that Ruby does not have:
class Foo; end
class Bar; end
class Foo::Bar
p Bar #=> Bar (the top-level ::Bar, not Foo::Bar)
end
module N; end
class N::D
D #=> NameError: uninitialized constant N::D::D
end
In Ruby, the scope's own name is visible only through an enclosing lexical scope's constant table. That table is already covered by constants_from_context. So constants_itself takes effect only in the compact style (class Foo::Bar), and there it differs from Ruby.
The fix above keeps that compact-style behavior as it is. Whether it is intended is a separate question, and I am not proposing to change it here.
The new order also changes one more case, again toward Ruby: a compact-style class inside a module that has a same-named constant.
class Foo; end
module X
class Bar; end
class Foo::Bar
p Bar #=> X::Bar
end
end
With context [[nil, ::X], ::Foo::Bar], resolve(:Bar) returns ::Foo::Bar today and ::X::Bar with the new order.
Environment
- rbs: master (8575f87dab1605320e257830505b0db49b4367ab). The line above is the same on the current master.
- Steep: 2.1.0
- Ruby: 4.0.6
- Ngôn ngữ chính
- Ruby
- Star
- 2.2k
- Fork
- 258
- Merge trung bình
- 1 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 52
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của ruby/rbs
-
InlineParser: `@rbs @ivar` annotation is reported as unused unless it is the first thing in the class bodyCó thể đã có người làm @ksss đã nhận 15 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 57/100
Maintainer thường phản hồi trong vòng 1 ngày
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
voxpupuli/puppet-wireguard#151 ·
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug f:search
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
feature
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 60/100
manyfold3d/manyfold#7346 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug javascript smallFix update
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
puzzle/decidim-zuerich#636 ·