Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'no formats' #16135

Open
p5pRT opened this issue Sep 1, 2017 · 15 comments
Open

'no formats' #16135

p5pRT opened this issue Sep 1, 2017 · 15 comments

Comments

@p5pRT
Copy link

p5pRT commented Sep 1, 2017

Migrated from rt.perl.org#132013 (status was 'open')

Searchable as RT132013$

@p5pRT
Copy link
Author

p5pRT commented Sep 1, 2017

From @epa

Created by @epa

It would be useful to have

  no formats;

to turn off, at compile time, features related to formats
(until re-enabled with 'use formats').

This is useful because of the fair number of format-only special
variables such as $- and $=, whose existence tends to make it harder
to detect typos in code. If the programmer can declare that formats
aren't being used, perl can give a helpful message on seeing these.

Perl Info

Flags:
    category=core
    severity=wishlist

Site configuration information for perl 5.22.2:

Configured by Red Hat, Inc. at Fri Nov  4 14:35:02 UTC 2016.

Summary of my perl5 (revision 5 version 22 subversion 2) configuration:
   
  Platform:
    osname=linux, osvers=4.7.9-200.fc24.x86_64, archname=x86_64-linux-thread-multi
    uname='linux buildvm-12.phx2.fedoraproject.org 4.7.9-200.fc24.x86_64 #1 smp thu oct 20 14:26:16 utc 2016 x86_64 x86_64 x86_64 gnulinux '
    config_args='-des -Doptimize=none -Dccflags=-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches  -m64 -mtune=generic -Dldflags=-Wl,-z,relro  -Dccdlflags=-Wl,--enable-new-dtags -Wl,-z,relro  -Dlddlflags=-shared -Wl,-z,relro  -Dshrpdir=/usr/lib64 -DDEBUGGING=-g -Dversion=5.22.2 -Dmyhostname=localhost -Dperladmin=root@localhost -Dcc=gcc -Dcf_by=Red Hat, Inc. -Dprefix=/usr -Dvendorprefix=/usr -Dsiteprefix=/usr/local -Dsitelib=/usr/local/share/perl5 -Dsitearch=/usr/local/lib64/perl5 -Dprivlib=/usr/share/perl5 -Dvendorlib=/usr/share/perl5/vendor_perl -Darchlib=/usr/lib64/perl5 -Dvendorarch=/usr/lib64/perl5/vendor_perl -Darchname=x86_64-linux-thread-multi -Dlibpth=/usr/local/lib64 /lib64 /usr/lib64 -Duseshrplib -Dusethreads -Duseithreads -Dusedtrace=/usr/bin/dtrace -Duselargefiles -Dd_semctl_semun -Di_db -Ui_ndbm -Di_gdbm -Di_shadow -Di_syslog -Dman3ext=3pm -Duseperlio -Dinstallusrbinperl=n -Ubincompat5005 -Uversiononly -Dpager=/usr/bin/less -isr -Dd_gethostent_r_proto -Ud_endhostent_r_proto -Ud_sethostent_r_proto -Ud_endprotoent_r_proto -Ud_setprotoent_r_proto -Ud_endservent_r_proto -Ud_setservent_r_proto -Dscriptdir=/usr/bin -Dusesitecustomize'
    hint=recommended, useposix=true, d_sigaction=define
    useithreads=define, usemultiplicity=define
    use64bitint=define, use64bitall=define, uselongdouble=undef
    usemymalloc=n, bincompat5005=undef
  Compiler:
    cc='gcc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fwrapv -fno-strict-aliasing -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64',
    optimize='  -g',
    cppflags='-D_REENTRANT -D_GNU_SOURCE -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fwrapv -fno-strict-aliasing -I/usr/local/include'
    ccversion='', gccversion='5.3.1 20160406 (Red Hat 5.3.1-6)', gccosandvers=''
    intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678, doublekind=3
    d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16, longdblkind=3
    ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
    alignbytes=8, prototype=define
  Linker and Libraries:
    ld='gcc', ldflags ='-Wl,-z,relro  -fstack-protector-strong -L/usr/local/lib'
    libpth=/usr/local/lib64 /lib64 /usr/lib64 /usr/local/lib /usr/lib /lib/../lib64 /usr/lib/../lib64 /lib
    libs=-lpthread -lresolv -lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lc -lgdbm_compat
    perllibs=-lpthread -lresolv -lnsl -ldl -lm -lcrypt -lutil -lc
    libc=libc-2.22.so, so=so, useshrplib=true, libperl=libperl.so
    gnulibc_version='2.22'
  Dynamic Linking:
    dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,--enable-new-dtags -Wl,-z,relro '
    cccdlflags='-fPIC', lddlflags='-shared -Wl,-z,relro  -L/usr/local/lib -fstack-protector-strong'

Locally applied patches:
    Fedora Patch1: Removes date check, Fedora/RHEL specific
    Fedora Patch3: support for libdir64
    Fedora Patch4: use libresolv instead of libbind
    Fedora Patch5: USE_MM_LD_RUN_PATH
    Fedora Patch6: Skip hostname tests, due to builders not being network capable
    Fedora Patch7: Dont run one io test due to random builder failures
    Fedora Patch15: Define SONAME for libperl.so
    Fedora Patch16: Install libperl.so to -Dshrpdir value
    Fedora Patch22: Document Math::BigInt::CalcEmu requires Math::BigInt (CPAN RT#85015)
    Fedora Patch26: Make *DBM_File desctructors thread-safe (RT#61912)
    Fedora Patch27: Make PadlistNAMES() lvalue again (CPAN RT#101063)
    Fedora Patch28: Make magic vtable writable as a work-around for Coro (CPAN RT#101063)
    Fedora Patch29: Fix duplicating PerlIO::encoding when spawning threads (RT#31923)
    Fedora Patch30: Do not let XSLoader load relative paths (CVE-2016-6185)
    Fedora Patch31: Avoid loading optional modules from default . (CVE-2016-1238)
    Fedora Patch32: Fix a crash in lexical scope warnings (RT#128597)
    Fedora Patch33: Do not mangle errno from failed socket calls (RT#128316)
    Fedora Patch34: Fix crash in "evalbytes S" (RT#129196)
    Fedora Patch35: Fix crash in "evalbytes S" (RT#129196)
    Fedora Patch36: Fix crash in "evalbytes S" (RT#129196)
    Fedora Patch37: Fix crash in splice (RT#129164, RT#129166, RT#129167)
    Fedora Patch38: Fix string overrun in Perl_gv_fetchmethod_pvn_flags (RT#129267)
    Fedora Patch39: Fix string overrun in Perl_gv_fetchmethod_pvn_flags (RT#129267)
    Fedora Patch40: Fix string overrun in Perl_gv_fetchmethod_pvn_flags (RT#129267)
    Fedora Patch41: Fix string overrun in Perl_gv_fetchmethod_pvn_flags (RT#129267)
    Fedora Patch42: Fix string overrun in Perl_gv_fetchmethod_pvn_flags (RT#129267)
    Fedora Patch43: Fix crash when matching UTF-8 string with non-UTF-8 substrings (RT#129350)
    Fedora Patch44: Fix parsing perl options in shell bang line (RT#129336)
    Fedora Patch45: Fix firstchar bitmap under UTF-8 with prefix optimization (RT#129950)
    Fedora Patch46: Avoid infinite loop in h2xs tool if enum and type have the same name (RT130001)
    Fedora Patch47: Fix stack handling when calling chdir without an argument (RT#129130)
    Fedora Patch200: Link XS modules to libperl.so with EU::CBuilder on Linux
    Fedora Patch201: Link XS modules to libperl.so with EU::MM on Linux


@INC for perl 5.22.2:
    /home/eda/lib64/perl5/
    /usr/local/lib64/perl5
    /usr/local/share/perl5
    /usr/lib64/perl5/vendor_perl
    /usr/share/perl5/vendor_perl
    /usr/lib64/perl5
    /usr/share/perl5


Environment for perl 5.22.2:
    HOME=/home/eda
    LANG=en_GB.UTF-8
    LANGUAGE (unset)
    LC_COLLATE=C
    LC_CTYPE=en_GB.UTF-8
    LC_MESSAGES=en_GB.UTF-8
    LC_MONETARY=en_GB.UTF-8
    LC_NUMERIC=en_GB.UTF-8
    LC_TIME=en_GB.UTF-8
    LD_LIBRARY_PATH (unset)
    LOGDIR (unset)
    PATH=/home/eda/bin:/home/eda/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/sbin:/home/eda/.local/bin:/home/eda/bin:/sbin:/usr/sbin
    PERL5LIB=/home/eda/lib64/perl5/
    PERL_BADLANG (unset)
    SHELL=/bin/bash

@p5pRT
Copy link
Author

p5pRT commented Sep 1, 2017

From zefram@fysh.org

Ed Avis wrote​:

This is useful because of the fair number of format-only special
variables such as $- and $=, whose existence tends to make it harder
to detect typos in code.

I don't think this justifies a core feature, at least not yet.
It would be possible for a CPAN module to disable the use of those magic
variables, so that would seem to satisfy your use case. (In fact my
module IO​::ExplicitHandle does disable a bunch of them, along with the
implicit-handle form of some ops.) If such a stricture module becomes
very popular then we might consider implementing it in the core in
some form.

Thinking in terms of a stricture that disables magic variables,
taking out only the format-related ones doesn't seem the most useful
approach. Nor does it seem useful to disable other aspects of formats.
It would probably be better for a stricture to disable *all* of the
punctuation-named variables, with the exception of ones listed in the
"use" invocation. Effectively requiring declaration of each magic
variable before use. So you'd say something like

  use strict​::punctuation qw(except $_ $@​);

-zefram

@p5pRT
Copy link
Author

p5pRT commented Sep 1, 2017

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Sep 1, 2017

From @petdance

On Sep 1, 2017, at 1​:07 PM, Zefram <zefram@​fysh.org> wrote​:

This is useful because of the fair number of format-only special
variables such as $- and $=, whose existence tends to make it harder
to detect typos in code.

Perl​::Critic already has a policy that forbids using formats.

https://metacpan.org/pod/Perl::Critic::Policy::Miscellanea::ProhibitFormats

I could see expanding that to include any format-only variables.

@p5pRT
Copy link
Author

p5pRT commented Sep 1, 2017

From @petdance

Perl​::Critic requires a heroic amount of tweaking even to be useful at the minimal level, though. Something that says “I don’t even understand formats, let alone use them” might be useful to people who don’t have the patience to wrangle Perl​::Critic.

I wasn’t arguing against the idea. I’d love to see it. I was just pointing out an alternative solution if the original idea doesn’t get implemented.

Here’s the ticket I filed​: https​://github.com/Perl-Critic/Perl-Critic/issues/785 <https://github.com/Perl-Critic/Perl-Critic/issues/785>

@p5pRT
Copy link
Author

p5pRT commented Sep 1, 2017

From @Abigail

On Fri, Sep 01, 2017 at 10​:32​:55PM +0200, Sam Kington wrote​:

This is useful because of the fair number of format-only special
variables such as $- and $=, whose existence tends to make it harder
to detect typos in code.

Perl​::Critic already has a policy that forbids using formats.

https://metacpan.org/pod/Perl::Critic::Policy::Miscellanea::ProhibitFormats

I could see expanding that to include any format-only variables.

Perl​::Critic requires a heroic amount of tweaking even to be useful at the minimal level, though. Something that says “I don’t even understand formats, let alone use them” might be useful to people who don’t have the patience to wrangle Perl​::Critic.

An interesting related question would be​: what other parts of Perl are there that could be potentially made optional? Is e.g. dbmopen still core? There’s no reason why “use feature ‘foo’” should be reserved for something that’s new to that particular version of Perl; we could easily say “if you still want to use formats, which aren’t deprecated but aren’t that commonly-used, say use feature ‘formats’” in one version of Perl, and emit warnings if people don’t explicitly use the feature.

I don't see the win of requiring people to use "use feature 'foo'" for a
feature that's currently in core, and which you don't want to get rid of.

There isn't less functionality to support (after all, we're keeping said
features), in fact, there will be more (because now we live in a world
where a feature can be on or off).

Abigail

@p5pRT
Copy link
Author

p5pRT commented Sep 2, 2017

From @Leont

On Sat, Sep 2, 2017 at 1​:58 AM, Abigail <abigail@​abigail.be> wrote​:

I don't see the win of requiring people to use "use feature 'foo'" for a
feature that's currently in core, and which you don't want to get rid of.

There isn't less functionality to support (after all, we're keeping said
features), in fact, there will be more (because now we live in a world
where a feature can be on or off).

Agreed

Leon

@p5pRT
Copy link
Author

p5pRT commented Sep 2, 2017

From @Tux

On Fri, 1 Sep 2017 22​:32​:55 +0200, Sam Kington <sam@​illuminated.co.uk>
wrote​:

Perl​::Critic already has a policy that forbids using formats.

https://metacpan.org/pod/Perl::Critic::Policy::Miscellanea::ProhibitFormats

I could see expanding that to include any format-only variables.

Perl​::Critic requires a heroic amount of tweaking even to be useful
at the minimal level, though. Something that says “I don’t even
understand formats, let alone use them” might be useful to people who
don’t have the patience to wrangle Perl​::Critic.

Where the heroic effort is done by volunteers that care for that
specific issue.

Choosing to have that option on or of is easy in your ~/.perlcriticrc

$ grep -i format ~/.perlcriticrc
[-Miscellanea​::ProhibitFormats]
[-RegularExpressions​::RequireExtendedFormatting]

So, I have this module installed, but - being a user of formats - I
disabled it. Look at that like having the feature to use formats on by
default.

An interesting related question would be​: what other parts of Perl
are there that could be potentially made optional? Is e.g. dbmopen
still core?

Interesting question, but what would be the gain. As Abigail already
noted, there is not a lot. People would then have to explicitly tell
the scope to disable it. If one does not know about it or does not use
it, why disable it? It is like telling "I won't eat glass". Not that I
ever do, but it is possible. I embrace the idea that if you want to
protect yourself from eating glass, you just shouldn't. If you are
afraid that some part of your code might accidentally use features you
don't want to use (because you made a typo), Perl​::Critic is *THE* tool
to use. There simply are too many obscure/unknown/powerful/whatever
features in the perl core to have "no feature" pragma's for.

There’s no reason why “use feature ‘foo’” should be reserved for
something that’s new to that particular version of Perl; we could
easily say “if you still want to use formats, which aren’t deprecated

you assume formats will be deprecated? Why?

but aren’t that commonly-used,

You don't know DarkPAN. What is "commonly used"?

I *never* used threads in any of my perl scripts. Is that a reason to
call it "not commonly used"? Did you ever use long doubles or quad
doubles? Quad doubles are relatively new, so I'd expect them to not
being commonly used, but that is no reason to disable that or get rid
of it. Does having a useful feature like formats hinder your
development?

say use feature ‘formats’” in one version of Perl, and emit warnings
if people don’t explicitly use the feature.

Sam

--
H.Merijn Brand http​://tux.nl Perl Monger http​://amsterdam.pm.org/
using perl5.00307 .. 5.27 porting perl5 on HP-UX, AIX, and openSUSE
http​://mirrors.develooper.com/hpux/ http​://www.test-smoke.org/
http​://qa.perl.org http​://www.goldmark.org/jeff/stupid-disclaimers/

@p5pRT
Copy link
Author

p5pRT commented Sep 2, 2017

From sam@illuminated.co.uk

This is useful because of the fair number of format-only special
variables such as $- and $=, whose existence tends to make it harder
to detect typos in code.

Perl​::Critic already has a policy that forbids using formats.

https://metacpan.org/pod/Perl::Critic::Policy::Miscellanea::ProhibitFormats

I could see expanding that to include any format-only variables.

Perl​::Critic requires a heroic amount of tweaking even to be useful at the minimal level, though. Something that says “I don’t even understand formats, let alone use them” might be useful to people who don’t have the patience to wrangle Perl​::Critic.

An interesting related question would be​: what other parts of Perl are there that could be potentially made optional? Is e.g. dbmopen still core? There’s no reason why “use feature ‘foo’” should be reserved for something that’s new to that particular version of Perl; we could easily say “if you still want to use formats, which aren’t deprecated but aren’t that commonly-used, say use feature ‘formats’” in one version of Perl, and emit warnings if people don’t explicitly use the feature.

Sam
--
Website​: http​://www.illuminated.co.uk/

@p5pRT
Copy link
Author

p5pRT commented Sep 4, 2017

From @xsawyerx

 
 
 
 
 
 

  On 09/01/2017 09​:07 PM, Zefram wrote​:

 
 
  Ed Avis wrote​:

 
  This is useful because of the fair number of format-only special
variables such as $- and $=, whose existence tends to make it harder
to detect typos in code.

 
 
I don't think this justifies a core feature, at least not yet.
It would be possible for a CPAN module to disable the use of those magic
variables, so that would seem to satisfy your use case. (In fact my
module IO​::ExplicitHandle does disable a bunch of them, along with the
implicit-handle form of some ops.) If such a stricture module becomes
very popular then we might consider implementing it in the core in
some form.

Thinking in terms of a stricture that disables magic variables,
taking out only the format-related ones doesn't seem the most useful
approach. Nor does it seem useful to disable other aspects of formats.
It would probably be better for a stricture to disable *all* of the
punctuation-named variables, with the exception of ones listed in the
"use" invocation. Effectively requiring declaration of each magic
variable before use. So you'd say something like

  use strict​::punctuation qw(except $_ $@​);
 
 

  I actually like this idea.

 

  use punctuation qw( $_ @​_ $@​ $! );

  no punctuation qw( $- );

 
 

 

@p5pRT
Copy link
Author

p5pRT commented Sep 4, 2017

From @epa

Thanks to everyone who has replied. First to say something about formats. I haven't used them, and in my personal and subjective experience, they seem like a relic from another age; one of fixed-width terminal output and monospaced hard copy. If I wanted something fancier than plain text output with the occasional tab, I would generate HTML; for printed documentation I'd use a toolchain that makes PDFs. And what I said here was equally true twenty years ago.

However, that is not by any means a reason to remove formats or even to provide a way to disable them. As Abigail notes, it doesn't improve maintainability of the core because now you have two modes to support, with and without formats. And who knows, there may be programmers who make regular use of them. So this bug is not intended as "I do not like formats; please get rid of them".

The particular issue is the punctuation variables associated with formats. There are six and some of these two-character sequences can often appear as typos. The common typing mistakes vary by programmer; I often type $->[0] instead of $_-&gt;[0] but I can imagine others making mistakes with $% when first learning about how to use hash references.

So it's not really the same as threads or dbmopen(), or even dump(), which may not be much used, but don't affect the language syntax.

Perlcritic is a great tool and I use it regularly, but it isn't a replacement for error and warning checking in the language itself. For me at least, it's something to look at as a regular report but not every single time you edit and run the program. If the normal edit-test cycle is to be replaced by edit-perlcritic-test, development becomes a lot slower. As others have noted, perlcritic tends to need a lot of customization and adaptation. It's certainly worth it in the long term, but the more guru-ish issues raised by perlcritic are in a different class from a mistyped variable name.

I see it as analogous to "use strict". Why should perl check for mistyped variable names when it's perfectly legal to have both $transformed and $tansformed in your code, and perfectly legal to call &{"hello"}()? You can always have a perlcritic policy for that. And it means that the perl core now has to support running both with 'strict' and without. Yet checking for these typos saves so much development time, and doing it in the core is much faster than as an external linter, that it's worth having (and in my opinion, is one of the big plus points for Perl compared with other scripting languages).

Zefram's idea of disabling particular punctuation variables may be the way to go. As noted, the bug is not really about formats but just about $-, $% and so on, which are never used in most programs but have the effect of hiding typos and language misunderstandings. A pragma "no format_vars" would turn these off at compile time (and, perhaps, the longer named but still odd variables like $ACCUMULATOR). You could still use formats with the IO​::Handle named methods, or just "use format_vars" for a particular section of code.

Or if you forget the emphasis on formats and just cover magic variables in general, it could be used to address what I feel is another of the "paper cuts" in the language, the special behaviour of $a and $b. The crystal chamber could be used to change these to ordinary variable names.

--
Ed Avis <eda@​waniasset.com>

@p5pRT
Copy link
Author

p5pRT commented Oct 24, 2017

From @tonycoz

On Mon, 04 Sep 2017 02​:01​:24 -0700, ed wrote​:

The particular issue is the punctuation variables associated with
formats. There are six and some of these two-character sequences can
often appear as typos. The common typing mistakes vary by programmer;
I often type $->[0] instead of $_->[0] but I can imagine others making
mistakes with $% when first learning about how to use hash references.

Something like the attached?

Tony

@p5pRT
Copy link
Author

p5pRT commented Oct 24, 2017

From @tonycoz

0001-perl-132013-implement-strict-punctuation.patch
From cc93575397fe3016d970743ca9727aa19a83bb49 Mon Sep 17 00:00:00 2001
From: Tony Cook <tony@develop-help.com>
Date: Thu, 19 Oct 2017 16:43:51 +1100
Subject: (perl #132013) implement strict::punctuation

---
 MANIFEST                  |   2 +
 Porting/Maintainers.pl    |   1 +
 gv.c                      |  52 +++++++++++
 lib/strict/punctuation.pm | 214 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/strict/punctuation.t  | 155 +++++++++++++++++++++++++++++++++
 pod/perldiag.pod          |   5 ++
 6 files changed, 429 insertions(+)
 create mode 100644 lib/strict/punctuation.pm
 create mode 100644 lib/strict/punctuation.t

diff --git a/MANIFEST b/MANIFEST
index 124812b..52413f1 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -4683,6 +4683,8 @@ lib/sort.pm			For "use sort"
 lib/sort.t			See if "use sort" works
 lib/strict.pm			For "use strict"
 lib/strict.t			See if strictures work
+lib/strict/punctuation.pm	Enable or disable punctuation variables
+lib/strict/punctuation.t	See if strict::punctuation works
 lib/subs.pm			Declare overriding subs
 lib/subs.t			See if subroutine pseudo-importation works
 lib/Symbol.pm			Symbol table manipulation routines
diff --git a/Porting/Maintainers.pl b/Porting/Maintainers.pl
index 9c8c128..68e95b4 100755
--- a/Porting/Maintainers.pl
+++ b/Porting/Maintainers.pl
@@ -1437,6 +1437,7 @@ use File::Glob qw(:case);
                 lib/sigtrap.{pm,t}
                 lib/sort.{pm,t}
                 lib/strict.{pm,t}
+                lib/strict/punctuation.{pm,t}
                 lib/subs.{pm,t}
                 lib/unicore/
                 lib/utf8.{pm,t}
diff --git a/gv.c b/gv.c
index fed5b7c..9d2b0b8 100644
--- a/gv.c
+++ b/gv.c
@@ -1766,6 +1766,36 @@ S_gv_is_in_main(pTHX_ const char *name, STRLEN len, const U32 is_utf8)
     return FALSE;
 }
 
+static int
+punct_bit_number(svtype sv_type, U8 ch) {
+    /* This needs to match _punct_bit() in strict::punctation */
+    int base = sv_type == SVt_PV ? 0 :
+        sv_type == SVt_PVAV ? 1 :
+        sv_type == SVt_PVHV ? 2 : -1;
+    ch = NATIVE_TO_LATIN1(ch);
+
+    assert(sv_type == SVt_PV || sv_type == SVt_PVAV || sv_type == SVt_PVHV);
+    if (ch == 95) { /* _ */
+        return base;
+    }
+    else if (ch >= 33 && ch < 48) { /* ! .. / */
+        return base + 3 * (ch - 32);
+    }
+    else if (ch >= 58 && ch < 65) { /* : .. @ */
+        return base + 3 * (ch - 42);
+    }
+    else if (ch >= 91 && ch < 97) { /* [ .. ^ */
+        /* yes, this leaves an unused bit for _ */
+        return base + 3 * (ch - 68);
+    }
+    else if (ch == 126) { /* ~ */
+        return base + 3 * (ch - 97);
+    }
+    else {
+        return -1;
+    }
+}
+
 
 /* This function is called if parse_gv_stash_name() failed to
  * find a stash, or if GV_NOTQUAL or an empty name was passed
@@ -1784,7 +1814,29 @@ S_find_default_stash(pTHX_ HV **stash, const char *name, STRLEN len,
     /* No stash in name, so see how we can default */
 
     if ( gv_is_in_main(name, len, is_utf8) ) {
+        int bit;
+        SV *punct_bits;
+            
         *stash = PL_defstash;
+        if (IN_PERL_COMPILETIME && add &&
+            (PL_hints & HINT_LOCALIZE_HH) != 0 &&
+            (sv_type == SVt_PV || sv_type == SVt_PVAV || sv_type == SVt_PVHV) &&
+            len == 1 &&
+            (bit = punct_bit_number(sv_type, (U8)*name)) != -1 &&
+            (punct_bits = cop_hints_fetch_pvs(PL_curcop, "strict::punctuation", 0)) != &PL_sv_placeholder
+            ) {
+            STRLEN len;
+            U8 *bits = (U8*)SvPV(punct_bits, len);
+            if (Off((unsigned)bit) >= len || !IsSet(bits, (unsigned)bit)) {
+                qerror(Perl_mess(aTHX_
+                                 "Punctuation variable '%s%s' disabled by strict::punctuation",
+                                 (sv_type == SVt_PV ? "$"
+                                  : sv_type == SVt_PVAV ? "@"
+                                  : sv_type == SVt_PVHV ? "%"
+                                  : ""), name));
+            }
+        }
+        
     }
     else {
         if (IN_PERL_COMPILETIME) {
diff --git a/lib/strict/punctuation.pm b/lib/strict/punctuation.pm
new file mode 100644
index 0000000..170b8c9
--- /dev/null
+++ b/lib/strict/punctuation.pm
@@ -0,0 +1,214 @@
+package strict::punctuation;
+use strict;
+use warnings;
+use v5.26.0;
+
+our $VERSION = '1.000';
+
+our @CARP_NOT = __PACKAGE__;
+
+sub _croak {
+    require Carp;
+    goto &Carp::croak;
+}
+
+sub _punct_bit {
+    # we want ASCII-ish numbering here
+    my $o = ord(utf8::native_to_unicode(substr($_[0], 1)));
+    my $sigil = substr($_[0], 0, 1);
+    my $base =
+      $sigil eq '$' ? 0 :
+      $sigil eq '@' ? 1 :
+      $sigil eq '%' ? 2 :
+      return;
+    if ($o == 95) { # _
+        return $base;
+    }
+    elsif ($o >= 33 && $o < 48) {
+        return $base + 3 * ($o - 32);
+    }
+    elsif ($o >= 58 && $o < 65) {
+        return $base + 3 * ($o - 42);
+    }
+    elsif ($o >= 91 && $o < 97) {
+        # yes, this leaves an unused bit for _
+        return $base + 3 * ($o - 68);
+    }
+    elsif ($o == 126) {
+        return $base + 3 * ($o - 97);
+    }
+    else {
+        return;
+    }
+}
+
+my $def_bits;
+my $all_bits;
+my $key = "strict::punctuation";
+{
+    vec($def_bits, _punct_bit('$_'), 1) = 1;
+    vec($def_bits, _punct_bit('@_'), 1) = 1;
+    $all_bits = "\xFF" x ((_punct_bit('%~') + 7) / 8);
+}
+
+my %groups =
+  (
+   regex   => [ qw($& $` $' $+ @+ %+ @- %-) ],
+   formats => [ qw($% $: $= $^ $~) ],
+   users   => [ qw($< $>) ],
+   groups  => [ qw[$( $)] ],
+   files   => [ qw($. $/ $\ $|), '$,' ],
+   errors  => [ qw($! $@ $? %!) ],
+  );
+
+sub _do_bits {
+    my $value = shift;
+
+    my $bits = $^H{$key} || $def_bits;
+    if (@_) {
+        if ($_[0] eq ':none') {
+            shift;
+            $bits = $value ? '' : $all_bits;
+        }
+        elsif ($_[0] eq ':all') {
+            shift;
+            $bits = $value ? $all_bits : '';
+        }
+    }
+    while (defined(my $var = shift)) {
+        if ($var =~ s/^://) {
+            $groups{$var}
+              or _croak("Unknown punctuation variable group :$var");
+            unshift @_, @{$groups{$var}};
+        }
+        elsif ($var =~ s/^-://) {
+            $groups{$var}
+              or _croak("Unknown punctuation variable group :$var");
+            unshift @_, map "-$_", @{$groups{$var}};
+        }
+        else {
+            my $set = $value;
+            $set = !$set if $var =~ s/^-//;
+            my $bit = _punct_bit($var);
+            defined $bit
+              or _croak("Unknown punctuation variable $var");
+            vec($bits, $bit, 1) = $set;
+        }
+    }
+    $bits =~ s/\0+\z//g;
+    if ($bits eq $all_bits) {
+        delete $^H{$key};
+    }
+    else {
+        $^H{$key} = $bits;
+    }
+}
+
+sub import {
+    shift;
+    _do_bits(1, @_);
+}
+
+sub unimport {
+    shift;
+    _do_bits(0, @_);
+}
+
+1;
+
+=head1 NAME
+
+strict::punctuation - disable/enable punctuation variables lexically
+
+=head1 SYNOPSIS
+
+  use strict::punctuation;
+  # only $_ and @_ enabled
+  use strict::punctuation '$!';
+  # can now use $!
+  use strict::punctuation ':errors';
+  # can now use $!, $@, $? and %!
+  no strict::punctuation ':errors';
+  # can't use $! etc
+
+=head1 DESCRIPTION
+
+The C<strict::punctuation> pragma disables most punctuation variables
+to prevent accidental use of the wrong variables.
+
+By default the C<$_> and C<@_> variables remain enabled, but they can
+also be disabled by explicitly disabling them, or with the C<:none>
+group:
+
+  use strict::punctuation qw(-$_ -@_);
+  no strict::punctuation qw($_ @_);
+  use strict::punctuation ':none';
+
+You can enable or disable punctuation variables either individually or
+in groups:
+
+  use strict::punctuation qw($! $@ $? %!);
+  use strict::punctuation ':errors';
+
+You can prefix a variable name or most groups to reverse the
+operation:
+
+  use strict::punctuation '-$!'; # disable $!
+  no strict::punctuation '-$!'; # enable $!
+
+This is most useful when combining elements:
+
+  # enable all error variables except for $?
+  use strict::punctuation ':errors', '-$?';
+
+=head2 Variable Groups
+
+=over
+
+=item *
+
+C<:all> - all punctuation variables.  This can only be at the
+beginning of the import list and cannot be negated.
+
+  # enable all but $!
+  use strict::punctuation ':all', '-$!';
+
+=item *
+
+C<:none> - no variables.  This can only be at the beginning of the
+import list and cannot be negated.
+
+  # only $! is enabled
+  use strict::punctuation ':none', '$!';
+
+=item *
+
+C<:regex> - enable regular expression punctuation variables. C<$&>,
+C<$`>, C<$'>, C<$+>, C<@+>, C<%+>, C<@-> and C<%->.
+
+=item *
+
+C<:formats:> - enable punctuation variables associated with formats.
+C<$%>, C<$:>, C<$=>, C<$^> and C<$~>.
+
+=item *
+
+C<:users> - the C<< $< >> and C<< $> >> variables.
+
+=item *
+
+C<:groups> - the C<$(> and C<$)> variables.
+
+=item *
+
+C<:files> - punctuation variables associated with file handling.
+C<$.>, C<$/>, C<$\>, C<$|> and C<$,>.
+
+=item *
+
+C<:errors> - punctuation variables associated with errors. C<$!>,
+C<$@>, C<$?> and C<%!>.
+
+=back
+
+=cut
diff --git a/lib/strict/punctuation.t b/lib/strict/punctuation.t
new file mode 100644
index 0000000..afe815c
--- /dev/null
+++ b/lib/strict/punctuation.t
@@ -0,0 +1,155 @@
+#!perl
+
+BEGIN {
+    chdir 't' if -d 't';
+    require "./test.pl";
+    set_up_inc(".", "../lib");
+}
+
+run_multiple_progs('', \*DATA);
+
+done_testing();
+
+__END__
+# NAME strict::punctuation defaults
+use strict::punctuation;
+my $x = $_;
+my @x = @_;
+my %x = %_;
+$x = $!;
+@x = @!;
+%x = %!;
+print "Fail\n";
+EXPECT
+OPTIONS fatal
+Punctuation variable '%_' disabled by strict::punctuation at - line 4.
+Punctuation variable '$!' disabled by strict::punctuation at - line 5.
+Punctuation variable '@!' disabled by strict::punctuation at - line 6.
+Punctuation variable '%!' disabled by strict::punctuation at - line 7.
+BEGIN not safe after errors--compilation aborted at - line 7.
+########
+# NAME strict::punctuation disable $_
+no strict::punctuation '$_';
+my $x = $_;
+use strict::punctuation '$_';
+my $y = $_;
+EXPECT
+OPTIONS fatal
+Punctuation variable '$_' disabled by strict::punctuation at - line 2.
+BEGIN not safe after errors--compilation aborted at - line 3.
+########
+# NAME strict::punctuation disable $_
+my $z = $_;
+no strict::punctuation '$_';
+my $x = $_;
+use strict::punctuation '$_';
+my $y = $_;
+EXPECT
+OPTIONS fatal
+Punctuation variable '$_' disabled by strict::punctuation at - line 3.
+BEGIN not safe after errors--compilation aborted at - line 4.
+########
+# NAME test - prefix
+my $x = $_;
+use strict::punctuation '-$_', '$!';
+die $!;
+my $y = $_;
+EXPECT
+OPTIONS fatal
+Punctuation variable '$_' disabled by strict::punctuation at - line 4.
+Execution of - aborted due to compilation errors.
+########
+# NAME strict::punctuation disable @_
+sub foo {
+    my @x = @_;
+    use strict::punctuation '@_';
+    my @y = @_;
+    no strict::punctuation '@_';
+    my @z = @_;
+}
+EXPECT
+OPTIONS fatal
+Punctuation variable '@_' disabled by strict::punctuation at - line 6.
+Execution of - aborted due to compilation errors.
+########
+# NAME regex group (and - on groups)
+"a" =~ /(1)/;
+my $x = $';
+my $y = $&;
+my $z = $`;
+my $w = $+;
+my @x = @-;
+my @y = @+;
+my %x = %-;
+my %y = %+;
+use strict::punctuation ':regex';
+$x = $';
+$y = $&;
+$z = $`;
+$w = $+;
+@x = @-;
+@y = @+;
+%x = %-;
+%y = %+;
+use strict::punctuation '-:regex';
+$x = $';
+$y = $&;
+$z = $`;
+$w = $+;
+@x = @-;
+@y = @+;
+%x = %-;
+%y = %+;
+EXPECT
+OPTIONS fatal
+Punctuation variable '$'' disabled by strict::punctuation at - line 20.
+Punctuation variable '$&' disabled by strict::punctuation at - line 21.
+Punctuation variable '$`' disabled by strict::punctuation at - line 22.
+Punctuation variable '$+' disabled by strict::punctuation at - line 23.
+Punctuation variable '@-' disabled by strict::punctuation at - line 24.
+Punctuation variable '@+' disabled by strict::punctuation at - line 25.
+Punctuation variable '%-' disabled by strict::punctuation at - line 26.
+Punctuation variable '%+' disabled by strict::punctuation at - line 27.
+Execution of - aborted due to compilation errors.
+########
+# NAME errors group
+$! = 0;
+$@ = '';
+$? = 0;
+my @x = keys %!;
+use strict::punctuation ':errors';
+$! = 0;
+$@ = '';
+$? = 0;
+@x = keys %!;
+no strict::punctuation ':errors';
+$! = 0;
+$@ = '';
+$? = 0;
+my @x = keys %!;
+EXPECT
+OPTIONS fatal
+Punctuation variable '$!' disabled by strict::punctuation at - line 11.
+Punctuation variable '$@' disabled by strict::punctuation at - line 12.
+Punctuation variable '$?' disabled by strict::punctuation at - line 13.
+Punctuation variable '%!' disabled by strict::punctuation at - line 14.
+Execution of - aborted due to compilation errors.
+########
+# NAME all group
+use strict::punctuation ':all';
+my $x = $_;
+no strict::punctuation ':all';
+my $x = $_;
+EXPECT
+OPTIONS fatal
+Punctuation variable '$_' disabled by strict::punctuation at - line 4.
+Execution of - aborted due to compilation errors.
+########
+# NAME none group
+use strict::punctuation ':none', '$!';
+my $x = $_;
+$! = 0;
+EXPECT
+OPTIONS fatal
+Punctuation variable '$_' disabled by strict::punctuation at - line 2.
+Execution of - aborted due to compilation errors.
diff --git a/pod/perldiag.pod b/pod/perldiag.pod
index d417fb2..43c3cde 100644
--- a/pod/perldiag.pod
+++ b/pod/perldiag.pod
@@ -5079,6 +5079,11 @@ the sub name and via the prototype attribute.  The prototype in
 parentheses is useless, since it will be replaced by the prototype
 from the attribute before it's ever used.
 
+=item Punctuation variable '%s' disabled by strict::punctuation
+
+(F) You've attemptted to use a punctuation variable that you've
+disabled with the L<strict::punctuation> pragma.
+
 =item Quantifier follows nothing in regex; marked by S<<-- HERE> in m/%s/
 
 (F) You started a regular expression with a quantifier.  Backslash it if
-- 
2.1.4

@p5pRT
Copy link
Author

p5pRT commented Oct 31, 2017

From @epa

On Mon, 23 Oct 2017 22​:10​:46 -0700, tonyc wrote​:

Something like the attached?

Yes, that looks great (based on reading the documentation).

Although you have provided a few canned presets, I envisage that the exact selection would often be done in a 'style' module like Modern​::Perl. Personally, I would set it to a small change at first, only disabling those punctuation variables which are truly obscure and obsolete, like $[.

(I do still like the idea of 'no formats', with 'use formats' enabling both the format keywords and the associated punctuation variables, but I understand that it's kind of unrelated to the key issue in this bug, which is to get more useful diagnostics out of the parser for typing errors.)

@p5pRT
Copy link
Author

p5pRT commented Oct 31, 2017

From @epa

And thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants