Skip to content

Improving Bean Shell Support in Playground#4780

Open
shai-almog wants to merge 76 commits intomasterfrom
feat/bsh-java-parity-matrix
Open

Improving Bean Shell Support in Playground#4780
shai-almog wants to merge 76 commits intomasterfrom
feat/bsh-java-parity-matrix

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

No description provided.

shai-almog and others added 30 commits April 19, 2026 20:12
Static nested classes
- ScriptedClass.build now recognises nested BSHClassDeclaration children
  with the `static` modifier and evaluates them eagerly against the
  parent's static namespace.
- BSHAllocationExpression.lookupScriptedClass walks dotted names like
  `Outer.Inner` by resolving the first segment as a ScriptedClass and
  then descending through each enclosing class's static namespace.
- Manual `.`-split (avoid CN1's restricted String.split which fails the
  compliance check).
- `class_decl/static_inner` flips EVAL_ERROR -> SUCCESS.

Top-level anonymous SAM rewrite
- New rewriteTopLevelAnonSams pass converts statement-level
  `new Runnable() { public void run() {...} }` and similar
  expressions for the SAM types LambdaValue implements
  (Runnable, Supplier, Consumer, BiConsumer, Function, Predicate,
  Comparator) into __lambdaSupport.lambda(...) calls.
- The conversion is restricted to that hard-coded set so it doesn't
  intercept anonymous implementations of user-declared scripted
  interfaces (those have their own anon-class path that needs the user's
  method bodies).
- `class_decl/inner_anonymous_runnable` flips EVAL_ERROR -> SUCCESS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously enum constants were materialised with an empty backing
namespace and a single __enumName__ field. Constants like
`LOW(1), HIGH(10)` ignored their arguments, so reading `Prio.LOW.v`
returned the field's default rather than 1.

ScriptedClass.populateEnumConstants now uses the standard newInstance
path for each constant, threading the declared constructor arguments
through. Field initialisers and the constructor body run normally.
__enumName__ is set on the resulting instance namespace afterwards.

Per-constant body blocks (Op.ADD { public int apply(...) {...} })
remain unsupported — they would need a per-constant subclass with its
own method overrides.

Matrix
- enum_decl/with_fields_unsupported renames to with_fields and flips
  EVAL_ERROR -> SUCCESS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The arrow-form rewrite already turned `case <e> -> <r>;` into
`case <e>: var = <r>; break;`. The yield form preserves traditional
case-label structure but uses `yield <expr>;` to emit the result —
which BSH's existing parser doesn't recognise.

PlaygroundRunner now post-processes switch-expression bodies that
don't contain arrows by replacing each top-level
`yield <expr>;` with `<varName> = <expr>; break;`. Combined with the
existing declaration-hoisting, this lets users write:

  String s = switch (x) {
      case 1: yield "one";
      default: yield "?";
  };

Limitations
- The yield rewrite is a flat scan that ignores switch nesting; a
  yield inside an inner switch expression would also be rewritten with
  the outer variable, which is wrong. This rare combination is left as
  a follow-up.

Matrix
- switch_expr/yield_form_unsupported renames to yield_form and flips
  PARSE_ERROR -> SUCCESS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`enum Op { ADD { public int apply(...) {...} } ... }` — each constant
can now have its own block body that supplies method overrides. The
constant becomes an instance of an anonymous subclass whose parent is
the declared enum type.

Implementation
- ScriptedClass.populateEnumConstants checks each BSHEnumConstant for a
  BSHBlock child. When present, build an anonymous subclass via the
  existing ScriptedClass.build path (declaring namespace = enum's
  declaring scope, parent = the enum class itself, body = the
  per-constant block) and instantiate it. When absent, instantiate the
  enum class directly.
- The enum class's own non-abstract default method (e.g.
  `public int apply(int a, int b) { return 0; }`) is inherited by
  constants without an override, satisfying call sites that don't
  override on every constant. Abstract methods are documented as
  unsupported.

Matrix
- enum_decl/with_method_per_constant_unsupported renames to
  with_method_per_constant and flips EVAL_ERROR -> SUCCESS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Now that LambdaValue implements common java.util.function SAMs
(Runnable, Function, Predicate, Comparator, Supplier, Consumer,
BiConsumer), registry-dispatched method calls like
Collections.sort(list, comparator) should accept a lambda directly —
but the generator's matchesType check returned false for any
LambdaValue not in a hardcoded SAM-bridge list, so calls that wanted a
java.util.Comparator (which LambdaValue implements) were rejected.

Generator change
- matchesType now also accepts LambdaValue when type.isInstance(value)
  is true, in addition to the existing CN1-specific listener bridge
  fallback.
- adaptValue takes a fast path that returns the LambdaValue unchanged
  when the target type is one it directly implements, instead of
  routing through adaptLambdaValue's hardcoded wrappers.
- Regenerated registry — diff is a one-line change applied to every
  per-package matchesType emit site.

Matrix
- New `integration/lambda_to_collections_sort` case exercises
  Collections.sort with a comparator lambda; was EVAL_ERROR, now SUCCESS.
- Five additional integration cases added (record_in_list,
  two_level_inheritance, interface_with_default_used,
  switch_expr_in_method_return, lambda_method_ref_combo) — all SUCCESS.
- Total cases now 126.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
super(args) in scripted-class constructors
- Inside a subclass ctor body, `super(arg1, arg2, ...)` now forwards
  to the matching ctor template on the parent ScriptedClass, bound to
  the current instance namespace. Implementation lives in
  Name.invokeLocalMethod: when the bare method name is "super" and
  there's a ScriptedInstance on the call stack with a parent, dispatch
  to ScriptedClass.findConstructorTemplate(args).
- New matrix case `class_decl/super_constructor_call` exercises a
  base ctor that initializes a field plus a derived ctor that calls
  super(v) and adds its own initialization.

java.util.stream registry inclusion
- Drop `java.util.stream` from the generator's exclusion list so
  Stream/Collector/Collectors are at least *resolvable* as classes.
- The actual stream() entry point is unavailable because CN1's
  java.util.Collection backport doesn't expose stream(). Documented
  with a `stream_unsupported` matrix case.

Total cases now 128. Two remain as documented negative tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Method dispatch on ScriptedInstance previously matched by name+arity
only, so calls like `m.fmt(\"hi\")` against
`class M { String fmt(int n); String fmt(String s); }` picked the first
arity-1 template (the int one) and failed at parameter coercion.

ScriptedClass.findInstanceMethodTemplate now scores same-arity
templates against the actual argument types and returns the highest-
scoring one. Exact class match scores 3, isInstance scores 2,
primitive-compatible scores 2, untyped/Object-typed scores 1, and
mismatch scores -5. Ties resolve to the first declared template.

The primitive-compatibility helper avoids `Boolean.TYPE`/`Character.TYPE`
because CN1's restricted Boolean/Character don't expose those static
fields (compliance check would fail).

Three new integration matrix cases — method_overloading,
ctor_overloading, iterator_walk — all SUCCESS. Total 131.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README
- Replace the "Limitations" section's outdated bullets with an accurate
  capability inventory mirroring what the syntax matrix asserts. The
  old section said classes, interfaces, enums, method references, and
  TWR were unsupported — all of those work now.
- Document the remaining real gaps (non-static inner classes, runtime
  reflection, and Collection.stream()).

Matrix
- New `integration/class_implements_scripted_interface` case verifies
  that a regular class can implement a script-declared interface.
- New `integration/comparator_natural_order` case sorts a list with a
  Comparator lambda built via `(a, b) -> a.compareTo(b)`.
- Total cases now 133.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three bugs surfaced by 10 new advanced integration cases:

Multi-level super dispatch
- `class A -> B -> C` where each B.m() calls super.m() and C.m() calls
  super.m() was infinite-looping. findScriptedThis always returned the
  actual instance's class, so super.m() from inside B resolved to B's
  parent (A) — but ALSO when inside C.m()'s super call into B, the
  nested super.m() still saw C.getClass() and looped back to B.
- Added a ThreadLocal dispatch-class stack on ScriptedClass. Pushed on
  super-method entry, popped on exit. Name's super-dispatch consults
  this stack first, falling back to the instance's class when empty.
  Chained supers now walk up the inheritance chain as expected.

Nested switch expressions
- `switch(...) { case 2 -> switch(...) { ... }; ... }` parse-errored
  because rewriteSwitchExpressions only ran one pass. The inner switch
  was produced textually inside a case body after the outer rewrite
  turned its case body into an assignment, so we never saw it.
- rewriteSwitchExpressions now loops until a fixed point (capped at 16
  iterations as a safety).

Per-constant enum anon subclass visibility + enum-ness
- `enum S { OPEN { ... return CLOSED; ... }, CLOSED { ... } }` failed
  because (a) the anon subclass used the declaring namespace as parent,
  so sibling constants (`CLOSED`) weren't visible from OPEN's body, and
  (b) the anon wasn't marked as enum, so the built-in name()/ordinal()
  dispatch in ScriptedInstance skipped it.
- populateEnumConstants now declares each anon against the enum's
  static namespace (sibling constants resolve) and propagates
  markEnum(true) to the anon.

Ten new integration matrix cases exercise these combinations:
three_level_inheritance, recursion_factorial, enum_implements_interface,
interface_extends_interface, builder_pattern, custom_exception_hierarchy,
nested_switch_expressions, record_with_static_factory,
enum_used_in_switch, state_machine_via_enum. All SUCCESS.

Total cases now 143.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New SUCCESS cases
- integration/field_init_at_declaration — `int x = 42;` in class body
- integration/list_of_records — sorted list of record-typed values
- integration/generic_method_on_class — generic method with T return
- integration/multi_dim_array — nested enhanced-for on int[][]
- integration/interface_with_two_methods — class implementing a
  non-SAM scripted interface with two methods.

New documented gap cases
- integration/twr_with_var_unsupported — `try (var r = ...)` not
  accepted by BSH's TWR grammar (type must be declared explicitly).
- integration/exception_with_cause_chain_unsupported — super(args)
  forwarded to a Java superclass (RuntimeException) is a no-op, so the
  cause chain breaks. Subclassing Java exceptions beyond the single
  message form is best done by throwing a raw RuntimeException.

Total cases now 150.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
record equality, map keyset walk, protected method dispatch — all
SUCCESS. Plus three documented registry gaps:

- map_entryset_iteration — Map.Entry nested interface not exposed
- stringbuilder_* — all StringBuilder ctors missing from registry
  (under investigation; `new StringBuilder(\"\")` and `new
  StringBuilder()` both fail)
- arrays_aslist — varargs Arrays.asList not wired

Each gap has a companion SUCCESS case that uses the supported
workaround (keySet, `+=` concatenation, manual list build).

Total cases now 160; two pre-existing negative tests remain
(enhanced_for/null_array and interface_decl/cannot_instantiate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ApiClass.isLookupOnly() hardcoded `java.lang.StringBuilder` as
lookup-only, meaning the generator emitted only the class entry for it
and no constructor / method dispatch blocks. The comment (no longer
there, removed implicitly by its absence) suggested this was a
workaround for an older emit limitation with StringBuilder's final
varargs append overloads; the current emitter handles those fine.

Dropping the special case lets `new StringBuilder()`,
`new StringBuilder(String)`, and the full append-chain run.

Matrix
- The two previously-EVAL_ERROR stringbuilder cases flip to SUCCESS:
  stringbuilder_no_arg and stringbuilder_with_initial.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New SUCCESS cases
- visitor_pattern — interface + concrete implementation + caller
- factory_method_returning_interface — static factory returning an
  anonymous Shape implementation
- chained_method_calls_returning_this — fluent builder with return this
- static_method_calling_other_static — static dispatch within a class
- generic_container — Box<T> parameterised two different ways
- conditional_with_sideeffects_substitute — ternary using array access
  (without post-increment, which isn't supported on subscripted elements)

New documented gap
- array_element_increment_unsupported — `a[0]++` hits a BSH unary-op
  limitation ("Unary operation `++` inappropriate for object"). The
  workaround (`a[0] = a[0] + 1`) works.

Total cases now 167.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`public static <T> List<T> asList(T... array)` was being dropped
because resolveSimpleName("T") returned null — method-level type
parameters aren't tracked in the class-level typeParameterBounds. Since
generic type parameters erase to Object at runtime anyway, short
all-uppercase names (T, U, V1, K, etc.) that otherwise resolve to
nothing are now treated as java.lang.Object.

This unblocks a range of generic utility methods on JDK classes:
Arrays.asList, Arrays.copyOf(T[],int), Collections methods with
`<T>` signatures, etc.

Matrix
- integration/arrays_aslist_unsupported renames to arrays_aslist and
  flips EVAL_ERROR -> SUCCESS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Array-element access (a[0] on int[]) returns a boxed Integer rather
than a Primitive, so `a[0]++` hit BSHUnaryExpression.unaryOperation's
"inappropriate for object" branch. Same issue for array-of-any-wrapper
and fields returning boxed numerics.

BSHUnaryExpression now wraps Number/Character operands into a
Primitive before delegating to Operators.unaryOperation. The wrap uses
the typed Primitive constructors since the Object-typed one is
private.

Matrix
- array_element_increment_unsupported -> array_element_increment
  (EVAL_ERROR -> SUCCESS).
- New array_element_postfix_decrement case (SUCCESS).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nested lambda bug
- `Function<Integer, Function<Integer, Integer>> adder = a -> b -> a + b;`
  failed with "Unable to parse code syntax" because the outer lambda's
  body was normalised (prepended with `return`) BEFORE its inner lambda
  was rewritten. The normalised body `return b -> a + b;` couldn't be
  re-processed as a lambda expression (the prefix is `return b`, not a
  valid parameter list).
- rewriteTopLevelLambdas now recurses on the raw body before
  normalisation, so nested arrows get unwound outer-to-inner.

New integration cases (all SUCCESS)
- nested_lambdas
- array_access_in_ternary
- class_with_static_field_used_via_instance
- method_ref_in_sort (via Integer.compare inside a comparator)
- long_chain_calls (method chaining on a freshly-constructed generic)
- cast_of_scripted_instance (Object → ScriptedInstance cast)

Total cases now 173.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`try (var in = new StringReader(\"hi\"))` crashed with an NPE inside
BSHAutoCloseable.eval — `var` resolves to null Type, and then
`AutoCloseable.class.isAssignableFrom(null)` threw.

BSHAutoCloseable now:
- Only fails the upfront AutoCloseable check when the type is known
  (declared explicitly). `var` bypasses it.
- After the declarator runs, verifies the bound value actually
  implements AutoCloseable when the type was inferred.

Matrix
- integration/twr_with_var_unsupported renames to twr_with_var and
  flips EVAL_ERROR -> SUCCESS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All SUCCESS. Exercises common real-world patterns:

- exception_across_methods — try/catch inside one method catching an
  exception thrown by another method on the same scripted class.
- return_inside_lambda_block — early-return from a block-bodied lambda.
- multiple_anonymous_impls — two anon implementations of the same
  scripted interface coexisting in the same scope.
- generic_class_with_generic_method — Box<T> with an additional
  method-level type parameter <U>.
- enum_switch_with_yield — yield form for an enum-discriminant switch.
- record_toString_format — implicit record.toString() in `+` context.
- class_with_private_helper_method — private instance method called
  from another instance method on the same class.
- loop_accumulating_into_list — classic for loop filling an ArrayList
  followed by an enhanced-for sum.

Total cases now 181.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- bounded_generic_type — `class NumberBox<T extends Number>` with
  `val.doubleValue()` calling through to Number's method.
- string_equals / string_substring_indexof — common String API patterns.
- integer_parse_and_format — Integer.parseInt round-trip.
- container_with_multiple_buttons — builds a container with listeners
  in a loop and verifies getComponentCount.
- form_layout_switching — creates a FlowLayout container then switches
  its layout to BoxLayout.
- hashset_basic — HashSet dedup.
- linked_list_as_queue — LinkedList.add / removeFirst.

Total cases now 189. Remaining documented gaps: 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Java implicitly widens char to int in numeric contexts, so
`s.indexOf(' ')` on CN1's `indexOf(int ch)` should work — but the
registry's matchesType required value instanceof Number, rejecting
Character. The emitted cast also went through ((Number) x).intValue()
which fails for Character.

Generator changes
- matchesType now accepts Character for byte/short/int/long/float/
  double param slots.
- castValue for those types calls a new emitted toIntValue(Object)
  helper that handles both Number and Character.

Regenerated registry. Matrix includes a new string_indexof_char_widens
case that calls `s.indexOf(' ')` directly.

Total cases now 190.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All SUCCESS. Covers patterns I had not yet exercised:

- instanceof_array_type — `o instanceof int[]`.
- final_local_variable — a `final` local that's used read-only.
- long_arithmetic / bitwise_long_ops — long literals, shifts, bitwise.
- scripted_class_as_map_key — ScriptedInstance as a HashMap key
  (identity hashing is fine, since we don't override equals/hashCode).
- multi_return_paths — method with three early returns.
- this_in_listener_captures_field — a scripted class registering a
  listener on a passed-in button; the listener body mutates the class's
  own field through the captured lambda scope.

Total cases now 197.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All SUCCESS. Exercises:

- triple_nested_enhanced_for — 3D int array traversed with nested
  enhanced-for.
- labeled_outer_break_with_search — classic nested-loop search that
  breaks out of the outer loop via a label.
- try_finally_side_effect — try body and finally both mutating the
  same captured array, verifying finally runs.
- null_check_then_use — null-guard ternary on a local String.
- abstract_template_method — abstract Base.doubled() calls abstract
  getNum() which the subclass supplies (classic template method).
- chained_ternary — nested ternary expressions in a single statement.

Total cases now 203.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`class Dog implements Named` where Named declares a default method
didn't pick up the default — only explicit `extends` merged parent
methods. Now `implements` is walked too:

- BSHClassDeclaration.eval gathers scripted interfaces from the AST
  children after the optional extends-name and passes them alongside
  the parent to ScriptedClass.build.
- ScriptedClass.build merges each interface's instance methods into
  this class's method table when they aren't shadowed by a concrete
  declaration or a parent-class method.
- A new convenience build overload keeps call sites that have no
  interfaces (enum anon subclasses) compiling without changes.

Ten new integration cases (interface_default_calling_abstract, enum
identity/switch patterns, Math operations, rethrow, static field
sharing, 3-level polymorphism, finally-after-return, etc.) — all
SUCCESS.

Total cases now 212.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All SUCCESS. Covers classic design patterns and common API ops:

- observer_pattern — multiple listeners subscribed to a Bus emit-loop
- decorator_pattern — Excited wraps Basic greeter, double-wrap chains
- map_of_list — Map<String, List<String>> with nested generic mutations
- continue_in_loop — basic classic for-loop with continue
- string_starts_ends_with / string_char_at_and_length — common String API
- deeply_nested_conditions — four-level nested if/else
- enum_iter_if_else — E.values() iteration with per-constant behaviour

Total cases now 220.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eight SUCCESS, one documented gap.

New SUCCESS
- generic_interface_scripted — `interface Mapper<T, R>` with anonymous
  one-off impl.
- throwing_scripted_exception — scripted class extending RuntimeException
  used in throw/catch.
- array_literal_in_call — Arrays.asList receiving an explicit
  Integer[] (bypasses varargs inference).
- null_coalesce_via_ternary — simple null-guard ternary.
- sequence_of_if_returns — Validator method with four early returns.
- collections_sort_natural_order — Collections.sort(List) default order.
- class_shadowing_imported_type — script-defined Button shadows CN1's.
- record_with_primitive_components — tests the common primitive case.

Documented gap
- nested_records_unsupported — when a record component's type is
  another scripted class, the pre-declared field (typed Object) isn't
  correctly updated by the canonical ctor's `this.left = left`. Use
  primitive or Java-class component types as a workaround.

Total cases now 229.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`this.field = value` on a ScriptedInstance previously returned a
LOOSETYPE_FIELD LHS whose assign() calls setTypedVariable(varName,
Types.getType(val), ...). When the field had been pre-declared with a
different resolved type (e.g. a scripted-class-typed field resolving
to Object), the re-typed assignment didn't cleanly update the pre-
existing variable. Switching to a VARIABLE LHS (localVar=true) routes
through setLocalVariableOrProperty which updates the value without
re-declaring the variable's type.

The nested-records case that exposed this still has another subtle
interaction (field is resolved on the declaring-namespace chain rather
than the instance-namespace chain when both contain a class called
Inner); leaving as a documented gap for follow-up, but the LHS fix is
a prerequisite.

Matrix is stable at 229 cases; no flips in this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lambda-body passes
- rewriteTopLevelLambdas now also threads the body through
  rewriteSwitchExpressions and rewriteArrowSwitchStatements before
  normalisation. `n -> { String s = switch (n) { ... }; return s; }`
  previously failed at lambda-invocation time because the body was
  stringified verbatim and BSH's eval of it doesn't run the playground
  rewrites. Enables `switch_in_lambda_body` (flipped to SUCCESS).

Null-callstack guards
- BSHType.resolvesToScriptedClass and BSHAllocationExpression
  .lookupScriptedClass now short-circuit when the callstack is null
  rather than NPE'ing. Observed in paths where the allocation is
  exercised through a non-standard entry point (some nested-generic
  field-decl eval).

Matrix
- 10 new cases (visitor-style observer, decorator, nested generic,
  deeply nested conditions, enum iter, internal/external field
  assignment variants...). 238 total; two new documented gaps noted:
  two_classes_typed_cross_ref_unsupported and
  external_field_assignment_unsupported (with internal setter
  workaround demonstrated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SUCCESS
- cn1_container_border_layout — Container with BorderLayout slots.
- cn1_textfield_and_listener — TextField with ActionListener.
- cn1_button_chain_setters — Button UIID + enabled state setters.
- collections_reverse_then_check — Collections.reverse.
- function_chain_via_scripted_class — method dispatch chain in a
  scripted class.
- varargs_like_user_method — `int... xs` in a user-declared class.

Documented gaps
- interface_constant_unqualified_unsupported — interface constants
  aren't visible from an implementing class's method body without
  qualification. Qualify with `Iface.NAME`.
- interface_constant_qualified_unsupported — qualified access also
  fails because static field resolution on scripted interfaces cross-
  references an unresolved field path that hits the same callstack-
  null issue as other external scripted-field access. Tracked with
  the related external_field_assignment / two_classes_cross_ref
  limitations.

Total cases now 246.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Java spec: fields declared in an interface are implicitly
`public static final`. ScriptedClass.build wasn't honouring that —
fields without an explicit `static` modifier were deferred to the
instance-field list, so `Config.DEFAULT_NAME = "world"` inside an
interface was never evaluated and never bound in Config's static
namespace.

ScriptedClass.build now takes a `treatFieldsAsStatic` flag; when an
interface is being declared it's set to true and all field decls are
evaluated into the static namespace. BSHClassDeclaration passes the
flag based on `type == Type.INTERFACE`.

Matrix: interface_constant_qualified flips EVAL_ERROR -> SUCCESS
(246/246). The unqualified companion remains as a documented gap —
implementing classes still don't walk their implemented-interface
static namespaces for bare name lookups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All SUCCESS. Exercises:
- boolean_short_circuit_and / boolean_short_circuit_or — verifying
  that && / || really short-circuit (no side-effect, no div-by-zero).
- scripted_class_returned_from_method — a factory returning a
  scripted-class instance.
- nested_enhanced_for_with_continue — skip-even sum on a 2D array.
- complex_predicate_with_and_or — composing two Predicate lambdas.
- enum_with_multiple_fields — enum constants with two args each.
- enum_method_returning_self — enum method returning enum constants.
- method_parameter_shadowing_field — `this.x + x` disambiguating a
  parameter shadowing a field.

Total cases now 254.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

3 participants