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
22 changes: 11 additions & 11 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ and exposed as the
[`CompilePhase`](src/main/java/org/codehaus/groovy/control/CompilePhase.java)
enum that AST transformations and customizers attach to:

| # | Phase | What happens | Driver classes |
|---|---|---|---|
| 1 | `INITIALIZATION` | Source files opened, `CompilationUnit` configured, customizers applied | `CompilationUnit`, `CompilerConfiguration` |
| 2 | `PARSING` | ANTLR4 lexer + parser produce a CST (parse tree) | `Antlr4ParserPlugin`, `GroovyLangLexer`, `GroovyLangParser` |
| 3 | `CONVERSION` | CST → AST (`ModuleNode` / `ClassNode` / `MethodNode` / ...) | `AstBuilder` |
| 4 | `SEMANTIC_ANALYSIS` | Class resolution, import handling, validity checks the grammar can't catch | `ResolveVisitor`, `StaticImportVisitor`, `AnnotationConstantsVisitor` |
| 5 | `CANONICALIZATION` | Fill in the AST: synthesised members, generic types, most local AST transforms run here | `ASTTransformationVisitor`, `GenericsVisitor` |
| # | Phase | What happens | Driver classes |
|---|---|-----------------------------------------------------------------------------------------|---|
| 1 | `INITIALIZATION` | Source files opened, `CompilationUnit` configured, customizers applied | `CompilationUnit`, `CompilerConfiguration` |
| 2 | `PARSING` | ANTLR4 lexer + parser produce a CST (parse tree) | `Antlr4ParserPlugin`, `GroovyLangLexer`, `GroovyLangParser` |
| 3 | `CONVERSION` | CST → AST (`ModuleNode` / `ClassNode` / `MethodNode` / ...) | `AstBuilder` |
| 4 | `SEMANTIC_ANALYSIS` | Class resolution, import handling, validity checks the grammar can't catch | `ResolveVisitor`, `StaticImportVisitor`, `AnnotationConstantsVisitor` |
| 5 | `CANONICALIZATION` | Fill in the AST: synthesized members, generic types, most local AST transforms run here | `ASTTransformationVisitor`, `GenericsVisitor` |
| 6 | `INSTRUCTION_SELECTION` | Optimisations and instruction-set selection; `@CompileStatic` / `@TypeChecked` run here | `OptimizerVisitor`, `StaticTypeCheckingVisitor` |
| 7 | `CLASS_GENERATION` | AST → bytecode in memory | `AsmClassGenerator`, `Verifier`, classes under `classgen/asm/` |
| 8 | `OUTPUT` | Write generated `.class` files | `CompilationUnit` output stage |
| 9 | `FINALIZATION` | Cleanup, `Janitor` callbacks | `CompilationUnit`, `Janitor` |
| 7 | `CLASS_GENERATION` | AST → bytecode in memory | `AsmClassGenerator`, `Verifier`, classes under `classgen/asm/` |
| 8 | `OUTPUT` | Write generated `.class` files | `CompilationUnit` output stage |
| 9 | `FINALIZATION` | Cleanup, `Janitor` callbacks | `CompilationUnit`, `Janitor` |

Each phase iterates over all `SourceUnit`s before the next phase
begins. AST transformations declare which phase they run in; the
Expand Down Expand Up @@ -140,7 +140,7 @@ verbatim keeps the reference precise; paraphrasing tends to drift.

- `org.codehaus.groovy.classgen.AsmClassGenerator` walks the AST and
emits bytecode via ASM. Supporting visitors run here too:
`Verifier` (synthesises bridge methods, accessors, default
`Verifier` (synthesizes bridge methods, accessors, default
constructors), `EnumVisitor`, `EnumCompletionVisitor`,
`InnerClassVisitor`, `InnerClassCompletionVisitor`,
`VariableScopeVisitor`, `ReturnAdder`.
Expand Down
13 changes: 11 additions & 2 deletions src/antlr/GroovyParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,16 @@ forUpdate
// EXPRESSIONS

castParExpression
: LPAREN type RPAREN
: LPAREN intersectionType RPAREN
;

intersectionType
: type (BITAND nls type)*
;

coercionType
: castParExpression // (T) or (A & B & ...)
| type // T
;

parExpression
Expand Down Expand Up @@ -833,7 +842,7 @@ expression

// boolean relational expressions (level 7)
| left=expression nls op=INSTANCEOF nls matchingType #relationalExprAlt
| left=expression nls op=(AS | NOT_INSTANCEOF) nls type #relationalExprAlt
| left=expression nls op=(AS | NOT_INSTANCEOF) nls coercionType #relationalExprAlt
| left=expression nls op=(LE | GE | GT | LT | IN | NOT_IN) nls right=expression #relationalExprAlt

// equality/inequality (==/!=) (level 8)
Expand Down
47 changes: 42 additions & 5 deletions src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.MultipleAssignmentMetadata;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.IntersectionTypeClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModifierNode;
import org.codehaus.groovy.ast.ModuleNode;
Expand Down Expand Up @@ -2475,7 +2476,32 @@ public Expression visitCommandArgument(final CommandArgumentContext ctx) {

@Override
public ClassNode visitCastParExpression(final CastParExpressionContext ctx) {
return this.visitType(ctx.type());
return this.visitIntersectionType(ctx.intersectionType());
}

@Override
public ClassNode visitIntersectionType(final IntersectionTypeContext ctx) {
List<? extends TypeContext> typeCtxs = ctx.type();
if (typeCtxs.size() == 1) {
return this.visitType(typeCtxs.get(0));
}
ClassNode[] components = new ClassNode[typeCtxs.size()];
Set<String> seenNames = new HashSet<>();
for (int i = 0, n = typeCtxs.size(); i < n; i += 1) {
ClassNode component = this.visitType(typeCtxs.get(i));
if (!seenNames.add(component.getName())) {
throw createParsingFailedException("Duplicate type in intersection: " + component.getName(), ctx);
}
components[i] = component;
}
return configureAST(new IntersectionTypeClassNode(components), ctx);
}

@Override
public ClassNode visitCoercionType(final CoercionTypeContext ctx) {
return ctx.castParExpression() != null
? this.visitCastParExpression(ctx.castParExpression())
: this.visitType(ctx.type());
}

@Override
Expand Down Expand Up @@ -3204,7 +3230,7 @@ public Expression visitRelationalExprAlt(final RelationalExprAltContext ctx) {
if (expr instanceof VariableExpression && ((VariableExpression) expr).isSuperExpression()) {
throw this.createParsingFailedException("Cannot cast or coerce `super`", ctx); // GROOVY-9391
}
Expression cast = CastExpression.asExpression(this.visitType(ctx.type()), expr);
Expression cast = CastExpression.asExpression(this.visitCoercionType(ctx.coercionType()), expr);
return configureAST(
cast,
ctx);
Expand All @@ -3218,14 +3244,25 @@ public Expression visitRelationalExprAlt(final RelationalExprAltContext ctx) {
this.visitMatchingType(ctx.matchingType())),
ctx);

case NOT_INSTANCEOF:
ctx.type().putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE);
case NOT_INSTANCEOF: {
CoercionTypeContext coercionCtx = ctx.coercionType();
if (coercionCtx.castParExpression() != null
&& coercionCtx.castParExpression().intersectionType().type().size() > 1) {
throw this.createParsingFailedException("Intersection types are not supported as the right-hand side of !instanceof", ctx);
}
ClassNode notInstType = this.visitCoercionType(coercionCtx);
// GROOVY-11998: keep IS_INSIDE_INSTANCEOF_EXPR on the parser context for the resolver
(coercionCtx.type() != null
? coercionCtx.type()
: coercionCtx.castParExpression().intersectionType().type(0)
).putNodeMetaData(IS_INSIDE_INSTANCEOF_EXPR, Boolean.TRUE);
return configureAST(
new BinaryExpression(
(Expression) this.visit(ctx.left),
this.createGroovyToken(ctx.op),
configureAST(new ClassExpression(this.visitType(ctx.type())), ctx.type())),
configureAST(new ClassExpression(notInstType), coercionCtx)),
ctx);
}

case GT:
case GE:
Expand Down
103 changes: 103 additions & 0 deletions src/main/java/org/codehaus/groovy/ast/IntersectionTypeClassNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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.ast;

import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;

import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;

/**
* Represents a user-written intersection type used as the target of a cast
* expression or {@code as} coercion, e.g.
* <pre>
* (Runnable &amp; Serializable) () -&gt; ...
* value as (A &amp; B)
* </pre>
*
* <p>Distinct from the implicit lowest-upper-bound nodes that
* {@link org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor}
* synthesizes during inference: an instance of this class records the ordered
* list of components exactly as written by the user. That ordering is needed
* for cast-conversion checks, error messages and (in later phases) bytecode
* generation via {@code LambdaMetafactory.altMetafactory} markers.
*
* <p>Lifecycle: at parse time the components have not yet been resolved to
* bound {@link ClassNode}s, so the constructor places all components in the
* inherited {@link #getInterfaces() interfaces} array with {@code Object} as
* the placeholder superclass. After {@code ResolveVisitor} resolves each
* component it should call {@link #reclassifyComponents()} so that the
* interfaces array contains only interface components and the superclass is
* the (at most one) class component.
*
* @since 6.0.0
*/
public final class IntersectionTypeClassNode extends ClassNode {

private final ClassNode[] components;

public IntersectionTypeClassNode(final ClassNode[] components) {
super("IntersectionType", ACC_PUBLIC | ACC_FINAL, ClassHelper.OBJECT_TYPE, components.clone(), MixinNode.EMPTY_ARRAY);
if (components.length < 2) {
throw new IllegalArgumentException("IntersectionTypeClassNode requires at least two components");
}
this.components = components.clone();
}

/**
* Returns the components of this intersection type in user-written order.
*/
public ClassNode[] getComponents() {
return components.clone();
}

/**
* Reclassifies the components after resolution: separates the (at most
* one) class component from the interface components and updates the
* inherited superclass and interfaces accordingly. Components are
* resolved in place — callers do not need to substitute new instances.
*/
public void reclassifyComponents() {
ClassNode klass = null;
List<ClassNode> ifaces = new ArrayList<>(components.length);
for (ClassNode c : components) {
if (c.isInterface()) {
ifaces.add(c);
} else {
klass = c; // STC will validate "at most one" elsewhere
}
}
setSuperClass(klass != null ? klass : ClassHelper.OBJECT_TYPE);
setInterfaces(ifaces.toArray(ClassNode.EMPTY_ARRAY));
}

@Override
public String getText() {
StringJoiner sj = new StringJoiner(" & ", "(", ")");
for (ClassNode c : components) sj.add(c.toString(false));
return sj.toString();
}

@Override
public String toString(final boolean showRedirect) {
return getText();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.GStringExpression;
import org.codehaus.groovy.ast.IntersectionTypeClassNode;
import org.codehaus.groovy.ast.expr.LambdaExpression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
Expand Down Expand Up @@ -1000,9 +1001,27 @@ public void visitBitwiseNegationExpression(final BitwiseNegationExpression expre
@Override
public void visitCastExpression(final CastExpression castExpression) {
Expression expression = castExpression.getExpression();
ClassNode type = castExpression.getType();

// GROOVY-11998: lambda / method-reference factory invocations already
// emit an object that implements every component of an intersection
// target via altMetafactory FLAG_MARKERS, so the outer cast is a no-op.
if (type instanceof IntersectionTypeClassNode
&& (expression instanceof LambdaExpression
|| expression instanceof MethodReferenceExpression)) {
expression.visit(this);
return;
}
// GROOVY-11998: non-functional intersection casts route through the
// runtime helper IntersectionCastSupport, which strict-casts for `()`
// and may build a multi-interface proxy via ProxyGenerator for `as`.
if (type instanceof IntersectionTypeClassNode) {
emitIntersectionCastCall((IntersectionTypeClassNode) type, expression, castExpression.isCoerce());
return;
}

expression.visit(this);

ClassNode type = castExpression.getType();
if (isObjectType(type)) return;
maybeInnerClassEntry(type);

Expand All @@ -1019,6 +1038,47 @@ public void visitCastExpression(final CastExpression castExpression) {
}
}

/**
* Emits a call to {@link org.codehaus.groovy.runtime.IntersectionCastSupport}
* for an intersection-target cast or coercion on a non-functional source.
*
* Bytecode layout:
* <pre>
* visit(source) // pushes source on the JVM stack
* bipush/iconst N // component count
* anewarray Class // create Class[N]
* for each component i:
* dup; bipush i; ldc Type; aastore
* invokestatic IntersectionCastSupport.{castTo|asType}(Object, Class[]) Object
* </pre>
*/
private void emitIntersectionCastCall(final IntersectionTypeClassNode it, final Expression source, final boolean coerce) {
source.visit(this); // leaves source on the operand / JVM stack as Object-ish
controller.getOperandStack().box(); // ensure boxed reference (handles primitive sources)

MethodVisitor mv = controller.getMethodVisitor();
ClassNode[] components = it.getComponents();

BytecodeHelper.pushConstant(mv, components.length);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
for (int i = 0; i < components.length; i += 1) {
mv.visitInsn(DUP);
BytecodeHelper.pushConstant(mv, i);
BytecodeHelper.visitClassLiteral(mv, components[i]);
mv.visitInsn(AASTORE);
}

mv.visitMethodInsn(
INVOKESTATIC,
"org/codehaus/groovy/runtime/IntersectionCastSupport",
coerce ? "asType" : "castTo",
"(Ljava/lang/Object;[Ljava/lang/Class;)Ljava/lang/Object;",
false
);

controller.getOperandStack().replace(ClassHelper.OBJECT_TYPE);
}

@Override
public void visitNotExpression(final NotExpression expression) {
controller.getUnaryExpressionHelper().writeNotExpression(expression);
Expand Down
Loading
Loading