package gw.rating.flow.domain uses com.google.common.collect.ImmutableList uses com.google.common.collect.ImmutableSet uses gw.api.locale.DisplayKey uses gw.api.util.TypeUtil uses gw.lang.reflect.IType uses gw.rating.flow.util.RateflowTypeUtils /** * For IF and LOOP statements.
* * Subclasses are required to implement the getter Statements which should return the list of * statements in the body of the BlockStatement.
* * Extracted getVariables() and friends from {@link IfStatement} so that we can use if for {@link LoopStatement} as * well. */ abstract class BlockStatement extends StatementContainer { protected static final var NUMBER_TYPE: Type = java.lang.Number.Type protected construct(statements : ImmutableList) { super(statements) } private static function canAssign(t1 : IType, t2 : IType) : boolean { if (t1 == null or t2 == null) return false // prevent NPE if expression type unknown if (gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(t1, java.math.BigDecimal)) { return gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(NUMBER_TYPE, t2) } return TypeUtil.isNominallyOrStructurallyAssignable(t1, t2) } /** * Appends variable declarations for all the local variables that are declared within the body of this * BlockStatement to the generation context. * * @param context the generation context */ function addLocalVariableDeclarations(context : GenerationContext) { var varsMap = getBlockLocalVariables(context) if (Parent == null) { // create a list of "var varName : varType" varsMap.entrySet().stream() .sorted(\o1 : Map.Entry, o2 : Map.Entry -> o1.Key.compareTo(o2.Key)).forEach(\entry: Map.Entry -> { var varName = entry.Key var varType = entry.Value.Type if (context.AddLocalVarDecls) { context.append("var ${varName}: ${varType.Name}\n") } context.VarMap.put(varName, varType) // Modify the VarMap so type for variable is the (would be) decalred type. }) } } /** * Collects all the local variables that are declared in the specified block of statements that have not been previously * declared. Excludes InScope, loop and loop index variables. * * @param context the generation context * * @return local variables used in the block that need to be declared, as a map (essentially) of name to type */ private function getBlockLocalVariables(context : GenerationContext) : Map { var existingVars = ImmutableSet.copyOf(context.VarMap.Keys) var varsMap = getVariables(context) varsMap.retainWhereKeys(\k -> not existingVars.contains(k)) // Remove any InScope amd Loop related variables or any variable field/property reference. varsMap.retainWhereKeys(\ k -> not (ParseUtil.isInScopeOrLoopVariableReference(k))) // ... and persist the rest in the generation context context.VarMap.putAll(varsMap.mapValues(\v -> v.Type)) return varsMap } /** * Gets all the local variables used in this which have not previously been declared (i.e., they are not already in * the context's variable map). * * Moved here from {@link IfStatement} so it can also be used by {@link LoopStatement}. * * @param context generation context * * @return local variables used in the block that need to be declared, as a map (essentially) of name to type */ private function getVariables(context: GenerationContext) : Map { var varmap : Map = {} // this is essentially a local "function" that has varmap in scope var checkTypes = \ currentStatement : AssignmentStatement -> { var currentVarKey = currentStatement.Variable.VariableReference var currentType = currentStatement.getExpressionType(context) var accumulatedVarAssignmentInfo = varmap.get(currentVarKey) currentType = RateflowTypeUtils.getBoxedType(currentType) if (accumulatedVarAssignmentInfo == null) { var lhs = new VariableAssignmentInfo(currentType, currentStatement) varmap.put(currentVarKey, lhs) if (currentType == null and currentStatement.Operands.first() typeis LocalVariableOperand) { // currentType == null can happen when the assignment is of the form localVar2 <- localVar1 // and localVar1 was defined inside the IF(s) that we're currently working on. // We should have a locally-available type for the var on the RHS...but it might have to be // widened. So we use whatever type we have, and register the dependency so if that // type is widened later we can re-evaluate the dependent variable and widen it if necessary. var localVar = currentStatement.Operands.first() as LocalVariableOperand var key = localVar.VariableKey var rhs = varmap.get(key) if (rhs == null) { // let's hope that context knows the type of the local variable from a previous declaration if (lhs.Type == null) { lhs.Type = context.VarMap.get(key) } } else { rhs.registerDependency(lhs) if (lhs.Type == null) { lhs.Type = rhs.Type } } } } else { if (currentType == null and currentStatement.Operands.first() typeis LocalVariableOperand) { var key = (currentStatement.Operands.first() as LocalVariableOperand).VariableKey currentType = varmap.get(key).Type ?: context.VarMap.get(key) } if (canAssign(accumulatedVarAssignmentInfo.Type, currentType)) { accumulatedVarAssignmentInfo.registerAssignmentAttempt(currentStatement) } else { accumulatedVarAssignmentInfo.registerAssignmentAttempt(currentStatement) if (canAssign(currentType, accumulatedVarAssignmentInfo.Type)) { accumulatedVarAssignmentInfo.Type = currentType } else { var incompatibleElements = accumulatedVarAssignmentInfo.AssignmentAttempts incompatibleElements.each(\element -> element.addToContextOrThrow(context.Context, DisplayKey.get("Validation.Rating.RateRoutineDefinition.IncompatibleVariableTypes", "${accumulatedVarAssignmentInfo.Type.RelativeName} (${accumulatedVarAssignmentInfo.Type})", "${currentType.RelativeName} (${currentType})", "${currentVarKey}"))) } } } } // this is essentially a local "function" that has varmap in scope var doStatements : block(s : List) // forward reference so doStatements can be recursive! doStatements = \ _statements -> { for (statement in _statements) { if (statement typeis AssignmentStatement) { checkTypes(statement) } else if (statement typeis StatementContainer) { doStatements(statement.Statements) } else if (not (statement typeis CostDataRoundingStatement or statement typeis CommentStatement)) { throw new IllegalStateException("getVariables() does not know how to handle ${typeof statement}") } } } doStatements(Statements) return varmap } private static class VariableAssignmentInfo { private var _type: IType as Type private var _statements : Set = {} private var _dependencies : Set = {} property set Type(t : IType) { _type = t if (t != null) { _dependencies.where(\ d -> canAssign(t, d.Type) and not canAssign(d.Type, t)).each(\ d -> {d.Type = t}) } } property get AssignmentAttempts() : List { return _statements.map(\ s -> s.HighlightElement) } construct(t : IType, s : AssignmentStatement) { Type = t _statements.add(s) } function registerAssignmentAttempt(s : AssignmentStatement) { _statements.add(s) } function registerDependency(d : VariableAssignmentInfo) { _dependencies.add(d) } } }