/* * Copyright 2003-2011 JetBrains s.r.o. * * Licensed 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 jetbrains.mps.newTypesystem.state; import gnu.trove.THashMap; import gnu.trove.THashSet; import jetbrains.mps.errors.IErrorReporter; import jetbrains.mps.errors.messageTargets.NodeMessageTarget; import jetbrains.mps.lang.typesystem.runtime.ICheckingRule_Runtime; import jetbrains.mps.lang.typesystem.runtime.IsApplicableStatus; import jetbrains.mps.logging.Logger; import jetbrains.mps.typesystem.inference.TypeSubstitution; import jetbrains.mps.newTypesystem.TypesUtil; import jetbrains.mps.newTypesystem.VariableIdentifier; import jetbrains.mps.newTypesystem.context.TracingTypecheckingContext; import jetbrains.mps.newTypesystem.operation.AbstractOperation; import jetbrains.mps.newTypesystem.operation.AddRemarkOperation; import jetbrains.mps.newTypesystem.operation.ApplyRuleOperation; import jetbrains.mps.newTypesystem.operation.CheckAllOperation; import jetbrains.mps.newTypesystem.operation.ClearNodeTypeOperation; import jetbrains.mps.newTypesystem.operation.SolveInequalitiesOperation; import jetbrains.mps.newTypesystem.operation.SubstituteTypeOperation; import jetbrains.mps.newTypesystem.operation.block.AddBlockOperation; import jetbrains.mps.newTypesystem.operation.block.AddDependencyOperation; import jetbrains.mps.newTypesystem.operation.block.RemoveBlockOperation; import jetbrains.mps.newTypesystem.operation.block.RemoveDependencyOperation; import jetbrains.mps.newTypesystem.state.blocks.Block; import jetbrains.mps.newTypesystem.state.blocks.BlockKind; import jetbrains.mps.newTypesystem.state.blocks.CheckEquationBlock; import jetbrains.mps.newTypesystem.state.blocks.ComparableBlock; import jetbrains.mps.newTypesystem.state.blocks.ConditionKind; import jetbrains.mps.newTypesystem.state.blocks.InequalityBlock; import jetbrains.mps.newTypesystem.state.blocks.RelationKind; import jetbrains.mps.newTypesystem.state.blocks.WhenConcreteBlock; import jetbrains.mps.smodel.SModelUtil_new; import jetbrains.mps.smodel.SNodeUtil; import jetbrains.mps.typesystem.inference.EquationInfo; import jetbrains.mps.typesystem.inference.TypeCheckingContext; import jetbrains.mps.typesystem.inference.util.StructuralNodeSet; import jetbrains.mps.util.IterableUtil; import jetbrains.mps.util.Pair; import jetbrains.mps.util.containers.ManyToManyMap; import org.apache.log4j.LogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SConcept; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeAccessUtil; import java.lang.reflect.Array; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.Stack; public class State { private static final Logger LOG = Logger.wrap(LogManager.getLogger(State.class)); private final TypeCheckingContext myTypeCheckingContext; private final Equations myEquations; private final Inequalities myInequalities; private final NodeMaps myNodeMaps; private final VariableIdentifier myVariableIdentifier; private final Stack<AbstractOperation> myOperationStack; private AbstractOperation myOperation; private List<AbstractOperation> myOperationsAsList; private boolean myInsideStateChangeAction = false; @StateObject private final Map<ConditionKind, ManyToManyMap<SNode, Block>> myBlocksAndInputs = new THashMap<ConditionKind, ManyToManyMap<SNode, Block>>(); @StateObject private final BlockSet myBlocks = new BlockSet(); public State(TypeCheckingContext tcc, AbstractOperation operation) { myTypeCheckingContext = tcc; myEquations = new Equations(this); myInequalities = createInequalities(); myNodeMaps = new NodeMaps(this); myVariableIdentifier = new VariableIdentifier(); { myBlocksAndInputs.put(ConditionKind.SHALLOW, new ManyToManyMap<SNode, Block>()); myBlocksAndInputs.put(ConditionKind.CONCRETE, new ManyToManyMap<SNode, Block>()); } myOperationStack = new Stack<AbstractOperation>(); myOperation = operation; myOperationStack.push(myOperation); } public State(TypeCheckingContext tcc) { this(tcc, new CheckAllOperation()); } protected Inequalities createInequalities() { return new Inequalities(this); } @StateMethod public void addDependency(Block dataFlowBlock, SNode var, ConditionKind condition) { assertIsInStateChangeAction(); ManyToManyMap<SNode, Block> map = myBlocksAndInputs.get(condition); map.addLink(var, dataFlowBlock); } @StateMethod public void removeDependency(Block dataFlowBlock, SNode var, ConditionKind condition) { assertIsInStateChangeAction(); ManyToManyMap<SNode, Block> map = myBlocksAndInputs.get(condition); map.removeLink(var, dataFlowBlock); } @StateMethod public void removeBlockNoVars(Block dataFlowBlock) { assertIsInStateChangeAction(); for (ManyToManyMap<SNode, Block> map : myBlocksAndInputs.values()) { if (myInequalities.isSolvingInProcess()) { //we can remove blocks with vars while solving inequalities map.clearSecond(dataFlowBlock); } else { assert !map.containsSecond(dataFlowBlock); } } boolean removed = myBlocks.remove(dataFlowBlock); assert removed; } @StateMethod public void addBlockNoVars(Block dataFlowBlock) { assertIsInStateChangeAction(); for (ManyToManyMap<SNode, Block> map : myBlocksAndInputs.values()) { assert !map.containsSecond(dataFlowBlock) || myInequalities.isSolvingInProcess(); } boolean addedAnew = myBlocks.add(dataFlowBlock); assert addedAnew; } public void applyRuleToNode(SNode node, ICheckingRule_Runtime rule, IsApplicableStatus status) { try { executeOperation(new ApplyRuleOperation(node, rule, status)); } catch (Throwable t) { LOG.error("an error occurred while applying rule to node " + node, t, node); } } public void substitute(SNode oldVar, SNode type) { for (ConditionKind conditionKind : new THashSet<ConditionKind>(myBlocksAndInputs.keySet())) { ManyToManyMap<SNode, Block> map = myBlocksAndInputs.get(conditionKind); Set<Block> blocks = map.getByFirst(oldVar); if (blocks == null) { return; } List<SNode> unresolvedInputs = conditionKind.getUnresolvedInputs(type, this); for (Block block : new THashSet<Block>(blocks)) { for (SNode variable : unresolvedInputs) { addInputAndTrack(block, variable, conditionKind); } removeInputAndTrack(block, oldVar, conditionKind); testInputsResolved(block); } } } private void addInputAndTrack(Block block, SNode var, ConditionKind conditionKind) { executeOperation(new AddDependencyOperation(block, var, conditionKind)); } private void removeInputAndTrack(Block block, SNode var, ConditionKind conditionKind) { executeOperation(new RemoveDependencyOperation(block, var, conditionKind)); } private void becameConcrete(Block block) { executeOperation(new RemoveBlockOperation(block)); } public void addBlock(Block block) { executeOperation(new AddBlockOperation(block)); } public boolean clearNode(SNode node) { SNode type = myNodeMaps.getType(node); List<IErrorReporter> nodeErrors = myNodeMaps.getNodeErrors(node); if (type != null || (nodeErrors != null && !nodeErrors.isEmpty())) { executeOperation(new ClearNodeTypeOperation(node, type, nodeErrors)); return true; } return false; } private void testInputsResolved(Block block) { if (!myBlocks.contains(block)) return; boolean concrete = true; for (ManyToManyMap<SNode, Block> map : myBlocksAndInputs.values()) { concrete = concrete && map.getBySecond(block).isEmpty(); } if (concrete) { becameConcrete(block); } } public void collectVarsExecuteIfNecessary(Block block) { Set<Pair<SNode, ConditionKind>> initialInputs = block.getInitialInputs(); for (Pair<SNode, ConditionKind> input : initialInputs) { SNode type = input.o1; ConditionKind conditionKind = input.o2; for (SNode variable : conditionKind.getUnresolvedInputs(type, this)) { addInputAndTrack(block, variable, conditionKind); } } testInputsResolved(block); } //--------------------------------------------- public Equations getEquations() { return myEquations; } public boolean addEquation(SNode left, SNode right, EquationInfo info) { return myEquations.addEquation(left, right, info); } public void addEquation(SNode left, SNode right, EquationInfo info, boolean checkOnly) { // substitute correct type left = lookupTypeSubstitution(left); right = lookupTypeSubstitution(right); if (!checkOnly) { addEquation(left, right, info); } else{ if (myTypeCheckingContext.isSingleTypeComputation()) return; //no need to check if we don't need to report errors) addBlock(new CheckEquationBlock(this, left, right, RelationKind.CHECK_EQUATION, info)); } } public void addInequality(SNode subType, SNode superType, boolean isWeak, boolean check, EquationInfo info, boolean lessThan) { // substitute correct type subType = lookupTypeSubstitution(subType); superType = lookupTypeSubstitution(superType); if (check && myTypeCheckingContext.isSingleTypeComputation()) return; //no need to check if we don't need to report errors addBlock(new InequalityBlock(this, subType, superType, lessThan, RelationKind.fromFlags(isWeak, check, false), info)); } public void addComparable(SNode left, SNode right, boolean isWeak, boolean inference, EquationInfo info) { if (!inference && myTypeCheckingContext.isSingleTypeComputation()) return; //no need to check if we don't need to report errors) addBlock(new ComparableBlock(this, left, right, RelationKind.fromFlags(isWeak, !inference, true), info)); } public NodeMaps getNodeMaps() { return myNodeMaps; } public Inequalities getInequalities() { return myInequalities; } public TypeCheckingContext getTypeCheckingContext() { return myTypeCheckingContext; } public void executeStateChangeAction(Runnable action) { try { myInsideStateChangeAction = true; action.run(); } finally { myInsideStateChangeAction = false; } } public void assertIsInStateChangeAction() { LOG.assertLog(myInsideStateChangeAction, "state change can be executed only inside state change action"); } public void executeOperation(AbstractOperation operation) { if (operation == null) return; if (myTypeCheckingContext instanceof TracingTypecheckingContext) { if (!myOperationStack.empty()) { myOperationStack.peek().addConsequence(operation); } } if (myTypeCheckingContext instanceof TracingTypecheckingContext || operation.hasEffect()) { myOperationStack.push(operation); operation.execute(this); if (!myOperationStack.empty()) { myOperationStack.pop(); } else { LOG.warning("Operation stack in type system state was empty"); } } else { operation.execute(this); //do not store unneeded operations } } private void visit(AbstractOperation operation, List<AbstractOperation> result) { result.add(operation); for (AbstractOperation child : operation.getConsequences()) { visit(child, result); } } public List<AbstractOperation> getOperationsAsList() { List<AbstractOperation> result = new ArrayList<AbstractOperation>(); visit(myOperation, result); return result; } public void addError(SNode node, IErrorReporter error, EquationInfo info) { myNodeMaps.addNodeToError(node, error, info); } public SNode typeOf(SNode node, EquationInfo info) { return myNodeMaps.typeOf(node, info); } public void clear(boolean clearDiff) { myEquations.clear(); myInequalities.clear(); myNodeMaps.clear(); myVariableIdentifier.clear(); myBlocks.clear(); for (ManyToManyMap map : myBlocksAndInputs.values()) { map.clear(); } if (clearDiff) { clearOperations(); } } public void clearOperations() { myOperationStack.clear(); myOperation = new CheckAllOperation(); myOperationStack.push(myOperation); } public void clearStateObjects() { if (!(myTypeCheckingContext instanceof TracingTypecheckingContext)/* && myInequalitySystem == null*/) { for (Entry<ConditionKind, ManyToManyMap<SNode, Block>> map : myBlocksAndInputs.entrySet()) { map.getValue().clear(); } myBlocks.clear(); myEquations.clear(); myNodeMaps.clearTypesToNodes(); } clearOperations(); } public void solveInequalities() { if (!myInequalities.getRelationsToSolve().isEmpty()) { executeOperation(new SolveInequalitiesOperation(new Runnable() { @Override public void run() { myInequalities.solveRelations(); } })); } } public void checkNonConcreteWhenConcretes() { for (Block block : getBlocks()) { if (block.getBlockKind().equals(BlockKind.WHEN_CONCRETE)) { WhenConcreteBlock wCBlock = (WhenConcreteBlock) block; if (!wCBlock.isSkipError()) { SNode node = myNodeMaps.getNode(wCBlock.getArgument()); if (node != null) { SConcept concept = node.getConcept(); boolean isRuntime = concept.equals(SNodeUtil.concept_RuntimeTypeVariable); if (!concept.isAbstract() && !isRuntime) { myTypeCheckingContext.reportWarning(node, "argument of WHEN CONCRETE block is never concrete", wCBlock.getNodeModel(), wCBlock.getNodeId(), null, new NodeMessageTarget()); } } } } } } public void performActionsAfterChecking() { checkNonConcreteWhenConcretes(); } public SNode expand(SNode node) { return myEquations.expandNode(node, false); } public SNode expandFinal(SNode node) { return myEquations.expandNode(node, true); } public AbstractOperation getOperation() { return myOperation; } public void expandAll(final Set<SNode> nodes, final boolean finalExpansion) { if (nodes != null && !nodes.isEmpty()) { executeOperation(new AddRemarkOperation("Types Expansion", new Runnable() { @Override public void run() { myNodeMaps.expandAll(nodes, finalExpansion); } })); } } public boolean executeOperationsBeforeAnchor(AbstractOperation firstOp, Object anchor) { firstOp.redo(this); if (firstOp.equals(anchor)) { return true; } for (AbstractOperation child : firstOp.getConsequences()) { if (executeOperationsBeforeAnchor(child, anchor)) { return true; } } return false; } public SNode getRepresentative(SNode node) { return myEquations.getRepresentative(node); } public SNode createNewRuntimeTypesVariable() { SNode typeVar = SModelUtil_new.instantiateConceptDeclaration(SNodeUtil.concept_RuntimeTypeVariable, null, null, false); //todo this code should be moved into MPS SNodeAccessUtil.setProperty(typeVar, SNodeUtil.property_INamedConcept_name, myVariableIdentifier.getNewVarName()); return typeVar; } public Set<Block> getBlocks() { return Collections.unmodifiableSet(myBlocks); } public Set<Block> getBlocks(BlockKind kind) { return myBlocks.getBlocks(kind); } private void executeOperationsFromTo(int from, int to) { assert from < to; for (int i = from + 1; i <= to; i++) { myOperationsAsList.get(i).redo(this); } } private void revertOperationsFromTo(int from, int to) { assert from > to; for (int i = from; i > to; i--) { myOperationsAsList.get(i).undo(this); } } public void updateState(AbstractOperation from, AbstractOperation to) { if (myOperationsAsList == null) { myOperationsAsList = getOperationsAsList(); } int fromIndex = myOperationsAsList.indexOf(from); int toIndex = myOperationsAsList.indexOf(to); if (fromIndex > toIndex) { revertOperationsFromTo(fromIndex, toIndex); } else if (fromIndex < toIndex) { executeOperationsFromTo(fromIndex, toIndex); } } @Nullable private SNode lookupTypeSubstitution(SNode origType) { if (origType == null || TypesUtil.isVariable(origType)) return origType; SNode newType = origType; // exhaustively apply substitutions until the operation has no effect StructuralNodeSet<SNode> seen = new StructuralNodeSet<SNode>(); TypeSubstitution typeSubs = myTypeCheckingContext.getSubstitution(origType); while (typeSubs != null && typeSubs.isValid()) { newType = typeSubs.getNewNode(); if (seen.containsStructurally(newType)) { break; } seen.addStructurally(newType); executeOperation(new SubstituteTypeOperation(typeSubs)); // next iteration typeSubs = myTypeCheckingContext.getSubstitution(newType); } return newType; } /** Nulls are not allowed. Not serializable. Not cloneable. */ private static class BlockSet extends AbstractSet<Block> { private EnumMap<BlockKind, Set<Block>> myBlockKindsToBlocks = new EnumMap<BlockKind, Set<Block>>(BlockKind.class); private Iterable<Block>[] myBlockSetArray; @SuppressWarnings("unchecked") BlockSet () { for(BlockKind bk: BlockKind.values()) { myBlockKindsToBlocks.put(bk, new THashSet<Block>()); } ArrayList<Iterable<Block>> sets = new ArrayList<Iterable<Block>>(); for(BlockKind bk: BlockKind.values()) { sets.add(myBlockKindsToBlocks.get(bk)); } final Iterable<Block>[] arr = (Iterable<Block>[]) Array.newInstance(Iterable.class, BlockKind.values().length); myBlockSetArray = sets.toArray(arr); } public Set<Block> getBlocks(BlockKind bk) { return Collections.unmodifiableSet(myBlockKindsToBlocks.get(bk)); } @Override public int size() { int count = 0; for(BlockKind bk: BlockKind.values()) { count += myBlockKindsToBlocks.get(bk).size(); } return count; } @Override @NotNull public Iterator<Block> iterator() { return IterableUtil.merge(myBlockSetArray).iterator(); } @Override public boolean contains(Object o) { if (!(o instanceof Block)) return false; final Block blk = (Block) o; return myBlockKindsToBlocks.get(blk.getBlockKind()).contains(blk); } @Override public boolean add(Block block) { if (block == null) throw new IllegalArgumentException("nulls not aloowed"); return myBlockKindsToBlocks.get(block.getBlockKind()).add(block); } @Override public boolean remove(Object o) { if (!(o instanceof Block)) return false; final Block blk = (Block) o; return myBlockKindsToBlocks.get(blk.getBlockKind()).remove(blk); } @Override public void clear() { for(BlockKind bk: BlockKind.values()) { myBlockKindsToBlocks.get(bk).clear(); } } @Override protected Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } } }