Skip to content

Conversation

@trinistr
Copy link
Contributor

@trinistr trinistr commented Nov 14, 2025

Closes #77.

Special handling for SortedSet itself would probably be wrong, as it may genuinely be available, possibly as a gem. Other constants may also raise in the future.

Per suggestion, I tried to reduce scope of rescue, but it made the method very annoying to look at. However, it was rescuing NoMethodError which is not obvious, so I added an extra return if the constant is not a Module.

@trinistr trinistr force-pushed the protect-against-sorted-set branch from f213c44 to 04727d6 Compare November 14, 2025 21:54
@trinistr trinistr marked this pull request as ready for review November 14, 2025 21:55
Comment on lines 70 to 76
return nil unless Module === const

filtered = @filter && EXCLUDED.include?(const.name)
return const if Module === const and !filtered
rescue NameError
return const unless filtered
rescue NameError, RuntimeError
# Either the constant doesn't exist or it is
# explicitly raising an error, like `SortedSet`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this can be reorganized using rescue-else to preserve nesting. Wow, that's a rare feature to remember.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option 1:

  def class_or_module(c)
    const = Object.const_get(c, false)
    return nil unless Module === const

    filtered = @filter && EXCLUDED.include?(const.name)
    return const unless filtered
  rescue NameError, RuntimeError
    # Either the constant doesn't exist or it is
    # explicitly raising an error, like `SortedSet`.
  end

Option 2:

  def class_or_module(c)
    const = Object.const_get(c, false)
  rescue NameError, RuntimeError
    # Either the constant doesn't exist or it is
    # explicitly raising an error, like `SortedSet`.
  else
    return nil unless Module === const

    filtered = @filter && EXCLUDED.include?(const.name)
    return const unless filtered
  end

Copy link
Member

@eregon eregon Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option 2 looks OK, but I'm not sure it works on Ruby 2.6 (which mspec still supports for now).
Also some early Ruby implementations might not support such a fancy method-level rescue/else, but this matters less for mkspec as it's meant primarily to be run on CRuby.
I would go with just being simple and explicit:

  def class_or_module(c)
    begin
      const = Object.const_get(c, false)
    rescue NameError, RuntimeError
      # Either the constant doesn't exist or it is
      # explicitly raising an error, like `SortedSet`.
    end
    return nil unless Module === const

    filtered = @filter && EXCLUDED.include?(const.name)
    return const unless filtered
  end

end

it "returns nil if accessing the constant raises RuntimeError" do
expect { NameMapSpecs::BadFile }.to raise_error(RuntimeError)
Copy link
Member

@eregon eregon Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you remove this line?
The issue is after triggering the autoload, given that it fails, the constant might be removed or be in a weird state, so it's better to only trigger the autoload once from class_or_module->const_get.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely understand the reservation but I'm reluctant to remove it. Without it the test can easily regress to any of the other cases which return nil.

Actually, there is a test for this exact situtation, so a broken constant would fail the spec.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely understand the reservation but I'm reluctant to remove it. Without it the test can easily regress to any of the other cases which return nil.

How could it? The autoload clearly raises the error.

Actually, there is a test for this exact situtation, so a broken constant would fail the spec.

Yes, but that has been the subject of discussions on the CRuby tracker and there have been changes in this area, there used to be "undefined constants" for example.

I suppose one way to meet both of our concerns is to just test both cases, I'll add a second test which doesn't trigger the autoload first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could it? The autoload clearly raises the error.

I'm worried that in the case someone changes the tests, it would be very easy to break this one accidentally. A typo, removing the autoload, or removing raise in the file would all cause the method to return nil, so without the sanity check the test would pass.

Yes, but that has been the subject of discussions on the CRuby tracker and there have been changes in this area, there used to be "undefined constants" for example.

Oh, that's concerning. To be fair, I also intuitively expected that the constant would be in a strange state, but current behavior is clearly superior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without the sanity check the test would pass.

We could have something like expect(NameMapSpecs.autoload?(:BadFile).to be_a(String) or so, that checks the autoload is registered at least and there is no typo in the constant name.

@trinistr trinistr force-pushed the protect-against-sorted-set branch from 04727d6 to 7b0a156 Compare November 16, 2025 09:52
@eregon eregon merged commit 3efc861 into ruby:master Nov 17, 2025
10 checks passed
@eregon
Copy link
Member

eregon commented Nov 17, 2025

Thank you for the PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mkspec no longer runs without RubyGems (SortedSet causes an error)

2 participants