From 379a078397c5a36de7901cc45b88ca66cb2ebb78 Mon Sep 17 00:00:00 2001 From: Joel Drapper Date: Thu, 16 Oct 2025 22:33:10 +0100 Subject: [PATCH 1/2] Binding assertions --- lib/literal.rb | 46 +++++++++++++++++++++++++++++++++++++++++ test/assert.test.rb | 50 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 test/assert.test.rb diff --git a/lib/literal.rb b/lib/literal.rb index 3aeb6f5..381ea4b 100644 --- a/lib/literal.rb +++ b/lib/literal.rb @@ -19,6 +19,52 @@ module Literal loader.setup end + CLASS_METHOD = Kernel.instance_method(:class) + CLASS_VARIABLE_GET_METHOD = Module.instance_method(:class_variable_get) + INSTANCE_VARIABLE_GET_METHOD = Kernel.instance_method(:instance_variable_get) + + module BindingAssert + def assert(...) + Literal.assert(self, ...) + end + end + + ::Binding.include(Literal::BindingAssert) + + + def self.assert(bind, **kwargs) + kwargs.each do |name, type| + string_name = (Symbol === name) ? name.name : name + first_byte = string_name.getbyte(0) + + if first_byte == 64 # @foo + if string_name.getbyte(1) == 64 # @@foo + value = CLASS_VARIABLE_GET_METHOD.bind_call( + CLASS_METHOD.bind_call(bind.receiver), name + ) + else # @foo + value = INSTANCE_VARIABLE_GET_METHOD.bind_call(bind.receiver, name) + end + elsif first_byte == 36 # $foo + raise if string_name.match?(/[\s\.]/) + value = eval(string_name) + else # foo + value = bind.local_variable_get(name) + end + + unless type === value + raise ::TypeError.new(<<~MESSAGE) + Assertion failed! + + #{name} + Expected: #{type.inspect} + Actual (#{value.class}): #{value.inspect} + MESSAGE + end + end + end + + def self.Value(*args, **kwargs, &block) value_class = Class.new(Literal::Value) diff --git a/test/assert.test.rb b/test/assert.test.rb new file mode 100644 index 0000000..4b09f0e --- /dev/null +++ b/test/assert.test.rb @@ -0,0 +1,50 @@ +$a = 1 + +test "assert with local variable" do + refute_raises do + a = 1 + b = "Hello" + + binding.assert(a: Integer, b: String) + binding.assert("a" => Integer, "b" => String) + end + + assert_raises TypeError do + a = 1 + binding.assert(a: String) + end +end + +test "assert with global variable" do + refute_raises do + binding.assert("$a": Integer) + end + + assert_raises TypeError do + binding.assert("$a": String) + end +end + +test "assert with instance variable" do + refute_raises do + @a = 1 + binding.assert("@a": Integer) + end + + assert_raises TypeError do + @a = 1 + binding.assert("@a": String) + end +end + +test "assert with class variable" do + refute_raises do + @@a = 1 + binding.assert("@@a": Integer) + end + + assert_raises TypeError do + @@a = 1 + binding.assert("@@a": String) + end +end From 2b6d3701371619196742bbe4d5689e3d277f2427 Mon Sep 17 00:00:00 2001 From: Joel Drapper Date: Sun, 19 Oct 2025 21:56:23 +0100 Subject: [PATCH 2/2] Update lib/literal.rb Co-authored-by: Stephen Margheim --- lib/literal.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/literal.rb b/lib/literal.rb index 381ea4b..15f4956 100644 --- a/lib/literal.rb +++ b/lib/literal.rb @@ -24,9 +24,7 @@ module Literal INSTANCE_VARIABLE_GET_METHOD = Kernel.instance_method(:instance_variable_get) module BindingAssert - def assert(...) - Literal.assert(self, ...) - end + def assert(...) = Literal.assert(self, ...) end ::Binding.include(Literal::BindingAssert)