Skip to content

fix: unblock GitLab::API::v4 (and Const::Fast) under jcpan#583

Merged
fglock merged 1 commit intomasterfrom
fix/gitlab-api-v4-and-const-fast
Apr 28, 2026
Merged

fix: unblock GitLab::API::v4 (and Const::Fast) under jcpan#583
fglock merged 1 commit intomasterfrom
fix/gitlab-api-v4-and-const-fast

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 28, 2026

Summary

Investigating jcpan -t GitLab::API::v4 surfaced four independent defects, each masking the next. They all need to land together for the module to load and its unit tests to pass.

1. Parser: use Foo qw() must skip import

Real Perl treats both use Foo () and use Foo qw() as the "skip import" form. PerlOnJava only special-cased () with parens, so use Carp qw(); ran Carp->import() with no args, which Exporter interprets as "import @export defaults" and leaked Carp::croak into the caller. That made sub croak { ... } in GitLab::API::v4::RESTClient look like a redefinition.

Fix: in StatementParser.parseUseDeclaration, also recognise a syntactically empty list expression (e.g. qw()) by walking the parsed AST for ListNode emptiness, gated on the parser having actually consumed tokens. use Foo @runtime_empty_list still calls import(), matching real Perl.

2. HTTP::Tiny stop pretending to export new/get/post/request

Real HTTP::Tiny exports nothing — these are object methods. PerlOnJava's HttpTiny.java added them to @EXPORT, so any package that did use HTTP::Tiny; got Foo::new aliased to HTTP::Tiny::new. Moo's assert_constructor then saw a pre-existing new and bailed with "Unknown constructor for Foo already exists" before installing its own constructor — breaking any Moo class that did use HTTP::Tiny;.

Fix: drop the bogus defineExport("EXPORT", ...). (Pre-existing bug; defect #1 used to make compilation fail earlier and hid it.)

3. Read-only error message format

RuntimeScalarReadOnly.vivify() threw a plain RuntimeException, which made stringifyException() fall through to the multi-line stack-trace formatter:

Modification of a read-only value attempted
        main at FILE line N
        main at FILE line N

Real Perl (and Test::Fatal-style regex matchers in Const::Fast's t/10-basics.t) expect the canonical single-line form:

Modification of a read-only value attempted at FILE line N.

Fix: throw PerlCompilerException instead, so the short-circuit path in stringifyException() runs and the location is appended from PerlCompilerException.buildErrorMessage().

4. Internals::hv_clear_placeholders no-op

Const::Fast calls &Internals::hv_clear_placeholders(\%h) when sealing a hash readonly. PerlOnJava doesn't model Perl's restricted-hash placeholder slots, so there's nothing to clear, but failing with "Undefined subroutine" aborts every call site.

Fix: register it as a no-op stub.

Test plan

  • make (full unit-test suite): green.
  • ./jcpan -t GitLab::API::v4 now reaches and passes the module's own t/unit.t (Files=3, Tests=2, Result: PASS). Was: fatal compile error.
  • Const::Fast t/10-basics.t goes from 5/15 passing (compilation bailed early on hv_clear_placeholders) to 17/25 passing.

The remaining 8 Const::Fast failures are deeper readonly-hash semantics and reassignment-detection (Internals::SvREADONLY returning false on \\do{45} levels, Attempt to reassign a readonly variable not surfacing) — out of scope here, but worth tracking separately.

Generated with Devin

Investigating `jcpan -t GitLab::API::v4` surfaced four independent
defects. Each one was masking the next, so they all need to land
together for the module to load and its unit tests to pass.

1. Parser: `use Foo qw()` must skip import (StatementParser.java)

   Real Perl treats both `use Foo ()` and `use Foo qw()` as the
   "skip import" form. PerlOnJava only special-cased `()` (parens),
   so `use Carp qw();` ran `Carp->import()` with no args, which
   Exporter interprets as "import @export defaults" and leaks
   `Carp::croak` into the caller. That made `sub croak { ... }` in
   GitLab::API::v4::RESTClient look like a redefinition.

   Fix: also recognise a syntactically empty list expression after
   the module name (e.g. `qw()`, `((), qw())`) by walking the parsed
   AST for ListNode emptiness, gated on the parser actually consuming
   tokens. `use Foo @runtime_empty_list` still calls import(), as in
   real Perl.

2. HTTP::Tiny: stop pretending to export `new`/`get`/`post`/`request`
   (HttpTiny.java)

   Real `HTTP::Tiny` exports nothing — these are object methods.
   PerlOnJava's HTTP::Tiny added them to @export, so any package
   that did `use HTTP::Tiny;` got `Foo::new` aliased to
   `HTTP::Tiny::new`. Moo's assert_constructor then saw a
   pre-existing `new` and bailed with "Unknown constructor for Foo
   already exists" before installing its own constructor, breaking
   any Moo class that mentioned HTTP::Tiny.

   Fix: drop the bogus `defineExport("EXPORT", ...)`. (This bug was
   pre-existing; defect #1 made compilation fail earlier and hid it.)

3. Read-only error message format (RuntimeScalarReadOnly.java)

   `RuntimeScalarReadOnly.vivify()` threw a plain
   `RuntimeException`, which makes stringifyException() fall through
   to the multi-line stack-trace formatter:

       Modification of a read-only value attempted
               main at FILE line N
               main at FILE line N

   Real Perl, and Test::Fatal-style regex matchers in Const::Fast's
   t/10-basics.t, expect the canonical single-line form:

       Modification of a read-only value attempted at FILE line N.

   Fix: throw `PerlCompilerException` instead so the short-circuit
   path in stringifyException() runs and the location is appended
   from PerlCompilerException's buildErrorMessage().

4. Internals::hv_clear_placeholders no-op (Internals.java)

   Const::Fast calls `&Internals::hv_clear_placeholders(\%h)` when
   sealing a hash readonly. PerlOnJava doesn't model Perl's
   restricted-hash placeholder slots, so there's nothing to clear,
   but failing with "Undefined subroutine" aborts every call site.

   Fix: register it as a no-op stub.

Verification

- `make` (full unit-test suite): green.
- `./jcpan -t GitLab::API::v4` now reaches and passes the module's
  own t/unit.t (was: fatal compile error).
- Const::Fast t/10-basics.t goes from 5/15 passing (compilation
  bailed early on hv_clear_placeholders) to 17/25 passing. The
  remaining 8 failures are deeper readonly-hash and reassignment
  semantics that are out of scope here.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit 00acf91 into master Apr 28, 2026
2 checks passed
@fglock fglock deleted the fix/gitlab-api-v4-and-const-fast branch April 28, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant