diff --git a/lib/syntax_tree/yarv/compiler.rb b/lib/syntax_tree/yarv/compiler.rb index bd20bc19..b0afcc99 100644 --- a/lib/syntax_tree/yarv/compiler.rb +++ b/lib/syntax_tree/yarv/compiler.rb @@ -875,8 +875,7 @@ def visit_defined(node) when Ident iseq.putobject("local-variable") when IVar - iseq.putnil - iseq.defined(Defined::TYPE_IVAR, name, "instance-variable") + iseq.defined_ivar(name, iseq.inline_storage, "instance-variable") when Kw case name when :false diff --git a/lib/syntax_tree/yarv/instruction_sequence.rb b/lib/syntax_tree/yarv/instruction_sequence.rb index 5aaaef44..2d89e052 100644 --- a/lib/syntax_tree/yarv/instruction_sequence.rb +++ b/lib/syntax_tree/yarv/instruction_sequence.rb @@ -673,12 +673,21 @@ def concatstrings(number) push(ConcatStrings.new(number)) end + def defineclass(name, class_iseq, flags) + push(DefineClass.new(name, class_iseq, flags)) + end + def defined(type, name, message) push(Defined.new(type, name, message)) end - def defineclass(name, class_iseq, flags) - push(DefineClass.new(name, class_iseq, flags)) + def defined_ivar(name, cache, message) + if RUBY_VERSION < "3.3" + push(PutNil.new) + push(Defined.new(Defined::TYPE_IVAR, name, message)) + else + push(DefinedIVar.new(name, cache, message)) + end end def definemethod(name, method_iseq) @@ -1058,6 +1067,8 @@ def self.from(source, options = Compiler::Options.new, parent_iseq = nil) iseq.defineclass(opnds[0], from(opnds[1], options, iseq), opnds[2]) when :defined iseq.defined(opnds[0], opnds[1], opnds[2]) + when :defined_ivar + iseq.defined_ivar(opnds[0], opnds[1], opnds[2]) when :definemethod iseq.definemethod(opnds[0], from(opnds[1], options, iseq)) when :definesmethod diff --git a/lib/syntax_tree/yarv/instructions.rb b/lib/syntax_tree/yarv/instructions.rb index 38c80fde..cf83ddeb 100644 --- a/lib/syntax_tree/yarv/instructions.rb +++ b/lib/syntax_tree/yarv/instructions.rb @@ -994,6 +994,64 @@ def call(vm) end end + # ### Summary + # + # `defined_ivar` checks if an instance variable is defined. It is a + # specialization of the `defined` instruction. It accepts three arguments: + # the name of the instance variable, an inline cache, and the string that + # should be pushed onto the stack in the event that the instance variable + # is defined. + # + # ### Usage + # + # ~~~ruby + # defined?(@value) + # ~~~ + # + class DefinedIVar < Instruction + attr_reader :name, :cache, :message + + def initialize(name, cache, message) + @name = name + @cache = cache + @message = message + end + + def disasm(fmt) + fmt.instruction( + "defined_ivar", + [fmt.object(name), fmt.inline_storage(cache), fmt.object(message)] + ) + end + + def to_a(_iseq) + [:defined_ivar, name, cache, message] + end + + def deconstruct_keys(_keys) + { name: name, cache: cache, message: message } + end + + def ==(other) + other.is_a?(DefinedIVar) && other.name == name && + other.cache == cache && other.message == message + end + + def length + 4 + end + + def pushes + 1 + end + + def call(vm) + result = (message if vm.frame._self.instance_variable_defined?(name)) + + vm.push(result) + end + end + # ### Summary # # `definemethod` defines a method on the class of the current value of