Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

ConstantResolver resolves a scope's own name over a same-named nested constant

Abierto
#3,224 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
72/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
ruby
Área
backend

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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:

https://github.com/ruby/rbs/blob/8575f87dab1605320e257830505b0db49b4367ab/lib/rbs/resolver/constant_resolver.rb#L213

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 C to ::M::C::C.
  • The scope's own name still wins over an ancestor constant. test_reference_constant_context_self (class Foo::Bar with a top-level ::Bar resolves Bar to ::Foo::Bar) keeps passing.
  • test/rbs/resolver/constant_resolver_test.rb passes, and the rest of test/rbs shows 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
Lenguaje dominante
Ruby
Estrellas
2.2k
Forks
258
Merge medio
1 d 4 h
PR fusionados (30 d)
52

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de ruby/rbs

Todos los issues de ruby/rbs

Issues similares

Más issues de Ruby

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.