/* * 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.context.component; import gnu.trove.THashMap; import gnu.trove.THashSet; import jetbrains.mps.errors.IErrorReporter; import jetbrains.mps.lang.typesystem.runtime.IsApplicableStatus; import jetbrains.mps.lang.typesystem.runtime.NonTypesystemRule_Runtime; import jetbrains.mps.newTypesystem.context.typechecking.IncrementalTypechecking; import jetbrains.mps.languageScope.LanguageScopeExecutor; import jetbrains.mps.newTypesystem.state.State; import jetbrains.mps.smodel.NodeReadEventsCaster; import jetbrains.mps.typesystemEngine.util.TypeSystemUtil; import jetbrains.mps.util.Cancellable; import jetbrains.mps.util.Computable; import jetbrains.mps.util.IterableUtil; import org.jetbrains.mps.openapi.model.SNode; import jetbrains.mps.typesystem.inference.TypeChecker; import jetbrains.mps.typesystem.inference.TypeCheckingContext; import jetbrains.mps.typesystem.inference.TypesReadListener; import jetbrains.mps.util.Pair; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; public class NonTypeSystemComponent extends IncrementalTypecheckingComponent<State> implements ITypeErrorComponent { private ConcurrentLinkedQueue<Pair<SNode, String>> myCurrentPropertiesToInvalidate = new ConcurrentLinkedQueue<>(); private ConcurrentLinkedQueue<SNode> myCurrentTypedTermsToInvalidate = new ConcurrentLinkedQueue<>(); private Set<Pair<SNode, NonTypesystemRule_Runtime>> myCheckedNodes = new HashSet<Pair<SNode, NonTypesystemRule_Runtime>>(); // nodes which are checked themselves but not children private Map<SNode, List<IErrorReporter>> myNodesToErrorsMap = new HashMap<SNode, List<IErrorReporter>>(); // nodes to rules which depend on this nodes private Map<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>> myNodesToDependentNodesWithNTRules = new THashMap<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>>(); // properties to rules which depend on this nodes' properties private Map<Pair<SNode, String>, Map<NonTypesystemRule_Runtime, Set<SNode>>> myPropertiesToDependentNodesWithNTRules = new THashMap<Pair<SNode, String>, Map<NonTypesystemRule_Runtime, Set<SNode>>>(); // typed terms to rules which depend on this nodes private Map<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>> myTypedTermsToDependentNodesWithNTRules = new THashMap<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>>(); private Map<SNode, Set<NonTypesystemRule_Runtime>> myNodesDependentOnCachesWithNTRules = new THashMap<SNode, Set<NonTypesystemRule_Runtime>>(); //checked node & NT rule -> set of errors private Map<SNode, Map<NonTypesystemRule_Runtime, Set<IErrorReporter>>> myNodesAndNTRulesToErrors = new THashMap<SNode, Map<NonTypesystemRule_Runtime, Set<IErrorReporter>>>(); private Pair<SNode, NonTypesystemRule_Runtime> myRuleAndNodeBeingChecked = null; public NonTypeSystemComponent(TypeChecker typeChecker, State state, IncrementalTypechecking nodeTypesComponent) { super(typeChecker, state, nodeTypesComponent); } @Override public void clear() { super.clear(); clearCaches(); } @Override public void clearNodeTypes() { super.clearNodeTypes(); clearAllExceptErrors(); myNodesToErrorsMap.clear(); } private void clearAllExceptErrors() { super.clearNodeTypes(); myCurrentPropertiesToInvalidate.clear(); myCurrentTypedTermsToInvalidate.clear(); } void clearCaches() { myCheckedNodes.clear(); myNodesAndNTRulesToErrors.clear(); myNodesToDependentNodesWithNTRules.clear(); myPropertiesToDependentNodesWithNTRules.clear(); myTypedTermsToDependentNodesWithNTRules.clear(); myNodesDependentOnCachesWithNTRules.clear(); myNodesToErrorsMap.clear(); } @Override public Map<SNode, List<IErrorReporter>> getNodesToErrorsMap() { return Collections.unmodifiableMap(myNodesToErrorsMap); } @Override protected IncrementalTypechecking getTypechecking() { return (IncrementalTypechecking) super.getTypechecking(); } private void doInvalidate(Map<NonTypesystemRule_Runtime, Set<SNode>> nodesAndRules, Set<Pair<SNode, NonTypesystemRule_Runtime>> invalidatedNodesAndRules) { if (nodesAndRules != null) { for (NonTypesystemRule_Runtime ruleOfNode : nodesAndRules.keySet()) { Set<SNode> nodes = nodesAndRules.get(ruleOfNode); if (nodes != null) { for (SNode depNode : nodes) { invalidatedNodesAndRules.add(new Pair<SNode, NonTypesystemRule_Runtime>(depNode, ruleOfNode)); } } } } } //returns true if something was invalidated @Override protected boolean doInvalidate() { if (isInvalidationWasPerformed()) { return isInvalidationResult(); } Set<Pair<SNode, NonTypesystemRule_Runtime>> invalidatedNodesAndRules = new THashSet<Pair<SNode, NonTypesystemRule_Runtime>>(1); //nodes for (SNode node : getCurrentNodesToInvalidate()) { doInvalidate(myNodesToDependentNodesWithNTRules.get(node), invalidatedNodesAndRules); } //properties Pair<SNode, String> nextPair; while ((nextPair = myCurrentPropertiesToInvalidate.poll()) != null) { doInvalidate(myPropertiesToDependentNodesWithNTRules.get(nextPair), invalidatedNodesAndRules); } //typed terms SNode nextNode; while((nextNode = myCurrentTypedTermsToInvalidate.poll()) != null) { doInvalidate(myTypedTermsToDependentNodesWithNTRules.get(nextNode), invalidatedNodesAndRules); doInvalidate(myNodesToDependentNodesWithNTRules.get(nextNode), invalidatedNodesAndRules); } //cache-dependent if (isCacheWasRebuilt()) { for (SNode nodeOfRule : myNodesDependentOnCachesWithNTRules.keySet()) { Set<NonTypesystemRule_Runtime> rules = myNodesDependentOnCachesWithNTRules.get(nodeOfRule); if (rules != null) { for (NonTypesystemRule_Runtime rule : rules) { invalidatedNodesAndRules.add(new Pair<SNode, NonTypesystemRule_Runtime>(nodeOfRule, rule)); } } } } boolean result = !invalidatedNodesAndRules.isEmpty(); for (Pair<SNode, NonTypesystemRule_Runtime> nodeAndRule : invalidatedNodesAndRules) { myCheckedNodes.remove(nodeAndRule); Map<NonTypesystemRule_Runtime, Set<IErrorReporter>> rulesAndErrors = myNodesAndNTRulesToErrors.get(nodeAndRule.o1); if (rulesAndErrors != null) { Set<IErrorReporter> errors = rulesAndErrors.get(nodeAndRule.o2); if (errors != null) { for (IErrorReporter errorReporter : new HashSet<IErrorReporter>(errors)) { List<IErrorReporter> iErrorReporters = myNodesToErrorsMap.get(errorReporter.getSNode()); if (iErrorReporters != null) { iErrorReporters.remove(errorReporter); errors.remove(errorReporter); } } } } } clearAllExceptErrors(); setInvalidationResult(result); return result; } public void addPropertyToInvalidate(SNode eventNode, String propertyName) { myCurrentPropertiesToInvalidate.add(new Pair<SNode, String>(eventNode, propertyName)); setInvalidationWasPerformed(false); } public void typeWillBeRecalculatedForTerm(SNode term) { myCurrentTypedTermsToInvalidate.add(term); setInvalidationWasPerformed(false); } @Override public void addError(SNode node, IErrorReporter errorReporter) { Map<SNode, List<IErrorReporter>> errorMap = myNodesToErrorsMap; List<IErrorReporter> iErrorReporters = errorMap.get(node); if (iErrorReporters == null) { iErrorReporters = new ArrayList<IErrorReporter>(1); errorMap.put(node, iErrorReporters); } iErrorReporters.add(errorReporter); Collections.sort(iErrorReporters, new Comparator<IErrorReporter>() { @Override public int compare(IErrorReporter o1, IErrorReporter o2) { return o1.getMessageStatus().compareTo(o2.getMessageStatus()); } }); //dependencies if (myRuleAndNodeBeingChecked != null) { SNode currentNode = myRuleAndNodeBeingChecked.o1; NonTypesystemRule_Runtime currentRule = myRuleAndNodeBeingChecked.o2; Map<NonTypesystemRule_Runtime, Set<IErrorReporter>> rulesToErrorsMap = myNodesAndNTRulesToErrors.get(currentNode); if (rulesToErrorsMap == null) { rulesToErrorsMap = new THashMap<NonTypesystemRule_Runtime, Set<IErrorReporter>>(1); myNodesAndNTRulesToErrors.put(currentNode, rulesToErrorsMap); } Set<IErrorReporter> errorsSet = rulesToErrorsMap.get(currentRule); if (errorsSet == null) { errorsSet = new THashSet<IErrorReporter>(1); rulesToErrorsMap.put(currentRule, errorsSet); } errorsSet.add(errorReporter); // make sure the error is cleaned on the node deletion addDependentNodes(currentNode, currentRule, Collections.singleton(node)); } } private void addDependentTypeTerms(SNode sNode, NonTypesystemRule_Runtime rule, Set<SNode> typesToDependOn) { addDependentNodes(sNode, rule, typesToDependOn, true); } private void addDependentProperties(SNode sNode, NonTypesystemRule_Runtime rule, Set<Pair<SNode, String>> propertiesToDependOn) { Map<Pair<SNode, String>, Map<NonTypesystemRule_Runtime, Set<SNode>>> mapToNodesWithNTRules = myPropertiesToDependentNodesWithNTRules; for (Pair<SNode, String> propertyToDependOn : propertiesToDependOn) { if (propertyToDependOn == null) continue; Map<NonTypesystemRule_Runtime, Set<SNode>> dependentNodes = mapToNodesWithNTRules.get(propertyToDependOn); if (dependentNodes == null) { dependentNodes = new THashMap<NonTypesystemRule_Runtime, Set<SNode>>(1); mapToNodesWithNTRules.put(propertyToDependOn, dependentNodes); } Set<SNode> nodes = dependentNodes.get(rule); if (nodes == null) { nodes = new THashSet<SNode>(1); dependentNodes.put(rule, nodes); } nodes.add(sNode); } } private void addDependentNodes(SNode sNode, NonTypesystemRule_Runtime rule, Set<SNode> nodesToDependOn, boolean isTypedTerm) { Map<SNode, Map<NonTypesystemRule_Runtime, Set<SNode>>> mapToNodesWithNTRules = isTypedTerm ? myTypedTermsToDependentNodesWithNTRules : myNodesToDependentNodesWithNTRules; for (SNode nodeToDependOn : nodesToDependOn) { if (nodeToDependOn == null) continue; Map<NonTypesystemRule_Runtime, Set<SNode>> dependentNodes = mapToNodesWithNTRules.get(nodeToDependOn); if (dependentNodes == null) { dependentNodes = new THashMap<NonTypesystemRule_Runtime, Set<SNode>>(1); mapToNodesWithNTRules.put(nodeToDependOn, dependentNodes); } Set<SNode> nodes = dependentNodes.get(rule); if (nodes == null) { nodes = new THashSet<SNode>(1); dependentNodes.put(rule, nodes); } nodes.add(sNode); } } private void addCacheDependentNodesNonTypesystem(SNode node, NonTypesystemRule_Runtime rule) { Map<SNode, Set<NonTypesystemRule_Runtime>> dependentNodes = myNodesDependentOnCachesWithNTRules; Set<NonTypesystemRule_Runtime> rules = dependentNodes.get(node); if (rules == null) { rules = new THashSet<NonTypesystemRule_Runtime>(1); dependentNodes.put(node, rules); } rules.add(rule); } private void addDependentNodes(SNode sNode, NonTypesystemRule_Runtime rule, Set<SNode> nodesToDependOn) { addDependentNodes(sNode, rule, nodesToDependOn, false); } // true iff was fully executed (not cancelled) public boolean applyNonTypeSystemRulesToRoot(final TypeCheckingContext typeCheckingContext, final SNode rootNode, final Cancellable c) { if (rootNode == null) return false; return LanguageScopeExecutor.execWithModelScope(rootNode.getModel(), new Computable<Boolean>() { @Override public Boolean compute() { return applyRulesToRoot(typeCheckingContext, rootNode, c); } }); } // true iff fully executed private boolean applyRulesToRoot(TypeCheckingContext typeCheckingContext, SNode rootNode, Cancellable c) { doInvalidate(); try { Queue<SNode> frontier = new LinkedList<SNode>(); frontier.add(rootNode); while (!(frontier.isEmpty())) { if (c.isCancelled()) return false; SNode sNode = frontier.remove(); if (!TypeSystemUtil.shouldApplyTypeSystemRules(sNode)) { continue; } applyNonTypesystemRulesToNode(sNode, typeCheckingContext); frontier.addAll(IterableUtil.asCollection(sNode.getChildren())); } //all error reporters must be simple reporters, no error expansion needed } finally { setInvalidationWasPerformed(false); } return true; } private void applyNonTypesystemRulesToNode(final SNode node, final TypeCheckingContext typeCheckingContext) { getTypechecking().runApplyRulesTo(node, new Runnable() { @Override public void run() { List<Pair<NonTypesystemRule_Runtime, IsApplicableStatus>> nonTypesystemRules = TypeChecker.getInstance().getRulesManager().getNonTypesystemRules(node); MyEventsReadListener nodesReadListener = new MyEventsReadListener(); if (nonTypesystemRules == null) return; boolean incrementalMode = isIncrementalMode(); for (Pair<NonTypesystemRule_Runtime, IsApplicableStatus> rule : nonTypesystemRules) { Pair<SNode, NonTypesystemRule_Runtime> nodeAndRule = new Pair<SNode, NonTypesystemRule_Runtime>(node, rule.o1); MyTypesReadListener typesReadListener = new MyTypesReadListener(); if (incrementalMode) { if (myCheckedNodes.contains(nodeAndRule)) continue; nodesReadListener.clear(); NodeReadEventsCaster.setNodesReadListener(nodesReadListener); TypeChecker.getInstance().addTypesReadListener(typesReadListener); myRuleAndNodeBeingChecked = new Pair<SNode, NonTypesystemRule_Runtime>(node, rule.o1); } try { getTypechecking().applyRuleToNode(node, rule.o1, rule.o2, typeCheckingContext); } finally { myRuleAndNodeBeingChecked = null; if (incrementalMode) { TypeChecker.getInstance().removeTypesReadListener(typesReadListener); NodeReadEventsCaster.removeNodesReadListener(); } } if (incrementalMode) { nodesReadListener.setAccessReport(true); addDependentNodes(node, rule.o1, new THashSet<SNode>(nodesReadListener.getAccessedNodes())); addDependentNodes(node, rule.o1, Collections.singleton(node)); addDependentProperties(node, rule.o1, new THashSet<Pair<SNode, String>>(nodesReadListener.getAccessedProperties())); nodesReadListener.setAccessReport(false); typesReadListener.setAccessReport(true); addDependentTypeTerms(node, rule.o1, typesReadListener.getAccessedNodes()); typesReadListener.setAccessReport(false); nodesReadListener.clear(); } myCheckedNodes.add(nodeAndRule); } } }); } @Override protected boolean isIncrementalMode() { final boolean incrementalMode = getState().getTypeCheckingContext().isIncrementalMode(); return incrementalMode; // alright, alright } private static class MyTypesReadListener implements TypesReadListener { private Set<SNode> myAccessedNodes = new THashSet<SNode>(1); private boolean myIsSetAccessReport = false; public void setAccessReport(boolean accessReport) { myIsSetAccessReport = accessReport; } @Override public void nodeTypeAccessed(SNode term) { if (myIsSetAccessReport) { new Throwable().printStackTrace(); } myAccessedNodes.add(term); } public Set<SNode> getAccessedNodes() { return myAccessedNodes; } } }