Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
Expand Down Expand Up @@ -119,6 +120,22 @@ public Expression transform(final Expression exp) {
return transformBinaryExpression((BinaryExpression) exp);
} else if (exp instanceof MethodCallExpression mce) {
String obj = mce.getObjectExpression().getText();
// GROOVY-12104: T.this.m() inside trait code (where T is the
// enclosing trait) compiles successfully today but generates
// invalid or mis-typed bytecode that fails at runtime — Verify-
// Error on 4.x ("Class not assignable to Closure"), ClassCast-
// Exception on 5.x/6.x. T.this has no coherent meaning for
// traits anyway (per GEP-22 § this, super, and stackable traits
// item 1, this is the implementing instance, and the trait is
// not an enclosing scope of its implementer). Reject the
// qualifier at compile time pointing at the existing supported
// alternatives.
if (isTraitThisQualifier(mce.getObjectExpression())) {
unit.addError(new SyntaxException(
"'" + traitClass.getNameWithoutPackage() + ".this' is not allowed inside trait code; use 'this." + mce.getMethodAsString() + "(...)' for normal dispatch, or '" + traitClass.getNameWithoutPackage() + ".super." + mce.getMethodAsString() + "(...)' for explicit trait-anchored dispatch",
mce.getLineNumber(), mce.getColumnNumber()));
return mce;
}
if ("super".equals(obj)) {
return transformSuperMethodCall(mce); // super.m(x) --> $self.Ttrait$super$m(x)
} else if ("this".equals(obj)) {
Expand Down Expand Up @@ -152,6 +169,14 @@ public Expression transform(final Expression exp) {
}
} else if (exp instanceof PropertyExpression pexp) {
String obj = pexp.getObjectExpression().getText();
// GROOVY-12104: T.this.field qualifier — same rejection rationale
// as the method-call form above. Reject at compile time.
if (isTraitThisQualifier(pexp.getObjectExpression())) {
unit.addError(new SyntaxException(
"'" + traitClass.getNameWithoutPackage() + ".this' is not allowed inside trait code; use 'this." + pexp.getPropertyAsString() + "' for the field reference",
pexp.getLineNumber(), pexp.getColumnNumber()));
return pexp;
}
if (pexp.isImplicitThis() || "this".equals(obj)) {
String propName = pexp.getPropertyAsString();
if (knownFields.contains(propName)) {
Expand Down Expand Up @@ -260,6 +285,21 @@ private void throwSuperError(final ASTNode node) {
unit.addError(new SyntaxException("Call to super is not allowed in a trait", node.getLineNumber(), node.getColumnNumber()));
}

/**
* Returns {@code true} if the given expression has the shape
* {@code <traitClass>.this} — i.e. a {@link PropertyExpression} whose
* object is a {@link ClassExpression} of the enclosing trait class
* and whose property is the literal "this". Used by GROOVY-12104's
* compile-time rejection of {@code T.this.*} qualifier syntax in
* trait code.
*/
private boolean isTraitThisQualifier(final Expression exp) {
if (!(exp instanceof PropertyExpression pe)) return false;
if (!"this".equals(pe.getPropertyAsString())) return false;
if (!(pe.getObjectExpression() instanceof ClassExpression ce)) return false;
return ce.getType().equals(traitClass);
}

private Expression transformSuperMethodCall(final MethodCallExpression call) {
String method = call.getMethodAsString();
if (method == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.transform.traitx

import org.codehaus.groovy.control.MultipleCompilationErrorsException
import org.junit.Test

import static groovy.test.GroovyAssert.assertScript
import static groovy.test.GroovyAssert.shouldFail

/**
* {@code T.this.*} qualifier syntax inside trait code is rejected at
* compile time (GROOVY-12104). Without the reject, the call previously
* compiled successfully and generated invalid or mis-typed bytecode that
* failed at runtime: VerifyError on Groovy 4.x ("Class not assignable to
* Closure"), ClassCastException at runtime on Groovy 5.x/6.x.
*
* <p>{@code T.this} has no coherent meaning for traits — per GEP-22
* § this, super, and stackable traits item 1, {@code this} is the
* implementing instance and the trait is not an enclosing scope of its
* implementer. The supported alternatives are existing documented
* features: {@code this.m(...)} for normal dispatch (override-visible
* via row-1 dispatch), {@code T.super.m(...)} for explicit
* trait-anchored dispatch.
*/
final class Groovy12104 {

/** T.this.m() in a static trait method → compile error. */
@Test
void TthisMethodCall_inStaticTraitMethod_isCompileError() {
def err = shouldFail(MultipleCompilationErrorsException) {
assertScript '''
trait V {
static boolean defaultNullable() { false }
static seen() { V.this.defaultNullable() } // <-- rejected
}
class Over implements V { static boolean defaultNullable() { true } }
Over.seen()
'''
}
assert err.message.contains("'V.this' is not allowed inside trait code")
assert err.message.contains('this.defaultNullable(')
assert err.message.contains('V.super.defaultNullable(')
}

/** T.this.m() in an instance trait method → compile error too. */
@Test
void TthisMethodCall_inInstanceTraitMethod_isCompileError() {
def err = shouldFail(MultipleCompilationErrorsException) {
assertScript '''
trait V {
boolean defaultNullable() { false }
def seen() { V.this.defaultNullable() } // <-- rejected
}
class Over implements V { boolean defaultNullable() { true } }
new Over().seen()
'''
}
assert err.message.contains("'V.this' is not allowed inside trait code")
}

/** T.this.field in a trait static method → compile error. */
@Test
void TthisFieldAccess_inStaticTraitMethod_isCompileError() {
def err = shouldFail(MultipleCompilationErrorsException) {
assertScript '''
trait V {
static String origin = 'trait'
static seen() { V.this.origin } // <-- rejected
}
class Over implements V { }
Over.seen()
'''
}
assert err.message.contains("'V.this' is not allowed inside trait code")
assert err.message.contains('this.origin')
}

/** T.this.field in an instance trait method → compile error too. */
@Test
void TthisFieldAccess_inInstanceTraitMethod_isCompileError() {
def err = shouldFail(MultipleCompilationErrorsException) {
assertScript '''
trait V {
String origin = 'trait'
def seen() { V.this.origin } // <-- rejected
}
class Over implements V { }
new Over().seen()
'''
}
assert err.message.contains("'V.this' is not allowed inside trait code")
}

/** Recommended alternative #1: this.m() — works for normal override-visible dispatch. */
@Test
void thisMethodCall_recommendedAlternative_works() {
assertScript '''
trait V {
static boolean defaultNullable() { false }
static seen() { this.defaultNullable() } // normal dispatch
}
class Over implements V { static boolean defaultNullable() { true } }
assert Over.seen() == true // override visible (GEP-22 § Static members item 4)
'''
}

/** Recommended alternative #2: T.super.m() — works for explicit trait-anchored dispatch. */
@Test
void TsuperMethodCall_recommendedAlternative_works() {
assertScript '''
trait V {
static boolean defaultNullable() { false }
static seen() { V.super.defaultNullable() } // explicit trait-anchored
}
class Over implements V { static boolean defaultNullable() { true } }
assert Over.seen() == false // trait's own copy
'''
}

/**
* Plain (non-trait) class T.this in a static method → already a compile
* error pre-12104 (Java-conventional behaviour). Regression guard that
* the trait-only reject hasn't disturbed this.
*/
@Test
void Tthis_inPlainClassStatic_stillCompileError() {
def err = shouldFail(MultipleCompilationErrorsException) {
assertScript '''
class Outer {
static foo() { 'foo' }
static seen() { Outer.this.foo() }
}
Outer.seen()
'''
}
// Note: the existing compile-error message for plain classes
// differs from the trait-specific one — that's fine; both reject.
assert err != null
}

/**
* Trait static with explicit other-class qualifier (e.g. Locale.getDefault())
* must NOT be rejected — the heuristic targets {@code T.this} specifically,
* not any qualifier-style call.
*/
@Test
void explicitOtherClassQualifier_inTraitStatic_isAllowed() {
assertScript '''
trait V {
static getDefaultLocale() { Locale.getDefault() }
}
class Over implements V { }
assert Over.getDefaultLocale() != null
'''
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -516,16 +516,16 @@ class TraitStaticDispatchMatrix {
assert externalThrows : "row13 inInterface=false: external V.name() should still fail (no interface static)"
}

// ---- Row 14 — T.this.* qualifier in trait code (proposed compile error) ----
// Latent bug surfaced during the GROOVY-12093 alternatives discussion.
// `T.this.m()` inside trait code currently produces invalid bytecode
// (VerifyError on 4.x: "Class not assignable to Closure"; ClassCastException
// at runtime on 5.x/6.x). GEP-22 § "this, super, and stackable traits"
// item 4 proposes rejecting the syntax at compile time. NYI until the
// fix lands; flips red on a build that implements the rejection.
// ---- Row 14 — T.this.* qualifier in trait code (GROOVY-12104) ----
// `T.this.m()` inside trait code is rejected at compile time. Without
// the reject, the call previously generated invalid bytecode
// (VerifyError on 4.x: "Class not assignable to Closure";
// ClassCastException at runtime on 5.x/6.x). See Groovy12104.groovy
// for focused coverage (instance variants + field-access form +
// regression-guards for explicit other-class qualifiers). This row is
// the matrix-level anchor.
@Test
@NotYetImplemented
void row14_T_this_qualifier_inTrait_shouldBeCompileError_PROPOSED() {
void row14_T_this_qualifier_inTrait_isCompileError() {
GroovyAssert.shouldFail(MultipleCompilationErrorsException) {
ev '''
trait V {
Expand Down
Loading