Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions lib/prism/parse_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,17 @@ class Source
# be used instead of `new` and it will return either a `Source` or a
# specialized and more performant `ASCIISource` if no multibyte characters
# are present in the source code.
#--
#: (String source, ?Integer start_line, ?Array[Integer] offsets) -> Source
def self.for(source, start_line = 1, offsets = [])
#
# Note that if you are calling this method manually, you will need to supply
# the start_line and offsets parameters. start_line is the line number that
# the source starts on, which is typically 1 but can be different if this
# source is a subset of a larger source or if this is an eval. offsets is an
# array of byte offsets for the start of each line in the source code, which
# can be calculated by iterating through the source code and recording the
# byte offset whenever a newline character is encountered.
#--
#: (String source, Integer start_line, Array[Integer] offsets) -> Source
def self.for(source, start_line, offsets)
if source.ascii_only?
ASCIISource.new(source, start_line, offsets)
elsif source.encoding == Encoding::BINARY
Expand Down Expand Up @@ -55,8 +63,8 @@ def self.for(source, start_line = 1, offsets = [])

# Create a new source object with the given source code.
#--
#: (String source, ?Integer start_line, ?Array[Integer] offsets) -> void
def initialize(source, start_line = 1, offsets = [])
#: (String source, Integer start_line, Array[Integer] offsets) -> void
def initialize(source, start_line, offsets)
@source = source
@start_line = start_line # set after parsing is done
@offsets = offsets # set after parsing is done
Expand Down
47 changes: 38 additions & 9 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -21605,6 +21605,26 @@ pm_call_node_command_p(const pm_call_node_t *node) {
);
}

/**
* Determine if a given write node has a command call as its right-hand side. We
* need this because command calls as the values of writes cannot be extended by
* infix operators.
*/
static inline bool
pm_write_node_command_p(const pm_node_t *node) {
pm_node_t *value;
switch (PM_NODE_TYPE(node)) {
case PM_CLASS_VARIABLE_WRITE_NODE: value = ((pm_class_variable_write_node_t *) node)->value; break;
case PM_CONSTANT_PATH_WRITE_NODE: value = ((pm_constant_path_write_node_t *) node)->value; break;
case PM_CONSTANT_WRITE_NODE: value = ((pm_constant_write_node_t *) node)->value; break;
case PM_GLOBAL_VARIABLE_WRITE_NODE: value = ((pm_global_variable_write_node_t *) node)->value; break;
case PM_INSTANCE_VARIABLE_WRITE_NODE: value = ((pm_instance_variable_write_node_t *) node)->value; break;
case PM_LOCAL_VARIABLE_WRITE_NODE: value = ((pm_local_variable_write_node_t *) node)->value; break;
default: return false;
}
return PM_NODE_TYPE_P(value, PM_CALL_NODE) && pm_call_node_command_p((pm_call_node_t *) value);
}

/**
* Parse an expression at the given point of the parser using the given binding
* power to parse subsequent chains. If this function finds a syntax error, it
Expand Down Expand Up @@ -21674,12 +21694,6 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
) {
node = parse_expression_infix(parser, node, binding_power, current_binding_powers.right, accepts_command_call, (uint16_t) (depth + 1));

if (context_terminator(parser->current_context->context, &parser->current)) {
// If this token terminates the current context, then we need to
// stop parsing the expression, as it has become a statement.
return node;
}

switch (PM_NODE_TYPE(node)) {
case PM_MULTI_WRITE_NODE:
// Multi-write nodes are statements, and cannot be followed by
Expand All @@ -21695,9 +21709,13 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
case PM_INSTANCE_VARIABLE_WRITE_NODE:
case PM_LOCAL_VARIABLE_WRITE_NODE:
// These expressions are statements, by virtue of the right-hand
// side of their write being an implicit array.
if (PM_NODE_FLAG_P(node, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY) && pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) {
return node;
// side of their write being an implicit array or a command call.
// This mirrors parse.y's behavior where `lhs = command_call`
// reduces to stmt (not expr), preventing and/or from following.
if (pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) {
if (PM_NODE_FLAG_P(node, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY) || pm_write_node_command_p(node)) {
return node;
}
}
break;
case PM_CALL_NODE:
Expand Down Expand Up @@ -21792,6 +21810,17 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc
break;
}
}

if (context_terminator(parser->current_context->context, &parser->current)) {
pm_binding_powers_t next_binding_powers = pm_binding_powers[parser->current.type];
if (
!next_binding_powers.binary ||
binding_power > next_binding_powers.left ||
(PM_NODE_TYPE_P(node, PM_CALL_NODE) && pm_call_node_command_p((pm_call_node_t *) node))
) {
return node;
}
}
}

return node;
Expand Down
6 changes: 3 additions & 3 deletions prism/templates/lib/prism/dsl.rb.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Prism
# The DSL module provides a set of methods that can be used to create prism
# nodes in a more concise manner. For example, instead of writing:
#
# source = Prism::Source.for("[1]")
# source = Prism::Source.for("[1]", 1, [])
#
# Prism::ArrayNode.new(
# source,
Expand Down Expand Up @@ -62,7 +62,7 @@ module Prism
#--
#: (String string) -> Source
def source(string)
Source.for(string)
Source.for(string, 1, [])
end

# Create a new Location object.
Expand Down Expand Up @@ -136,7 +136,7 @@ module Prism
#--
#: () -> Source
def default_source
Source.for("")
Source.for("", 1, [])
end

# The default location object that gets attached to nodes if no location is
Expand Down
8 changes: 4 additions & 4 deletions prism/templates/lib/prism/serialize.rb.erb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module Prism
#: (String input, String serialized, bool freeze) -> ParseResult
def self.load_parse(input, serialized, freeze)
input = input.dup
source = Source.for(input)
source = Source.for(input, 1, [])
loader = Loader.new(source, serialized)

loader.load_header
Expand Down Expand Up @@ -81,7 +81,7 @@ module Prism
#--
#: (String input, String serialized, bool freeze) -> LexResult
def self.load_lex(input, serialized, freeze)
source = Source.for(input)
source = Source.for(input, 1, [])
loader = Loader.new(source, serialized)

tokens = loader.load_tokens
Expand Down Expand Up @@ -127,7 +127,7 @@ module Prism
#--
#: (String input, String serialized, bool freeze) -> Array[Comment]
def self.load_parse_comments(input, serialized, freeze)
source = Source.for(input)
source = Source.for(input, 1, [])
loader = Loader.new(source, serialized)

loader.load_header
Expand All @@ -151,7 +151,7 @@ module Prism
#--
#: (String input, String serialized, bool freeze) -> ParseLexResult
def self.load_parse_lex(input, serialized, freeze)
source = Source.for(input)
source = Source.for(input, 1, [])
loader = Loader.new(source, serialized)

tokens = loader.load_tokens
Expand Down
4 changes: 3 additions & 1 deletion string.c
Original file line number Diff line number Diff line change
Expand Up @@ -2623,7 +2623,9 @@ rb_str_format_m(VALUE str, VALUE arg)
VALUE tmp = rb_check_array_type(arg);

if (!NIL_P(tmp)) {
return rb_str_format(RARRAY_LENINT(tmp), RARRAY_CONST_PTR(tmp), str);
VALUE result = rb_str_format(RARRAY_LENINT(tmp), RARRAY_CONST_PTR(tmp), str);
RB_GC_GUARD(tmp);
return result;
}
return rb_str_format(1, &arg, str);
}
Expand Down
2 changes: 2 additions & 0 deletions test/prism/errors/command_call_in.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ foo 1 in a
^~ unexpected 'in', expecting end-of-input
^~ unexpected 'in', ignoring it
a = foo 2 in b
^~ unexpected 'in', expecting end-of-input
^~ unexpected 'in', ignoring it

4 changes: 4 additions & 0 deletions test/prism/errors/write_command.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a = b c and 1
^~~ unexpected 'and', expecting end-of-input
^~~ unexpected 'and', ignoring it

4 changes: 4 additions & 0 deletions test/prism/fixtures/case_in_in.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
case args
in [event]
context.event in ^event
end
13 changes: 13 additions & 0 deletions zjit/src/cruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,19 @@ impl VALUE {
true
}

/// A metaclass is the singleton class of an object that is a `Module`.
/// This is internal terminology from `class.c`.
pub fn is_metaclass(self) -> bool {
unsafe {
if RB_TYPE_P(self, RUBY_T_CLASS) && rb_zjit_singleton_class_p(self) {
let attached = rb_class_attached_object(self);
RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE)
} else {
false
}
}
}

/// Return true for a static (non-heap) Ruby symbol (RB_STATIC_SYM_P)
pub fn static_sym_p(self) -> bool {
let VALUE(cval) = self;
Expand Down
15 changes: 2 additions & 13 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3083,17 +3083,6 @@ impl Function {
}
}

fn is_metaclass(&self, object: VALUE) -> bool {
unsafe {
if RB_TYPE_P(object, RUBY_T_CLASS) && rb_zjit_singleton_class_p(object) {
let attached = rb_class_attached_object(object);
RB_TYPE_P(attached, RUBY_T_CLASS) || RB_TYPE_P(attached, RUBY_T_MODULE)
} else {
false
}
}
}

pub fn load_rbasic_flags(&mut self, block: BlockId, recv: InsnId) -> InsnId {
self.push_insn(block, Insn::LoadField { recv, id: ID!(_rbasic_flags), offset: RUBY_OFFSET_RBASIC_FLAGS, return_type: types::CUInt64 })
}
Expand Down Expand Up @@ -3313,7 +3302,7 @@ impl Function {
} else if !has_block && def_type == VM_METHOD_TYPE_IVAR && args.is_empty() {
// Check if we're accessing ivars of a Class or Module object as they require single-ractor mode.
// We omit gen_prepare_non_leaf_call on gen_getivar, so it's unsafe to raise for multi-ractor mode.
if self.is_metaclass(klass) && !self.assume_single_ractor_mode(block, state) {
if klass.is_metaclass() && !self.assume_single_ractor_mode(block, state) {
self.push_insn_id(block, insn_id); continue;
}
// Check singleton class assumption first, before emitting other patchpoints
Expand All @@ -3333,7 +3322,7 @@ impl Function {
} else if let (false, VM_METHOD_TYPE_ATTRSET, &[val]) = (has_block, def_type, args.as_slice()) {
// Check if we're accessing ivars of a Class or Module object as they require single-ractor mode.
// We omit gen_prepare_non_leaf_call on gen_getivar, so it's unsafe to raise for multi-ractor mode.
if self.is_metaclass(klass) && !self.assume_single_ractor_mode(block, state) {
if klass.is_metaclass() && !self.assume_single_ractor_mode(block, state) {
self.push_insn_id(block, insn_id); continue;
}

Expand Down