/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.ir.transform.expression; import gw.config.CommonServices; import gw.internal.gosu.ir.nodes.IRProperty; import gw.internal.gosu.ir.nodes.IRPropertyFactory; import gw.internal.gosu.ir.transform.ExpressionTransformer; import gw.internal.gosu.ir.transform.TopLevelTransformationContext; import gw.internal.gosu.parser.ArrayExpansionPropertyInfo; import gw.internal.gosu.parser.GosuVarPropertyInfo; import gw.internal.gosu.parser.JavaFieldPropertyInfo; import gw.internal.gosu.parser.LengthProperty; import gw.internal.gosu.parser.MetaType; import gw.internal.gosu.parser.MetaTypeTypeInfo; import gw.internal.gosu.parser.expressions.Identifier; import gw.internal.gosu.parser.expressions.Literal; import gw.internal.gosu.parser.expressions.MemberAccess; import gw.internal.gosu.parser.expressions.MemberExpansionAccess; import gw.internal.gosu.parser.optimizer.SinglePropertyMemberAccessRuntime; import gw.internal.gosu.runtime.GosuRuntimeMethods; import gw.lang.IAutocreate; import gw.lang.ShortCircuitingProperty; import gw.lang.function.IBlock; import gw.lang.ir.IRElement; import gw.lang.ir.IRExpression; import gw.lang.ir.IRStatement; import gw.lang.ir.IRSymbol; import gw.lang.ir.IRType; import gw.lang.ir.expression.IRCompositeExpression; import gw.lang.ir.statement.IRAssignmentStatement; import gw.lang.ir.statement.IRSyntheticStatement; import gw.lang.parser.EvaluationException; import gw.lang.parser.IBlockClass; import gw.lang.parser.ICustomExpressionRuntime; import gw.lang.parser.IExpression; import gw.lang.parser.Keyword; import gw.lang.parser.MemberAccessKind; import gw.lang.reflect.IAnnotationInfo; import gw.lang.reflect.IEntityAccess; import gw.lang.reflect.IExpando; import gw.lang.reflect.IPlaceholder; import gw.lang.reflect.IPropertyAccessor; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IPropertyInfoDelegate; import gw.lang.reflect.IRelativeTypeInfo; import gw.lang.reflect.IType; import gw.lang.reflect.ITypeInfo; import gw.lang.reflect.ITypeInfoPropertyInfo; import gw.lang.reflect.IUncacheableFeature; import gw.lang.reflect.ReflectUtil; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.gs.IGosuVarPropertyInfo; import gw.lang.reflect.java.GosuTypes; import gw.lang.reflect.java.JavaTypes; import java.util.ArrayList; import java.util.List; /** */ public class MemberAccessTransformer extends AbstractExpressionTransformer<MemberAccess> { public static IRExpression compile( TopLevelTransformationContext cc, MemberAccess expr ) { MemberAccessTransformer compiler = new MemberAccessTransformer( cc, expr ); return compiler.compile(); } private MemberAccessTransformer( TopLevelTransformationContext cc, MemberAccess expr ) { super( cc, expr ); } protected IRExpression compile_impl() { IExpression rootExpr = _expr().getRootExpression(); IType rootType = getConcreteType( rootExpr.getType() ); IPropertyInfo pi = _expr().getPropertyInfo(); if( isArrayExpansionProperty( pi ) ) { MemberExpansionAccess expr = MemberExpansionAccess.wrap( _expr() ); return MemberExpansionAccessTransformer.compile( _cc(), expr ); } else if ( _expr().getExpressionRuntime() instanceof ICustomExpressionRuntime) { return handleCustomExpressionRuntime( (ICustomExpressionRuntime) _expr().getExpressionRuntime(), _expr().getType() ); } else if( isTypeInfoProperty( pi ) ) { ITypeInfoPropertyInfo typeInfoProp = (ITypeInfoPropertyInfo)pi; IPropertyInfo backingProp = typeInfoProp.getBackingPropertyInfo(); return handleInstanceMemberAccess( rootExpr, backingProp.getOwnersType(), backingProp, IRPropertyFactory.createIRProperty( backingProp ) ); } else if( isStatic( pi ) ) { return handleStaticMemberAccess( rootExpr, rootType, pi, IRPropertyFactory.createIRProperty( pi ) ); } else { return handleInstanceMemberAccess( rootExpr, rootType, pi, IRPropertyFactory.createIRProperty( pi ) ); } } private boolean isArrayExpansionProperty( IPropertyInfo pi ) { return pi instanceof ArrayExpansionPropertyInfo; } private boolean isTypeInfoProperty( IPropertyInfo pi ) { return pi instanceof ITypeInfoPropertyInfo; } private IRExpression handleInstanceMemberAccess( IExpression rootExpr, IType rootType, IPropertyInfo pi, IRProperty irProperty ) { boolean mightRequireAutoCreation = mightRequireAutoCreation( rootType, pi ); boolean bShouldNullShortCircuitCheck = shouldNullShortCircuit( rootType, pi, mightRequireAutoCreation ); if( mightRequireAutoCreation && !bShouldNullShortCircuitCheck ) { throw new IllegalStateException( "Should never have an expression requiring auto-creation that doesn't null-shortcircuit" ); } IRExpression root = pushRootExpression( rootType, rootExpr, irProperty ); IRSymbol rootSymbol = null; IRExpression accessExpression; if( bShouldNullShortCircuitCheck ) { // Both null-short-circuiting and auto-creation require that the root be stored off in a temporary symbol rootSymbol = _cc().makeAndIndexTempSymbol( root.getType() ); accessExpression = buildAccessExpression( rootExpr, rootType, pi, irProperty, identifier( rootSymbol ) ); } else { accessExpression = buildAccessExpression( rootExpr, rootType, pi, irProperty, root ); } accessExpression = castIfTypeDerivedFromTypeVariable( irProperty, accessExpression ); // If we need to auto-create, handle that before the outer null-shortcircuiting if( mightRequireAutoCreation ) { accessExpression = handleAutoCreationWhenValueIsNull( pi, rootSymbol, accessExpression ); } // If we need to null-short-circuit, wrap everything in a ternary expression like: // temp_root = root_expression // (temp_root == null ? null : temp_root.member) if( bShouldNullShortCircuitCheck ) { accessExpression = buildComposite( buildAssignment( rootSymbol, root ), buildNullCheckTernary( identifier( rootSymbol ), shortCircuitValue( accessExpression.getType() ), accessExpression ) ); } return accessExpression; } private boolean shouldNullShortCircuit( IType rootType, IPropertyInfo pi, boolean mightRequireAutoCreation ) { return !rootType.isPrimitive() && // Standard Gosu requires the ?. operator for null-safe short-circuit (_expr().getMemberAccessKind() == MemberAccessKind.NULL_SAFE || // Backward compatibility for non-standard Gosu (!CommonServices.getEntityAccess().getLanguageLevel().isStandard() && (!_expr().getType().isPrimitive() || //## special case: boolean short-circuit to false _expr().getType() == JavaTypes.pBOOLEAN() || //## special case: handle array.length short-circuit to 0 _expr().getType() == JavaTypes.pINT() && isLengthProperty( pi ) || // Any property with the ShortCircuitingProperty annotation will also short-circuit pi.hasAnnotation( JavaTypes.getGosuType( ShortCircuitingProperty.class ) ))) || // If this expression in an l-value and auto-creation is required, all non-primitive expression types are considered null-safe (mightRequireAutoCreation && !_expr().getType().isPrimitive())); } private IRExpression buildAccessExpression(IExpression rootExpr, IType rootType, IPropertyInfo pi, IRProperty irProperty, IRExpression root) { if( irProperty == null ) { // a reflective member access. Why is this the same type of ParsedElement? return callStaticMethod( ReflectUtil.class, "getProperty", new Class[]{Object.class, String.class}, exprList( root, pushString( _expr().getMemberExpression() ) ) ); } if( isScopedField( pi ) ) { IGosuVarPropertyInfo varPropInfo = (IGosuVarPropertyInfo)pi; return getScopedSymbolValue( varPropInfo ); } else if( irProperty.isField() ) { IRExpression fieldGetter = getField( irProperty, root ); return castIfTypeDerivedFromTypeVariable( irProperty, fieldGetter ); } else if( isLengthProperty( pi ) ) { return buildArrayLength( root ); } else if( isSuperCall( rootExpr ) ) { return callSpecialMethod( getDescriptor( _cc().getSuperType() ), irProperty.getGetterMethod(), root, exprList() ); } else if( isOuterCall( pi ) ) { return pushOuter( getNextNonBlockOuter( rootType.getEnclosingType() ), rootType, root ); } else if( irProperty.isBytecodeProperty() ) { IRExpression irMethodCall = callMethod( irProperty.getGetterMethod(), root, exprList() ); assignStructuralTypeOwner( rootExpr, irMethodCall ); return irMethodCall; } else { return callPropertyInfo( rootType, pi, irProperty, root ); } } private boolean mightRequireAutoCreation(IType rootType, IPropertyInfo pi) { if(_expr().getExpressionRuntime() instanceof SinglePropertyMemberAccessRuntime) { if (((SinglePropertyMemberAccessRuntime) _expr().getExpressionRuntime()).isNestedInLhs()) { // The property in the path is null; attempt to dynamically set its value if @Autocreate annotation is present IType autocreateAnnotationType = GosuTypes.AUTOCREATE(); List<IAnnotationInfo> list = pi.getAnnotationsOfType( autocreateAnnotationType ); if (list != null && !list.isEmpty()) { return true; } else if( rootType instanceof IPlaceholder && ((IPlaceholder)rootType).isPlaceholder() ) { return true; } else { // TODO dlank - With a bit of work, the following can be replaced by adding @Autocreate annotations to the EntityTypeInfo's properties IEntityAccess ea = CommonServices.getEntityAccess(); if( ea.isEntityClass( rootType ) && ea.isEntityClass(pi.getFeatureType())) { return true; } } } } return false; } private IRExpression handleAutoCreationWhenValueIsNull(IPropertyInfo pi, IRSymbol rootSymbol, IRExpression accessExpression) { // If the access resulted in a null value, we need to auto-create it // To do that, we store the result in a temp variable, then generate a ternary expression like: // (result == null ? autoCreateValue(root) : result) IRSymbol resultSymbol = _cc().makeAndIndexTempSymbol( accessExpression.getType() ); return buildComposite( buildAssignment( resultSymbol, accessExpression ), buildNullCheckTernary( identifier( resultSymbol ), autoCreateEntityValue(pi, rootSymbol), identifier( resultSymbol ) ) ); } private IRExpression autoCreateEntityValue(IPropertyInfo pi, IRSymbol rootSymbol) { return checkCast( _expr().getType(), callStaticMethod(MemberAccessTransformer.class, "autoCreateEntityInstance", new Class[]{Object.class, String.class, String.class}, exprList( identifier( rootSymbol ), pushConstant(pi.getOwnersType().getName()), pushConstant(pi.getName())) ) ); } private IType getNextNonBlockOuter( IType type ) { while( type instanceof IBlockClass ) { type = type.getEnclosingType(); } return type; } private boolean isEnhancementProperty( IPropertyInfo pi ) { return getDelegatedEnhancementProperty( pi ) != null; } private IPropertyInfo getDelegatedEnhancementProperty( IPropertyInfo pi ) { IPropertyInfo delegatePI = null; if( pi instanceof IPropertyInfoDelegate ) { delegatePI = ((IPropertyInfoDelegate) pi).getSource(); if( !isEnhancementType( delegatePI.getOwnersType() ) ) { delegatePI = null; } } return delegatePI; } private IRExpression castIfTypeDerivedFromTypeVariable( IRProperty irProp, IRExpression root ) { if( irProp == null ) { return root; } IType type = _expr().getType(); if( type != JavaTypes.pVOID() && !type.isPrimitive() ) { if(!getDescriptor( type ).isAssignableFrom( irProp.getType() )) { return checkCast( type, root ); } } return root; } private IRExpression handleStaticMemberAccess( IExpression rootExpr, IType rootType, IPropertyInfo pi, IRProperty irProperty ) { rootType = maybeUnwrapMetaType( rootType ); if( isTypeProperty( pi ) ) { IRExpression result = checkCast( pi.getFeatureType(), pushType( rootType ) ); return maybeEvalRoot( rootExpr, result ); } else if( isScopedField( pi ) ) { IGosuVarPropertyInfo scopedSymbol = getActualPropertyInfo(pi); return maybeEvalRoot( rootExpr, getScopedSymbolValue( scopedSymbol ) ); } else if( pi != null && irProperty.isBytecodeProperty() ) { IRExpression result; if( irProperty.isField() ) { result = getField( irProperty, null ); } else { result = callMethod( irProperty.getGetterMethod(), null, exprList() ); } return maybeEvalRoot( rootExpr, result); } else { return callPropertyInfo( rootType, pi, irProperty, pushType( rootType ) ); } } private IRExpression maybeEvalRoot( IExpression rootExpr, IRExpression result ) { //If the root is a non literal, we must evaluate it, even if we are going to ignore it. if( !(rootExpr instanceof Literal) ) { return new IRCompositeExpression( new IRSyntheticStatement( ExpressionTransformer.compile( rootExpr, _cc() ) ), result); } else { return result; } } private boolean isTypeProperty( IPropertyInfo pi ) { return pi instanceof MetaTypeTypeInfo.TypeProperty; } private String getField( IPropertyInfo pi ) { if( !isField( pi ) ) { throw new IllegalArgumentException( pi.getName() + " is not a 'field' property" ); } while( pi instanceof IPropertyInfoDelegate ) { pi = ((IPropertyInfoDelegate)pi).getSource(); } // Note we don't want LengthProperty here... if( pi.getClass() == JavaFieldPropertyInfo.class ) { return ((JavaFieldPropertyInfo)pi).getField().getName(); } return pi.getName(); } private boolean isField( IPropertyInfo pi ) { return pi instanceof GosuVarPropertyInfo || pi instanceof JavaFieldPropertyInfo || ((pi instanceof IPropertyInfoDelegate) && isField( ((IPropertyInfoDelegate)pi).getSource() )); } private boolean isLengthProperty( IPropertyInfo pi ) { return pi instanceof LengthProperty || ((pi instanceof IPropertyInfoDelegate) && isLengthProperty( ((IPropertyInfoDelegate)pi).getSource() )); } private boolean isSuperCall( IExpression rootExpr ) { return rootExpr instanceof Identifier && Keyword.KW_super.equals( ((Identifier)rootExpr).getSymbol().getName() ); } private boolean isOuterCall( IPropertyInfo rootExpr ) { return Keyword.KW_outer.equals( rootExpr.getName() ); } private IRExpression callPropertyInfo( IType rootType, IPropertyInfo pi, IRProperty irProperty, IRExpression rawRoot ) { IRSymbol rootSymbol = null; List<IRElement> preEvaluationStatements = new ArrayList<IRElement>(); if( rawRoot != null ) { rootSymbol = _cc().makeAndIndexTempSymbol( rawRoot.getType() ); IRAssignmentStatement rootAssignment = buildAssignment( rootSymbol, rawRoot ); preEvaluationStatements.add( rootAssignment ); } IRExpression root; if( pi == null || pi.isStatic() ) { root = nullLiteral(); } else { IRStatement nullCheck = nullCheckVar( rootSymbol ); preEvaluationStatements.add( nullCheck ); root = identifier( rootSymbol ); } IRExpression result = buildInvocation( rootType, pi, irProperty, rootSymbol, root ); if( preEvaluationStatements.isEmpty() ) { return result; } else { preEvaluationStatements.add( result ); return new IRCompositeExpression( preEvaluationStatements ); } } private IRExpression buildInvocation( IType rootType, IPropertyInfo pi, IRProperty irProperty, IRSymbol rootSymbol, IRExpression root ) { IRExpression result; if( _expr().getRootExpression() != null && !(pi instanceof ITypeInfoPropertyInfo) ) { // For the time being, for debugging purposes we want standard property info accesses to go through // a helper method if( rootType instanceof IPlaceholder ) { // Placeholder types, such as snapshot types, have to get properties dynamically. They can't have static properties, though. if( rootSymbol == null ) { throw new IllegalArgumentException( "Cannot invoke a static property reflectively on a placeholder type" ); } result = callStaticMethod( GosuRuntimeMethods.class, "getPropertyDynamically", new Class[]{Object.class, String.class}, exprList( root, pushPropertyName( pi ) ) ); } else { // Everything else should get the property from the statically-determined type. Not all type systems will want // to do full dynamic dispatch, so we can't just grab the property off of the runtime object type, since that might // result in a different property being invoked than the one expected at compile time result = callStaticMethod( GosuRuntimeMethods.class, "getProperty", new Class[]{Object.class, IType.class, String.class}, exprList( root, pushType( rootType ), pushPropertyName( pi ) ) ); } } else { // // rootType // .getTypeInfo() // .getProperty( propertyName ) or getProperty( type, propertyName ) // .getAccessor() // .getValue( ctx ) // IRExpression typeExpression; if( _expr().getRootExpression() != null && !(pi instanceof ITypeInfoPropertyInfo) ) { // Must get root type from root expression when getting property dynamically // i.e., the property may not exist on the static root type typeExpression = callStaticMethod( TypeSystem.class, "getFromObject", new Class[]{Object.class}, exprList( root ) ); } else if( pi instanceof ITypeInfoPropertyInfo ) { // We want to get the MetaType . . . but calling TypeSystem.getFromObject will return the literal version // of the MetaType, which doesn't have all the properties we want . . . sooooooo // there's this gigantic hack in here to directly invoke MetaType.get typeExpression = callStaticMethod( MetaType.class, "get", new Class[]{IType.class}, exprList( root ) ); } else { typeExpression = pushType( rootType ); } IRExpression getTypeInfo = callMethod( IType.class, "getTypeInfo", new Class[0], typeExpression, exprList() ); IRExpression getProperty; boolean relativeTypeInfo = pi != null && pi.getOwnersType().getTypeInfo() instanceof IRelativeTypeInfo; if( relativeTypeInfo ) { getProperty = callMethod( IRelativeTypeInfo.class, "getProperty", new Class[]{IType.class, CharSequence.class}, checkCast( IRelativeTypeInfo.class, getTypeInfo ), exprList( pushType( rootType ), pushPropertyName( pi ) ) ); } else { getProperty = callMethod( ITypeInfo.class, "getProperty", new Class[]{CharSequence.class}, getTypeInfo, exprList( pushPropertyName( pi ) ) ); } IRExpression accessor = callMethod( IPropertyInfo.class, "getAccessor", new Class[0], getProperty, exprList() ); result = callMethod( IPropertyAccessor.class, "getValue", new Class[]{Object.class}, accessor, exprList( root ) ); } if( pi instanceof IPropertyInfoDelegate ) { IRType piClass = irProperty.getType(); if( !piClass.isPrimitive() ) { result = buildCast( piClass, result ); } else { result = unboxValueToType( _expr().getType(), result ); } } else { result = unboxValueToType( _expr().getType(), result ); } return result; } private IRExpression pushPropertyName( IPropertyInfo pi ) { if( pi != null ) { if ( pi instanceof IUncacheableFeature ) { // In the case of snapshot types, the property's name is garbage, so we need to go back to // the name from the member access itself return pushConstant( _expr().getMemberName() ); } else { return pushConstant( pi.getName() ); } } else { return pushString( _expr().getMemberExpression() ); } } private IRExpression pushRootExpression( IType rootType, IExpression rootExpr, IRProperty pi ) { // Push the root expression value IRExpression root = ExpressionTransformer.compile( rootExpr, _cc() ); //... and make sure it's boxed for the method call root = boxValue( rootType, root ); if( pi != null && !pi.isStatic() ) { IRType type = pi.getTargetRootIRType( ); if( !type.isAssignableFrom( root.getType() ) && (!(rootExpr.getType() instanceof IGosuClass) || !((IGosuClass)rootExpr.getType()).isStructure()) ) { root = buildCast( type, root ); } } return root; } private boolean isStatic( IPropertyInfo pi ) { return pi != null && pi.isStatic(); } public static Object autoCreateEntityInstance(Object rootValue, String typeName, String propertyName) { if( rootValue instanceof IExpando ) { ((IExpando)rootValue).setDefaultFieldValue( propertyName ); return ((IExpando)rootValue).getFieldValue( propertyName ); } IType entityType = TypeSystem.getByFullName(typeName); IPropertyInfo property = entityType.getTypeInfo().getProperty(propertyName); IEntityAccess ea = CommonServices.getEntityAccess(); List<IAnnotationInfo> annotationInfoList = property.getAnnotationsOfType( GosuTypes.AUTOCREATE() ); Object value; boolean usingAutoCreateAnnotation = annotationInfoList != null && annotationInfoList.size() > 0; if( usingAutoCreateAnnotation ) { IAnnotationInfo annotation = annotationInfoList.get( 0 ); IAutocreate o = (IAutocreate)annotation.getInstance(); IBlock block = (IBlock)o.getBlock(); if( block == null ) { value = property.getFeatureType().getTypeInfo().getConstructor().getConstructor().newInstance(); } else { value = block.invokeWithArgs(); } } else { value = ea.getEntityInstanceFrom( rootValue, property.getFeatureType() ); } if( !property.isWritable() ) { throw new EvaluationException( "Property, " + property.getName() + ", on class, " + TypeSystem.getFromObject( rootValue ).getRelativeName() + ", is null and immutable." ); } value = CommonServices.getCoercionManager().convertValue( value, property.getFeatureType() ); property.getAccessor().setValue( rootValue, value ); return usingAutoCreateAnnotation ? property.getAccessor().getValue( rootValue ) : value; } }