package com.redhat.ceylon.compiler.java.codegen; import java.util.Arrays; import com.redhat.ceylon.common.Backend; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.sun.tools.javac.tree.JCTree.JCExpression; /** * <p>Thrown when the code generator is in a seemingly impossible situation * (i.e. failed assertion of some kind) when it's not feasible to * return {@link AbstractTransformer#makeErroneous(Node, String) JCErroneous} * (because there's no Node to hand, or the method does not return * {@code JCExpression}). Returning {@code JCErroneous} is generally * preferable since it results it just one downstream javac error, whereas * failing to generate a declaration will cause downstream javac errors * for every use site</p> * * <h3>At {@code throw} sites</h3> * <p>The various {@code static} * factory methods provide convenient ways to create instances for * common "impossible" circumstances.</p> * * <h3>At {@code catch} sites</h3> * <p>If there is no {@link Node} available at the {@code throw} site, * the exception can be {@linkplain #associate(Node) associated} * with one when it is caught.</p> * * <p>{@link #addError(Node)} is used to add an error in to the Ceylon tree * for subsequent reporting via the {@code Log}. * Alternativly instances can be conveniently transformed into * a {@code JCErroneous}. In either case it is only possible to add the err * to the Ceylon tree once.</p> * * @see AbstractTransformer#makeErroneous(Node, String) */ public class BugException extends RuntimeException { private Node node; private boolean attached; public BugException() { this(null); } public BugException(String message) { this(message, null); } public BugException(String message, Exception cause) { this(null, message, cause); } public BugException(Node node, String message) { this(node, message, null); } public BugException(Node node, String message, Exception cause) { super(message == null ? "assertion failed" : message, cause); // This is nasty: When we print the errors out following codegen // we don't want to dump the whole stack, just the first frame // the factory methods would occupy their own uninformative frame // so filter out the first frames until we reach one which is not // from our class StackTraceElement[] stackTrace = getStackTrace(); String myName = BugException.class.getName(); int from = 0; for (StackTraceElement frame : stackTrace) { if (!myName.equals(frame.getClassName())) { break; } from++; } setStackTrace(Arrays.copyOfRange(stackTrace, from, stackTrace.length)); this.node = node; } /** * Associate this exception with the given node if this exception is * not already associated with a node. */ public void associate(Node node) { if (this.node == null) { this.node = node; } } private Node bestNode(Node fallbackNode) { return this.node != null ? this.node : fallbackNode; } /** * Add an error to the a node. A {@link CodeGenError} * is created with the exception's message and the exception as cause, * and is added to the assoicated node (if there is one), and otherwise * the given node. * @param fallbackNode */ public void addError(Node fallbackNode) { if (!this.attached) { Node bestNode = bestNode(fallbackNode); bestNode.addError(new CodeGenError(bestNode, getMessage(), Backend.Java, this)); this.attached = true; } } /** * Returns an erroneous node based on the exceptions message, having * {@linkplain #addError(Node) added an error to the tree}. * @param gen * @param fallbackNode * @return */ public JCExpression makeErroneous(AbstractTransformer gen, Node fallbackNode) { addError(fallbackNode); return gen.makeErroneous(bestNode(fallbackNode), getMessage()); } /** * <p>Used at the end of a {@code if (x instanceof Foo)}/{@code else if (x instanceof Bar)} * chain on Node types</pre> */ public static BugException unhandledNodeCase(Node node) { return new BugException("unhandled node type " + node.getNodeType()); } /** * <p>Used at the end of a {@code if (x instanceof Foo)}/{@code else if (x instanceof Bar)} * chain on declaration types</p> */ public static BugException unhandledDeclarationCase(Declaration d) { return unhandledDeclarationCase(d, null); } public static BugException unhandledDeclarationCase(Declaration d, Node node) { return new BugException(node, "unhandled declaration " + d.getQualifiedNameString() + " with type " + (d == null ? "null" : d.getClass().getSimpleName())); } /** * <p>Used as the default case of an enum {@code switch} or at the end of a * {@code if (x instanceof Foo)}/{@code else if (x instanceof Bar)} * chain on enum elements.</p> */ public static <E extends Enum<E>> BugException unhandledEnumCase(Enum<E> element) { return new BugException("unhandled enum case " + element); } /** * Used at the end of the * {@code if (x instanceof Foo)}/{@code else if (x instanceof Bar)} chain */ public static BugException unhandledCase(Object o) { return new BugException("unhandled case " + (o == null ? "null" : o.getClass().toString())); } }