diff --git a/doc/string.rb b/doc/string.rb index 43386c644c80cb..a5d143cf369b45 100644 --- a/doc/string.rb +++ b/doc/string.rb @@ -398,7 +398,7 @@ # - #gsub!: Replaces each substring that matches a given pattern with a given replacement string; # returns +self+ if any changes, +nil+ otherwise. # - #succ! (aliased as #next!): Returns +self+ modified to become its own successor. -# - #initialize_copy (aliased as #replace): Returns +self+ with its entire content replaced by a given string. +# - #replace: Returns +self+ with its entire content replaced by a given string. # - #reverse!: Returns +self+ with its characters in reverse order. # - #setbyte: Sets the byte at a given integer offset to a given value; returns the argument. # - #tr!: Replaces specified characters in +self+ with specified replacement characters; diff --git a/prism/prism.c b/prism/prism.c index 875968f06be820..22ac19b5e469fe 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -5280,6 +5280,12 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_ switch (PM_NODE_TYPE(part)) { case PM_STRING_NODE: + // If inner string is not frozen, it stops being a static literal. We should *not* clear other flags, + // because concatenating two frozen strings (`'foo' 'bar'`) is still frozen. This holds true for + // as long as this interpolation only consists of other string literals. + if (!PM_NODE_FLAG_P(part, PM_STRING_FLAGS_FROZEN)) { + pm_node_flag_unset((pm_node_t *) node, PM_NODE_FLAG_STATIC_LITERAL); + } part->flags = (pm_node_flags_t) ((part->flags | PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN) & ~PM_STRING_FLAGS_MUTABLE); break; case PM_INTERPOLATED_STRING_NODE: diff --git a/prism_compile.c b/prism_compile.c index cd8287c7621d5b..66bee579c9b45a 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -558,7 +558,7 @@ parse_regexp_concat(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm static void pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node); static int -pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, rb_encoding *implicit_regexp_encoding, rb_encoding *explicit_regexp_encoding, bool mutable_result) +pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node, rb_encoding *implicit_regexp_encoding, rb_encoding *explicit_regexp_encoding, bool mutable_result, bool frozen_result) { int stack_size = 0; size_t parts_size = parts->size; @@ -668,10 +668,15 @@ pm_interpolated_node_compile(rb_iseq_t *iseq, const pm_node_list_t *parts, const if (RTEST(current_string)) { current_string = rb_fstring(current_string); - if (stack_size == 0 && (interpolated || mutable_result)) { - PUSH_INSN1(ret, current_location, putstring, current_string); - } - else { + if (stack_size == 0) { + if (frozen_result) { + PUSH_INSN1(ret, current_location, putobject, current_string); + } else if (mutable_result || interpolated) { + PUSH_INSN1(ret, current_location, putstring, current_string); + } else { + PUSH_INSN1(ret, current_location, putchilledstring, current_string); + } + } else { PUSH_INSN1(ret, current_location, putobject, current_string); } @@ -692,7 +697,7 @@ pm_compile_regexp_dynamic(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_ rb_encoding *explicit_regexp_encoding = parse_regexp_encoding(scope_node, node); rb_encoding *implicit_regexp_encoding = explicit_regexp_encoding != NULL ? explicit_regexp_encoding : scope_node->encoding; - int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node, implicit_regexp_encoding, explicit_regexp_encoding, false); + int length = pm_interpolated_node_compile(iseq, parts, node_location, ret, popped, scope_node, implicit_regexp_encoding, explicit_regexp_encoding, false, false); PUSH_INSN2(ret, *node_location, toregexp, INT2FIX(parse_regexp_flags(node) & 0xFF), INT2FIX(length)); } @@ -9571,7 +9576,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } else { const pm_interpolated_string_node_t *cast = (const pm_interpolated_string_node_t *) node; - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, !PM_NODE_FLAG_P(cast, PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN)); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, PM_NODE_FLAG_P(cast, PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE), PM_NODE_FLAG_P(cast, PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN)); if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); if (popped) PUSH_INSN(ret, location, pop); } @@ -9582,7 +9587,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // :"foo #{bar}" // ^^^^^^^^^^^^^ const pm_interpolated_symbol_node_t *cast = (const pm_interpolated_symbol_node_t *) node; - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, false); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, popped, scope_node, NULL, NULL, false, false); if (length > 1) { PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); @@ -9604,7 +9609,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_INSN(ret, location, putself); - int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node, NULL, NULL, false); + int length = pm_interpolated_node_compile(iseq, &cast->parts, &location, ret, false, scope_node, NULL, NULL, false, false); if (length > 1) PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); PUSH_SEND_WITH_FLAG(ret, location, idBackquote, INT2NUM(1), INT2FIX(VM_CALL_FCALL | VM_CALL_ARGS_SIMPLE)); diff --git a/spec/ruby/core/string/chilled_string_spec.rb b/spec/ruby/core/string/chilled_string_spec.rb index 968e4ec1f0dfae..73d055cbdf2fd0 100644 --- a/spec/ruby/core/string/chilled_string_spec.rb +++ b/spec/ruby/core/string/chilled_string_spec.rb @@ -47,6 +47,14 @@ input.should == "chilled-mutated" end + it "emits a warning for concatenated strings" do + input = "still" "+chilled" + -> { + input << "-mutated" + }.should complain(/literal string will be frozen in the future/) + input.should == "still+chilled-mutated" + end + it "emits a warning on singleton_class creation" do -> { "chilled".singleton_class diff --git a/string.c b/string.c index 53bbeb419150fb..a31aaece18f7a1 100644 --- a/string.c +++ b/string.c @@ -6648,11 +6648,13 @@ rb_str_gsub(int argc, VALUE *argv, VALUE str) * call-seq: * replace(other_string) -> self * - * Replaces the contents of +self+ with the contents of +other_string+: + * Replaces the contents of +self+ with the contents of +other_string+; + * returns +self+: * * s = 'foo' # => "foo" * s.replace('bar') # => "bar" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE @@ -7052,10 +7054,12 @@ rb_str_reverse(VALUE str) * * Returns +self+ with its characters reversed: * - * s = 'stressed' - * s.reverse! # => "desserts" - * s # => "desserts" + * 'drawer'.reverse! # => "reward" + * 'reviled'.reverse! # => "deliver" + * 'stressed'.reverse! # => "desserts" + * 'semordnilaps'.reverse! # => "spalindromes" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -12818,6 +12822,7 @@ Init_String(void) rb_define_singleton_method(rb_cString, "new", rb_str_s_new, -1); rb_define_singleton_method(rb_cString, "try_convert", rb_str_s_try_convert, 1); rb_define_method(rb_cString, "initialize", rb_str_init, -1); + rb_define_method(rb_cString, "replace", rb_str_replace, 1); rb_define_method(rb_cString, "initialize_copy", rb_str_replace, 1); rb_define_method(rb_cString, "<=>", rb_str_cmp_m, 1); rb_define_method(rb_cString, "==", rb_str_equal, 1); @@ -12848,7 +12853,6 @@ Init_String(void) rb_define_method(rb_cString, "byteindex", rb_str_byteindex_m, -1); rb_define_method(rb_cString, "rindex", rb_str_rindex_m, -1); rb_define_method(rb_cString, "byterindex", rb_str_byterindex_m, -1); - rb_define_method(rb_cString, "replace", rb_str_replace, 1); rb_define_method(rb_cString, "clear", rb_str_clear, 0); rb_define_method(rb_cString, "chr", rb_str_chr, 0); rb_define_method(rb_cString, "getbyte", rb_str_getbyte, 1); diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 4040c85907de4c..1bfd0a91af1990 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -6,7 +6,7 @@ use crate::cruby::*; use std::collections::HashSet; /// Default --zjit-num-profiles -const DEFAULT_NUM_PROFILES: u8 = 5; +const DEFAULT_NUM_PROFILES: u32 = 5; /// Default --zjit-call-threshold. This should be large enough to avoid compiling /// warmup code, but small enough to perform well on micro-benchmarks. @@ -40,7 +40,7 @@ pub struct Options { pub mem_bytes: usize, /// Number of times YARV instructions should be profiled. - pub num_profiles: u8, + pub num_profiles: u32, /// Enable YJIT statsitics pub stats: bool, @@ -112,9 +112,9 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[ ("--zjit-mem-size=num", "Max amount of memory that ZJIT can use (in MiB)."), ("--zjit-call-threshold=num", - "Number of calls to trigger JIT (default: 2)."), + "Number of calls to trigger JIT (default: 30)."), ("--zjit-num-profiles=num", - "Number of profiled calls before JIT (default: 1, max: 255)."), + "Number of profiled calls before JIT (default: 5)."), ("--zjit-stats[=quiet]", "Enable collecting ZJIT statistics (=quiet to suppress output)."), ("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."), ("--zjit-log-compiled-iseqs=path", diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index f73138d4bdcc41..471ff89e763148 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -278,7 +278,7 @@ pub struct IseqProfile { opnd_types: Vec>, /// Number of profiled executions for each YARV instruction, indexed by the instruction index - num_profiles: Vec, + num_profiles: Vec, } impl IseqProfile {