From dc685f58920d5bd1b2724aeea8ae595fd07005e0 Mon Sep 17 00:00:00 2001 From: Andrew Morris Date: Wed, 5 Apr 2023 10:15:19 +1000 Subject: [PATCH] require_mutable_this --- inputs/{passing => failing}/binaryTree.ts | 19 +++--- inputs/failing/const/callNestedConstMethod.ts | 19 ++++++ inputs/failing/exceptions/snapshotThis.ts | 2 +- .../helpers/BinaryTree.ts | 2 +- inputs/{passing => failing}/testBinaryTree.ts | 3 +- .../const/violateWithCustomMethod.ts | 3 +- .../const/violateWithCustomMethodNested.ts | 3 +- valuescript_common/src/instruction_byte.rs | 2 + valuescript_compiler/src/asm.rs | 3 + valuescript_compiler/src/assembler.rs | 2 +- valuescript_compiler/src/assembly_parser.rs | 2 + valuescript_compiler/src/function_compiler.rs | 7 +++ .../src/instruction_mutates_this.rs | 60 +++++++++++++++++++ valuescript_compiler/src/lib.rs | 1 + valuescript_compiler/src/link_module.rs | 2 +- valuescript_vm/src/bytecode_stack_frame.rs | 12 +++- 16 files changed, 121 insertions(+), 21 deletions(-) rename inputs/{passing => failing}/binaryTree.ts (77%) create mode 100644 inputs/failing/const/callNestedConstMethod.ts rename inputs/{passing => failing}/helpers/BinaryTree.ts (93%) rename inputs/{passing => failing}/testBinaryTree.ts (74%) rename inputs/{failing => passing}/const/violateWithCustomMethod.ts (65%) rename inputs/{failing => passing}/const/violateWithCustomMethodNested.ts (74%) create mode 100644 valuescript_compiler/src/instruction_mutates_this.rs diff --git a/inputs/passing/binaryTree.ts b/inputs/failing/binaryTree.ts similarity index 77% rename from inputs/passing/binaryTree.ts rename to inputs/failing/binaryTree.ts index c2f67db..f0138de 100644 --- a/inputs/passing/binaryTree.ts +++ b/inputs/failing/binaryTree.ts @@ -1,4 +1,5 @@ -// test_output! [[1,2,5],[1,2,3,4,5]] +// test_output! E: TypeError{"message":"Cannot mutate this because it is const"} +// (This is wrong.) export default function main() { let tree = BinaryTree(); @@ -18,17 +19,17 @@ export default function main() { function BinaryTree() { type BinaryTree = { data: { - value?: number, - left?: BinaryTree, - right?: BinaryTree, - }, - insert(this: BinaryTree, newValue: number): void, - toArray(this: BinaryTree): number[], + value?: number; + left?: BinaryTree; + right?: BinaryTree; + }; + insert(this: BinaryTree, newValue: number): void; + toArray(this: BinaryTree): number[]; }; let tree: BinaryTree = { data: {}, - insert: function(newValue) { + insert: function (newValue) { if (this.data.value === undefined) { this.data.value = newValue; return; @@ -42,7 +43,7 @@ function BinaryTree() { this.data.right.insert(newValue); } }, - toArray: function() { + toArray: function () { let res: number[] = []; if (this.data.left) { diff --git a/inputs/failing/const/callNestedConstMethod.ts b/inputs/failing/const/callNestedConstMethod.ts new file mode 100644 index 0000000..3ea60f2 --- /dev/null +++ b/inputs/failing/const/callNestedConstMethod.ts @@ -0,0 +1,19 @@ +// test_output! E: TypeError{"message":"Cannot mutate this because it is const"} +// (This is wrong.) + +export default function () { + const foo = new Foo(); + const x = foo.calc(); + + return x; +} + +class Foo { + calc() { + return this.get(); + } + + get() { + return 37; + } +} diff --git a/inputs/failing/exceptions/snapshotThis.ts b/inputs/failing/exceptions/snapshotThis.ts index bd79883..01e6fcd 100644 --- a/inputs/failing/exceptions/snapshotThis.ts +++ b/inputs/failing/exceptions/snapshotThis.ts @@ -2,7 +2,7 @@ // (Should be 0.) export default function main() { - const foo = new Foo(); + let foo = new Foo(); foo.inc(); return foo.x; diff --git a/inputs/passing/helpers/BinaryTree.ts b/inputs/failing/helpers/BinaryTree.ts similarity index 93% rename from inputs/passing/helpers/BinaryTree.ts rename to inputs/failing/helpers/BinaryTree.ts index 603897b..4a25701 100644 --- a/inputs/passing/helpers/BinaryTree.ts +++ b/inputs/failing/helpers/BinaryTree.ts @@ -1,4 +1,4 @@ -export default class BinaryTree { +export default class BinaryTree { left?: BinaryTree; value?: T; right?: BinaryTree; diff --git a/inputs/passing/testBinaryTree.ts b/inputs/failing/testBinaryTree.ts similarity index 74% rename from inputs/passing/testBinaryTree.ts rename to inputs/failing/testBinaryTree.ts index 8d20fc5..d015a31 100644 --- a/inputs/passing/testBinaryTree.ts +++ b/inputs/failing/testBinaryTree.ts @@ -1,4 +1,5 @@ -// test_output! [[1,2,5],[1,2,3,4,5]] +// test_output! E: TypeError{"message":"Cannot mutate this because it is const"} +// (This is wrong.) import BinaryTree from "./helpers/BinaryTree.ts"; diff --git a/inputs/failing/const/violateWithCustomMethod.ts b/inputs/passing/const/violateWithCustomMethod.ts similarity index 65% rename from inputs/failing/const/violateWithCustomMethod.ts rename to inputs/passing/const/violateWithCustomMethod.ts index 6d552e8..3fe5c69 100644 --- a/inputs/failing/const/violateWithCustomMethod.ts +++ b/inputs/passing/const/violateWithCustomMethod.ts @@ -1,5 +1,4 @@ -// test_output! 1 -// (This is wrong.) +// test_output! E: TypeError{"message":"Cannot mutate this because it is const"} export default function () { const foo = new Foo(); diff --git a/inputs/failing/const/violateWithCustomMethodNested.ts b/inputs/passing/const/violateWithCustomMethodNested.ts similarity index 74% rename from inputs/failing/const/violateWithCustomMethodNested.ts rename to inputs/passing/const/violateWithCustomMethodNested.ts index 114c074..486c950 100644 --- a/inputs/failing/const/violateWithCustomMethodNested.ts +++ b/inputs/passing/const/violateWithCustomMethodNested.ts @@ -1,5 +1,4 @@ -// test_output! 1 -// (This is wrong.) +// test_output! E: TypeError{"message":"Cannot mutate this because it is const"} export default function () { const foo = new Foo(); diff --git a/valuescript_common/src/instruction_byte.rs b/valuescript_common/src/instruction_byte.rs index 4b5974e..3604673 100644 --- a/valuescript_common/src/instruction_byte.rs +++ b/valuescript_common/src/instruction_byte.rs @@ -50,6 +50,7 @@ pub enum InstructionByte { SetCatch = 0x2f, UnsetCatch = 0x30, ConstSubCall = 0x31, + RequireMutableThis = 0x32, } impl InstructionByte { @@ -107,6 +108,7 @@ impl InstructionByte { 0x2f => SetCatch, 0x30 => UnsetCatch, 0x31 => ConstSubCall, + 0x32 => RequireMutableThis, _ => panic!("Unrecognized instruction: {}", byte), }; diff --git a/valuescript_compiler/src/asm.rs b/valuescript_compiler/src/asm.rs index c51a78c..9ddd482 100644 --- a/valuescript_compiler/src/asm.rs +++ b/valuescript_compiler/src/asm.rs @@ -288,6 +288,7 @@ pub enum Instruction { SetCatch(LabelRef, Register), UnsetCatch, ConstSubCall(Value, Value, Value, Register), + RequireMutableThis, } impl std::fmt::Display for Instruction { @@ -435,6 +436,7 @@ impl std::fmt::Display for Instruction { obj, subscript, args, register ) } + Instruction::RequireMutableThis => write!(f, "require_mutable_this"), } } } @@ -495,6 +497,7 @@ impl Instruction { SetCatch(..) => InstructionByte::SetCatch, UnsetCatch => InstructionByte::UnsetCatch, ConstSubCall(..) => InstructionByte::ConstSubCall, + RequireMutableThis => InstructionByte::RequireMutableThis, } } } diff --git a/valuescript_compiler/src/assembler.rs b/valuescript_compiler/src/assembler.rs index 7327817..738a4dd 100644 --- a/valuescript_compiler/src/assembler.rs +++ b/valuescript_compiler/src/assembler.rs @@ -160,7 +160,7 @@ impl Assembler { self.output.push(instruction.byte() as u8); match instruction { - End | UnsetCatch => {} + End | UnsetCatch | RequireMutableThis => {} OpInc(dst) | OpDec(dst) => { self.register(dst); } diff --git a/valuescript_compiler/src/assembly_parser.rs b/valuescript_compiler/src/assembly_parser.rs index 8867f7f..da29bb9 100644 --- a/valuescript_compiler/src/assembly_parser.rs +++ b/valuescript_compiler/src/assembly_parser.rs @@ -225,6 +225,7 @@ impl<'a> AssemblyParser<'a> { ("set_catch", InstructionByte::SetCatch), ("unset_catch", InstructionByte::UnsetCatch), ("const_subcall", InstructionByte::ConstSubCall), + ("require_mutable_this", InstructionByte::RequireMutableThis), ]); for (word, instruction) in instruction_word_map { @@ -676,6 +677,7 @@ impl<'a> AssemblyParser<'a> { self.assemble_value(), self.assemble_register(), ), + RequireMutableThis => Instruction::RequireMutableThis, } } diff --git a/valuescript_compiler/src/function_compiler.rs b/valuescript_compiler/src/function_compiler.rs index 076e2e6..c13254b 100644 --- a/valuescript_compiler/src/function_compiler.rs +++ b/valuescript_compiler/src/function_compiler.rs @@ -12,6 +12,7 @@ use crate::asm::{ use crate::diagnostic::{Diagnostic, DiagnosticLevel}; use crate::expression_compiler::CompiledExpression; use crate::expression_compiler::ExpressionCompiler; +use crate::instruction_mutates_this::instruction_mutates_this; use crate::name_allocator::{NameAllocator, RegAllocator}; use crate::scope::{NameId, OwnerId}; use crate::scope_analysis::{fn_to_owner_id, Name, ScopeAnalysis}; @@ -111,6 +112,12 @@ impl FunctionCompiler { } pub fn push(&mut self, instruction: Instruction) { + if instruction_mutates_this(&instruction) { + self.current.body.push(InstructionOrLabel::Instruction( + Instruction::RequireMutableThis, + )); + } + self .current .body diff --git a/valuescript_compiler/src/instruction_mutates_this.rs b/valuescript_compiler/src/instruction_mutates_this.rs new file mode 100644 index 0000000..4fe5bc8 --- /dev/null +++ b/valuescript_compiler/src/instruction_mutates_this.rs @@ -0,0 +1,60 @@ +use crate::asm::{Instruction, Register, Value}; + +pub fn instruction_mutates_this(instruction: &Instruction) -> bool { + use Instruction::*; + + match instruction { + End | Jmp(..) | JmpIf(..) | Throw(..) | UnsetCatch | RequireMutableThis => false, + Mov(_, reg) + | OpInc(reg) + | OpDec(reg) + | OpPlus(_, _, reg) + | OpMinus(_, _, reg) + | OpMul(_, _, reg) + | OpDiv(_, _, reg) + | OpMod(_, _, reg) + | OpExp(_, _, reg) + | OpEq(_, _, reg) + | OpNe(_, _, reg) + | OpTripleEq(_, _, reg) + | OpTripleNe(_, _, reg) + | OpAnd(_, _, reg) + | OpOr(_, _, reg) + | OpNot(_, reg) + | OpLess(_, _, reg) + | OpLessEq(_, _, reg) + | OpGreater(_, _, reg) + | OpGreaterEq(_, _, reg) + | OpNullishCoalesce(_, _, reg) + | OpOptionalChain(_, _, reg) + | OpBitAnd(_, _, reg) + | OpBitOr(_, _, reg) + | OpBitNot(_, reg) + | OpBitXor(_, _, reg) + | OpLeftShift(_, _, reg) + | OpRightShift(_, _, reg) + | OpRightShiftUnsigned(_, _, reg) + | TypeOf(_, reg) + | InstanceOf(_, _, reg) + | In(_, _, reg) + | Call(_, _, reg) + | Bind(_, _, reg) + | Sub(_, _, reg) + | SubMov(_, _, reg) + | UnaryPlus(_, reg) + | UnaryMinus(_, reg) + | New(_, _, reg) + | Import(_, reg) + | ImportStar(_, reg) + | SetCatch(_, reg) + | ConstSubCall(_, _, _, reg) => reg == &Register::This, + + Apply(_, ctx, _, reg) | SubCall(ctx, _, _, reg) => { + reg == &Register::This + || match ctx { + Value::Register(reg) => reg == &Register::This, + _ => false, + } + } + } +} diff --git a/valuescript_compiler/src/lib.rs b/valuescript_compiler/src/lib.rs index 4615b73..50e6431 100644 --- a/valuescript_compiler/src/lib.rs +++ b/valuescript_compiler/src/lib.rs @@ -8,6 +8,7 @@ mod expression_compiler; mod function_compiler; mod gather_modules; mod import_pattern; +mod instruction_mutates_this; mod link_module; mod module_compiler; mod name_allocator; diff --git a/valuescript_compiler/src/link_module.rs b/valuescript_compiler/src/link_module.rs index 7ff68e0..a7ebd61 100644 --- a/valuescript_compiler/src/link_module.rs +++ b/valuescript_compiler/src/link_module.rs @@ -234,7 +234,7 @@ where use Instruction::*; match instruction { - End | UnsetCatch | OpInc(..) | OpDec(..) | Jmp(..) | SetCatch(..) => {} + End | UnsetCatch | RequireMutableThis | OpInc(..) | OpDec(..) | Jmp(..) | SetCatch(..) => {} Mov(arg, _) | OpNot(arg, _) | OpBitNot(arg, _) diff --git a/valuescript_vm/src/bytecode_stack_frame.rs b/valuescript_vm/src/bytecode_stack_frame.rs index ecbc71b..3e2b949 100644 --- a/valuescript_vm/src/bytecode_stack_frame.rs +++ b/valuescript_vm/src/bytecode_stack_frame.rs @@ -92,9 +92,9 @@ impl BytecodeStackFrame { } impl StackFrameTrait for BytecodeStackFrame { - fn write_this(&mut self, _const: bool, this: Val) -> Result<(), Val> { + fn write_this(&mut self, const_: bool, this: Val) -> Result<(), Val> { self.registers[1] = this; - self.const_this = _const; + self.const_this = const_; Ok(()) } @@ -226,7 +226,7 @@ impl StackFrameTrait for BytecodeStackFrame { } } else { self.this_target = None; - new_frame.write_this(false, self.decoder.decode_val(&self.registers))?; + new_frame.write_this(true, self.decoder.decode_val(&self.registers))?; } self.transfer_parameters(&mut new_frame); @@ -452,6 +452,12 @@ impl StackFrameTrait for BytecodeStackFrame { UnsetCatch => { self.catch_setting = None; } + + RequireMutableThis => { + if self.const_this { + return type_error!("Cannot mutate this because it is const"); + } + } }; Ok(FrameStepOk::Continue)