Skip to content

Commit 22b51b3

Browse files
joeldrapperclaude
andcommitted
Add opt-in undefined global via require "literal/kernel"
Requiring "literal/kernel" defines `Kernel#undefined`, so a bare `undefined` returns `Literal::Undefined` anywhere in the application. It is opt-in because the trade-off — a typo'd bare `undefined` stops raising `NameError` and starts flowing a truthy sentinel — belongs to the application, not the library. Literal never loads it internally, which subprocess tests enforce. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent dddf188 commit 22b51b3

3 files changed

Lines changed: 41 additions & 0 deletions

File tree

lib/literal.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Literal
77
OBJECT_ID = BasicObject.instance_method(:__id__)
88

99
Loader = Zeitwerk::Loader.for_gem.tap do |loader|
10+
loader.ignore("#{__dir__}/literal/kernel.rb")
1011
loader.ignore("#{__dir__}/literal/rails")
1112
loader.ignore("#{__dir__}/literal/railtie.rb")
1213
loader.ignore("#{__dir__}/ruby_lsp")

lib/literal/kernel.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
require "literal"
4+
5+
# Opt-in global sugar, loaded with `require "literal/kernel"`: a bare
6+
# `undefined` anywhere in the application returns `Literal::Undefined`.
7+
#
8+
# Literal never loads this itself — a typo'd bare `undefined` would otherwise
9+
# stop raising NameError and start flowing a truthy sentinel through the
10+
# program, and that trade-off belongs to the application, not the library.
11+
module Kernel
12+
private def undefined
13+
Literal::Undefined
14+
end
15+
end

test/kernel.test.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# frozen_string_literal: true
2+
3+
# These assertions run in subprocesses: requiring "literal/kernel" in the test
4+
# process would define Kernel#undefined for the whole suite, masking any
5+
# accidental internal dependency on the opt-in global.
6+
7+
LIB = File.expand_path("../lib", __dir__)
8+
9+
test "requiring literal does not define a global undefined" do
10+
assert system(
11+
RbConfig.ruby, "-I", LIB, "-r", "literal",
12+
"-e", "exit !Object.private_method_defined?(:undefined)"
13+
) do
14+
"Expected requiring literal alone not to define Kernel#undefined."
15+
end
16+
end
17+
18+
test "requiring literal/kernel defines a global undefined" do
19+
assert system(
20+
RbConfig.ruby, "-I", LIB, "-r", "literal/kernel",
21+
"-e", "exit undefined.equal?(Literal::Undefined)"
22+
) do
23+
"Expected requiring literal/kernel to make a bare `undefined` return Literal::Undefined."
24+
end
25+
end

0 commit comments

Comments
 (0)