/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.parser; import gw.lang.reflect.IType; import gw.lang.parser.IParsedElement; import gw.lang.parser.resources.Res; import gw.internal.gosu.parser.expressions.Identifier; import gw.internal.gosu.parser.expressions.MemberAccess; import gw.internal.gosu.parser.expressions.BeanMethodCallExpression; import gw.internal.gosu.parser.expressions.StaticTypeOfExpression; import gw.internal.gosu.parser.expressions.TypeOfExpression; import gw.internal.gosu.parser.expressions.TemplateStringLiteral; import gw.internal.gosu.parser.expressions.AdditiveExpression; import gw.internal.gosu.parser.expressions.ImplicitTypeAsExpression; import gw.internal.gosu.parser.statements.ReturnStatement; import gw.internal.gosu.parser.statements.StatementList; import gw.lang.reflect.java.JavaTypes; import gw.util.Stack; import gw.util.GosuObjectUtil; import java.util.HashSet; import java.util.Arrays; import java.util.List; import java.util.ArrayList; import java.util.Iterator; public class ContextInferenceManager { private static final boolean ENABLED = true; private static final TypeAsContext EMPTY_CTX = new TypeAsContext(); private Stack<TypeAsContext> _inferenceStack = new Stack<TypeAsContext>(); private TypeAsContext _last; private boolean _refCollectionSuspended; public ContextInferenceManager copy() { ContextInferenceManager copy = new ContextInferenceManager(); copy._inferenceStack = new Stack<TypeAsContext>( _inferenceStack ); copy._last = _last; copy._refCollectionSuspended = false; return copy; } public void pushCtx() { if( ENABLED ) { _inferenceStack.push( new TypeAsContext() ); } } public void popCtx( boolean preserveInference ) { if( ENABLED ) { TypeAsContext last = _inferenceStack.pop(); if( preserveInference ) { _last = last; } } } public void pushLastCtx() { if( ENABLED ) { if( _last == null ) { _last = EMPTY_CTX; } _inferenceStack.push( _last ); } } public void restoreLastCtx() { if( ENABLED ) { if( !_inferenceStack.isEmpty() ) { _inferenceStack.peek().merge( _last ); } } } public void clear() { if( ENABLED ) { _inferenceStack.peek().entries.clear(); } } public void updateType( Expression expression, IType typeIsType ) { if( ENABLED ) { expression = unwrapParens( expression ); TypeAsEntry currentEntry = findEntry( expression ); IType type = currentEntry == null ? expression.getType() : currentEntry.inferredType; type = type.isAssignableFrom( typeIsType ) ? typeIsType : CompoundType.get( new HashSet<IType>( Arrays.asList( type, typeIsType ) ) ); if( currentEntry == null ) { _inferenceStack.peek().entries.add( new TypeAsEntry( expression, expression.getType(), type ) ); } else if( _inferenceStack.peek().entries.contains( currentEntry ) ) { currentEntry.inferredType = type; } else { _inferenceStack.peek().entries.add( new TypeAsEntry( currentEntry.expr, currentEntry.originalType, type ) ); } } } private Expression unwrapParens( Expression expression ) { if( expression instanceof ParenthesizedExpression ) { return unwrapParens( ((ParenthesizedExpression)expression).getExpression() ); } return expression; } public IType infer( Expression e ) { IType inferType = null; if( ENABLED ) { TypeAsEntry entry = findEntry( e ); if( entry != null ) { inferType = entry.inferredType; if( e instanceof Identifier ) { if( entry.loopCompromised > 0 && !_refCollectionSuspended) { entry.refs.add( e ); } } if( e instanceof MemberAccess ) { if( entry.loopCompromised > 0 && !_refCollectionSuspended ) { entry.refs.add( e ); } } } } return inferType; } public void cancelInferences( Expression assignmentRoot, Expression rhs ) { if( ENABLED ) { for( int i = _inferenceStack.size() - 1; i >= 0; i-- ) { for( Iterator<TypeAsEntry> it = _inferenceStack.get( i ).entries.iterator(); it.hasNext(); ) { TypeAsEntry typeAsEntry = it.next(); if( areExpressionsEquivalent( assignmentRoot, typeAsEntry.expr ) ) { if( !typeAsEntry.inferredType.isAssignableFrom( rhs.getType() ) ) { it.remove(); handleLoopCompromisedExpressions( typeAsEntry, assignmentRoot ); assignmentRoot.setType( typeAsEntry.originalType ); } } else if( isStartFor( assignmentRoot, typeAsEntry.expr ) ) { if( !typeAsEntry.inferredType.isAssignableFrom( rhs.getType() ) ) { it.remove(); handleLoopCompromisedExpressions( typeAsEntry, assignmentRoot ); } } } } } } private void handleLoopCompromisedExpressions( TypeAsEntry typeAsEntry, Expression assignmentRoot ) { if( typeAsEntry.loopCompromised > 0 ) { for( Expression expression : typeAsEntry.refs ) { reverifyExpression( typeAsEntry, assignmentRoot, expression ); } } } private void reverifyExpression( TypeAsEntry typeAsEntry, Expression assignmentRoot, Expression expression ) { if( expression.equals( assignmentRoot ) ) { return; } IParsedElement parent = unwrapImplicitTypeAs( expression ); expression.setType( typeAsEntry.originalType ); if( parent instanceof MemberAccess ) { MemberAccess ma = (MemberAccess)parent; if( typeAsEntry.originalType.getTypeInfo().getProperty( ma.getMemberName() ) == null ) { expression.addParseException( Res.MSG_LATER_ASSIGNMENT_MAKES_EXPRESSION_ILLEGAL, assignmentRoot.toString() ); } } else if( parent instanceof BeanMethodCallExpression ) { BeanMethodCallExpression methodCallExpression = (BeanMethodCallExpression)parent; if( methodCallExpression.getRootExpression().equals( expression ) ) { if( methodCallExpression.getMethodDescriptor() != null && !methodCallExpression.getMethodDescriptor().getOwnersType().isAssignableFrom( typeAsEntry.originalType ) ) { expression.addParseException( Res.MSG_LATER_ASSIGNMENT_MAKES_EXPRESSION_ILLEGAL, assignmentRoot.toString() ); } } else { Expression[] args = methodCallExpression.getArgs(); for( int i = 0; i < args.length; i++ ) { Expression arg = args[i]; if( arg.equals( expression ) ) { if( !methodCallExpression.getMethodDescriptor().getParameters()[i].getFeatureType().isAssignableFrom( typeAsEntry.originalType ) ) { expression.addParseException( Res.MSG_LATER_ASSIGNMENT_MAKES_EXPRESSION_ILLEGAL, assignmentRoot.toString() ); } } } } } else if( parent instanceof ReturnStatement ) { ReturnStatement returnStmt = (ReturnStatement)parent; if( returnStmt.getReturnType() != null && !returnStmt.getReturnType().isAssignableFrom( typeAsEntry.originalType ) ) { expression.addParseException( Res.MSG_LATER_ASSIGNMENT_MAKES_EXPRESSION_ILLEGAL, assignmentRoot.toString() ); } } else { if( parent instanceof StaticTypeOfExpression || parent instanceof TypeOfExpression || (parent instanceof StatementList && parent.getParent() instanceof TemplateStringLiteral) ) { return; } if( parent instanceof AdditiveExpression && parent.getReturnType().equals( JavaTypes.STRING() ) ) { return; } expression.addParseException( Res.MSG_LATER_ASSIGNMENT_MAKES_EXPRESSION_ILLEGAL, assignmentRoot.toString() ); } } public static IParsedElement unwrapImplicitTypeAs( Expression expression ) { IParsedElement parent = expression.getParent(); if( parent instanceof ImplicitTypeAsExpression ) { ImplicitTypeAsExpression typeas = (ImplicitTypeAsExpression)parent; parent = typeas.getParent(); if( parent != null ) { parent.getLocation().removeChild( typeas.getLocation() ); } typeas.getLocation().removeChild( expression.getLocation() ); if( parent != null ) { parent.getLocation().addChild( expression.getLocation() ); } } return parent; } /** * If the expression is wrapped in ImplicitTypeAsExpressions, this will will unwrap them back * down to the original expression. * * @param expression * @return */ public static Expression getUnwrappedExpression( Expression expression ) { while( expression instanceof ImplicitTypeAsExpression ) { expression = ((ImplicitTypeAsExpression) expression).getLHS(); } return expression; } private boolean isStartFor( Expression possibleStart, Expression expression ) { if( areExpressionsEquivalent( possibleStart, expression ) ) { return true; } else if( expression instanceof MemberAccess ) { return isStartFor( possibleStart, ((MemberAccess)expression).getRootExpression() ); } else { return false; } } private TypeAsEntry findEntry( Expression e ) { for( int i = _inferenceStack.size() - 1; i >= 0; i-- ) { List<TypeAsEntry> entries = _inferenceStack.get(i).entries; for (int j = 0; j < entries.size(); j++) { TypeAsEntry entry = entries.get(j); if( areExpressionsEquivalent( e, entry.expr ) ) { return entry; } } } return null; } private boolean areExpressionsEquivalent( Expression e1, Expression e2 ) { if( e1.hasParseExceptions() || e2.hasParseIssues() ) { return false; } else if( e1 instanceof Identifier && e2 instanceof Identifier ) { return ((Identifier)e1).getSymbol().equals( ((Identifier)e2).getSymbol() ); } else if( e1 instanceof MemberAccess && e2 instanceof MemberAccess ) { MemberAccess m1 = (MemberAccess)e1; MemberAccess m2 = (MemberAccess)e2; return areExpressionsEquivalent( m1.getRootExpression(), m2.getRootExpression() ) && GosuObjectUtil.equals( m1.getPropertyInfo(), m2.getPropertyInfo() ); } else if (e1 instanceof ImplicitTypeAsExpression && e2 instanceof ImplicitTypeAsExpression) { ImplicitTypeAsExpression i1 = (ImplicitTypeAsExpression) e1; ImplicitTypeAsExpression i2 = (ImplicitTypeAsExpression) e2; return areExpressionsEquivalent(i1.getLHS(), i2.getLHS()) && GosuObjectUtil.equals(i1.getType(), i2.getType()); } else { return false; } } public void pushLoopCompromised() { for( TypeAsContext typeAsContext : _inferenceStack ) { for( TypeAsEntry entry : typeAsContext.entries ) { entry.loopCompromised++; } } } public void popLoopCompromised() { for( TypeAsContext typeAsContext : _inferenceStack ) { for( TypeAsEntry entry : typeAsContext.entries ) { entry.loopCompromised--; if( entry.loopCompromised == 0 ) { entry.refs.clear(); } } } } public void suspendRefCollection() { _refCollectionSuspended = true; } public void resumeRefCollection() { _refCollectionSuspended = false; } private static class TypeAsContext { public Statement stmt; public List<TypeAsEntry> entries = new ArrayList<TypeAsEntry>(); public void merge( TypeAsContext last ) { entries.addAll( last.entries ); } } private static class TypeAsEntry { public Expression expr; public IType originalType; public IType inferredType; public int loopCompromised; public ArrayList<Expression> refs = new ArrayList<Expression>(); private TypeAsEntry( Expression expr, IType originalType, IType newType ) { this.expr = expr; this.originalType = originalType; this.inferredType = newType; } } }