Implement chilled strings

[Feature #20205]

As a path toward enabling frozen string literals by default in the future,
this commit introduce "chilled strings". From a user perspective chilled
strings pretend to be frozen, but on the first attempt to mutate them,
they lose their frozen status and emit a warning rather than to raise a
`FrozenError`.

Implementation wise, `rb_compile_option_struct.frozen_string_literal` is
no longer a boolean but a tri-state of `enabled/disabled/unset`.

When code is compiled with frozen string literals neither explictly enabled
or disabled, string literals are compiled with a new `putchilledstring`
instruction. This instruction is identical to `putstring` except it marks
the String with the `STR_CHILLED (FL_USER3)` and `FL_FREEZE` flags.

Chilled strings have the `FL_FREEZE` flag as to minimize the need to check
for chilled strings across the codebase, and to improve compatibility with
C extensions.

Notes:
  - `String#freeze`: clears the chilled flag.
  - `String#-@`: acts as if the string was mutable.
  - `String#+@`: acts as if the string was mutable.
  - `String#clone`: copies the chilled flag.

Co-authored-by: Jean Boussier <byroot@ruby-lang.org>
This commit is contained in:
Étienne Barrié 2023-12-01 11:33:00 +01:00 committed by Jean Boussier
parent 86b15316a7
commit 12be40ae6b
36 changed files with 714 additions and 282 deletions

6
iseq.h
View file

@ -47,6 +47,10 @@ extern const ID rb_iseq_shared_exc_local_tbl[];
#define ISEQ_FLIP_CNT(iseq) ISEQ_BODY(iseq)->variable.flip_count
#define ISEQ_FROZEN_STRING_LITERAL_ENABLED 1
#define ISEQ_FROZEN_STRING_LITERAL_DISABLED 0
#define ISEQ_FROZEN_STRING_LITERAL_UNSET -1
static inline rb_snum_t
ISEQ_FLIP_CNT_INCREMENT(const rb_iseq_t *iseq)
{
@ -227,7 +231,7 @@ struct rb_compile_option_struct {
unsigned int specialized_instruction: 1;
unsigned int operands_unification: 1;
unsigned int instructions_unification: 1;
unsigned int frozen_string_literal: 1;
signed int frozen_string_literal: 2; /* -1: not specified, 0: false, 1: true */
unsigned int debug_frozen_string_literal: 1;
unsigned int coverage_enabled: 1;
int debug_level;