From e796bdfd41b7afbf9f8de1ffb246041021af262b Mon Sep 17 00:00:00 2001 From: Daniel Sockwell Date: Mon, 14 Feb 2022 08:55:14 -0500 Subject: [PATCH] Add tests for re-binding Moves re-binding tests from Rakudo into Roast, specifying when variables can and cannot be rebound. Here's a description of the behavior specified by these tests (though, of course, the tests themselves are more authoritative than this description): * Non-sigiled 'variables' and other terms cannot be rebound * Sigiled variables that weren't declared as part of a Signature can be rebound * Sigiled variables that were declared as part of a Signature can be rebound *only* if declared with `is copy` or `is rw` For the above "declared as part of a Signature" covers two cases: * function declaration: `sub f($a, $b) { }` * The signature that is created when a parenthesized expression is used as `:=`'s LHS in a variable declaration (see S02, spec'ed in S02-names-vars/signature.t): `my ($a, $b) := (42, 47);` --- S03-binding/rebinding.t | 174 ++++++++++++++++++++++++++++++++++++++++ spectest.data | 3 +- 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 S03-binding/rebinding.t diff --git a/S03-binding/rebinding.t b/S03-binding/rebinding.t new file mode 100644 index 0000000000..2e624895c1 --- /dev/null +++ b/S03-binding/rebinding.t @@ -0,0 +1,174 @@ +use v6; +use Test; + +=begin pod + +Rebinding from L (with some guidance from L +and L). See rakudo/rakudo#4536 for related discussion + +=end pod + +subtest "Sigiled variables can be rebound", { + plan 3; + my $scalar := 'old scalar'; + $scalar := 'new'; + is $scalar, 'new', '$-sigiled variable can be rebound'; + + my @positional := ['old']; + @positional := ['new']; + is @positional, ['new'], '@-sigiled variable can be rebound'; + + my %associative := {:old}; + %associative := {:new}; + is %associative, {:new}, '%-sigiled variable can be rebound'; +} + +subtest "Scalars in signatures be rebound if they are 'copy' or 'rw'", { + plan 4; + is-deeply do { + sub f($bound-scalar-copy is copy) { $bound-scalar-copy := 'new' } + f 'old-value' }, 'new', + "Scalars in function signatures can be rebound when they are 'is copy'"; + is-deeply do { + sub f($bound-scalar-rw is rw) { $bound-scalar-rw := 'new' } + f my $ = 'old-value' }, 'new', + "Scalars in function signatures can be rebound when they are 'is rw' (and get a writable container)"; + is-deeply do { + my ($bound-scalar-copy is copy) := ('old-value',); + $bound-scalar-copy := 'new' }, 'new', + "Scalars in bound signatures can be rebound when they are 'is copy'"; + is-deeply do { + my ($bound-scalar-rw is rw) := (my $ = 'old-value',); + $bound-scalar-rw := 'new'}, 'new', + "Scalars in bound signatures can be rebound when they are 'is rw' (and get a writable container)"; +} + +subtest "Scalars in signatures that aren't 'copy' or 'rw' cannot be rebound", { + plan 4; + throws-like { + ‘sub f($bound-scalar-in-sig) { $bound-scalar-in-sig := 'new' }’.EVAL }, + X::Bind::Rebind, message => /'$bound-scalar-in-sig'/ & /'signature'/, + "Scalars in function signatures cannot be rebound"; + throws-like { + 'sub f(Mu $bound-scalar-with-type) { $bound-scalar-with-type := 0 }'.EVAL }, + X::Bind::Rebind, message => /'$bound-scalar-with-type'/ & /'signature'/, + "Scalars in function signatures cannot be rebound even if they have a type constraint"; + throws-like { + ‘my ($bound-scalar,) := ('original',); $bound-scalar := 'new'’.EVAL }, + X::Bind::Rebind, message => /'$bound-scalar'/ & /'signature'/, + "Scalars in bound signatures cannot be rebound"; + throws-like { + ‘my ($a, $b, Int $bound-scalar) := (0, 0, 0); $bound-scalar := 42’.EVAL }, + X::Bind::Rebind, message => /'$bound-scalar'/ & /'signature'/, + "Scalars in more complex bound signatures cannot be rebound"; +} + +subtest "Positional and Associative variables in signatures can be rebound if they are 'copy'", { + plan 4; + is-deeply do { + sub f(@positional-copy is copy) { @positional-copy := ['new'] } + f ['old-value'] }, ['new'], + "Positional variables in function signatures can be rebound when they are 'is copy'"; + is-deeply do { + my ($a, @positional-copy is copy) := (0, ['old']); + @positional-copy := ['new'] }, ['new'], + "Positional variables in bound signatures can be rebound when they are 'is copy'"; + is-deeply do { + sub f(%associative-copy is copy) { %associative-copy := {:new} } + f {:old-value} }, %(:new), + "Associative variables in function signatures can be rebound when they are 'is copy'"; + is-deeply do { + my ($a, %associative-copy is copy) := (0, {:old}); + %associative-copy := {:new} }, %(:new), + "Associative variables bound in signatures can be rebound when they are 'is copy'"; +} + +subtest "Positional and Associative variables in signatures that aren't 'copy' cannot be rebound", { + plan 4; + throws-like { + ‘sub f(@bound-positional-in-sig) { @bound-positional-in-sig := ['new'] }’.EVAL }, + X::Bind::Rebind, message => /'@bound-positional-in-sig'/ & /'signature'/, + "Positional variables in function signatures cannot be rebound"; + throws-like { + ‘my ($a, @bound-positional) := (0, []); @bound-positional := ['new']’.EVAL }, + X::Bind::Rebind, message => /'@bound-positional'/ & /'signature'/, + "Positional variables in bound signatures cannot be rebound"; + throws-like { + 'sub f(%bound-associative-in-sig) { %bound-associative-in-sig := {:new} }'.EVAL }, + X::Bind::Rebind, message => /'%bound-associative-in-sig'/ & /'signature'/, + "Associative variables in function signatures cannot be rebound"; + throws-like { + ‘my ($a, %bound-associative) := (0, []); %bound-associative := {:new}’.EVAL }, + X::Bind::Rebind, message => /'%bound-associative'/ & /'signature'/, + "Associative variables in bound signatures cannot be rebound"; +} + +subtest 'Sigilless "variables" can never be rebound', { + plan 4; + throws-like { + ‘my \sigilless = 'old'; sigilless := 'new'’.EVAL }, + X::Bind::Rebind, message => /'sigilless'/ & /'signature'/.none, + "Sigilless scalar terms cannot be rebound"; + throws-like { + ‘my \sigilless-array = ['old']; sigilless-array := ['new']’.EVAL }, + X::Bind::Rebind, message => /'sigilless-array'/ & /'signature'/.none, + "Sigilless positional terms cannot be rebound"; + throws-like { + ‘my \sigilless-hash = {:old}; sigilless-hash := {:new}’.EVAL }, + X::Bind::Rebind, message => /'sigilless-hash'/ & /'signature'/.none, + "Sigilless associative terms cannot be rebound"; + throws-like { + ‘constant con = 'old'; con := 'new'’.EVAL }, + X::Bind::Rebind, message => /'con'/ & /'signature'/.none, + "Constants cannot be rebound"; +} + +subtest "Code items can never be rebound", { + plan 2; + throws-like { + 'sub f() { }; &f := &say; }'.EVAL }, + X::Bind::Rebind, message => /'&f'/ & /'signature'/.none, + "A sub cannot be rebound"; + throws-like { + 'my regex reg-exp { old }; ®-exp := /new/; }'.EVAL }, + X::Bind::Rebind, message => /'®-exp'/ & /'signature'/.none, + "A regex cannot be rebound"; +} + +subtest "Terms can never be rebound", { + plan 4; + throws-like { + 'my class C { has $.old }; C := class { has $.new } }'.EVAL }, + X::Bind::Rebind, message => /'C'/ & /'signature'/.none, + "A class cannot be rebound"; + throws-like { + 'my role R { has $.old }; R := role { has $.new } }'.EVAL }, + X::Bind::Rebind, message => /'R'/ & /'signature'/.none, + "A role cannot be rebound"; + throws-like { + 'my grammar G { }; G := grammar { } }'.EVAL }, + X::Bind::Rebind, message => /'G'/ & /'signature'/.none, + "A grammar cannot be rebound"; + throws-like { + 'int := str;'.EVAL }, + X::Bind::Rebind, message => /'int'/ & /'signature'/.none, + "Native types cannot be rebound"; +} + +subtest "Items that were never bound don't throw *re*binding errors", { + plan 8; + given (try { ‘my int $var := 'new'’.EVAL }) { + cmp-ok $!, &[!~~], X::Bind::Rebind, ‘Binding to a native type doesn't throw X::Bind::Rebind’; + throws-like {$!.throw}, X::Bind::NativeType, 'Binding to a native type throws X::Bind::NativeType' } + given (try { ‘'literal string' := 'new'’.EVAL}) { + cmp-ok $!, &[!~~], X::Bind::Rebind, ‘Binding to a literal doesn't throw X::Bind::Rebind’; + throws-like {$!.throw}, X::Bind, 'Binding to a literal throws X::Bind' } + given (try { ‘sub f {}; f() := 'new'’.EVAL }) { + cmp-ok $!, &[!~~], X::Bind::Rebind, ‘Binding to a function call LHS doesn't throw X::Bind::Rebind’; + throws-like {$!.throw}, X::Bind, 'Binding to a function call LHS throws X::Bind' } + given (try { ‘::OUTER := 'new'’.EVAL }) { + cmp-ok $!, &[!~~], X::Bind::Rebind, ‘Binding to a pseudo-package LHS doesn't throw X::Bind::Rebind’; + throws-like {$!.throw}, X::Bind, 'Binding to a pseudo-package LHS throws X::Bind' } +} + +done-testing; diff --git a/spectest.data b/spectest.data index 232c6050f9..e6a0bea101 100644 --- a/spectest.data +++ b/spectest.data @@ -200,6 +200,7 @@ S03-binding/attributes.t S03-binding/closure.t S03-binding/hashes.t S03-binding/nested.t +S03-binding/rebinding.t S03-binding/ro.t S03-binding/scalars.t S03-buf/read-int.t # moar @@ -845,7 +846,7 @@ S15-string-types/Uni.t # moar S15-unicode-information/unimatch-general.t # moar S15-unicode-information/uniname.t S15-unicode-information/uniprop.t # moar -S15-unicode-information/unival.t # moar +S15-unicode-information/unival.t # moar S16-filehandles/argfiles.t S16-filehandles/chmod.t S16-filehandles/filestat.t