]> gerrit.simantics Code Review - simantics/platform.git/commitdiff
Merge "Fetch all audit logging events"
authorJani Simomaa <jani.simomaa@semantum.fi>
Sat, 16 Mar 2019 14:48:34 +0000 (14:48 +0000)
committerGerrit Code Review <gerrit2@simantics>
Sat, 16 Mar 2019 14:48:34 +0000 (14:48 +0000)
29 files changed:
bundles/org.simantics.databoard/src/org/simantics/databoard/Files.java
bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/request/ChildrenByIdentifier.java [new file with mode: 0644]
bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DiagramContentRequest.java
bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/TypeGroup.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/componentTypeEditor/DerivedPropertiesSection.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/handlers/e4/SyncCurrentTypicalInstanceWithTemplate.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/handlers/e4/SyncCurrentTypicalTemplateToInstances.java
bundles/org.simantics.modeling/scl/Simantics/Subscription.scl
bundles/org.simantics.modeling/src/org/simantics/modeling/MigrateModel.java
bundles/org.simantics.modeling/src/org/simantics/modeling/mapping/DiagramToCompositeMapping3.java
bundles/org.simantics.scl.compiler/scl/SCL/CommandSession.scl [new file with mode: 0644]
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/commands/CommandSession.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/constants/JavaStaticMethod.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/chr/CHRQuery.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/chr/CHRRule.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/contexts/TranslationContext.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/expressions/ERecord.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/expressions/EVar.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/expressions/VariableProcedure.java
bundles/org.simantics.scl.runtime/scl/Iterator.scl
bundles/org.simantics.scl.runtime/scl/Prelude.scl
bundles/org.simantics.scl.runtime/scl/Set.scl
bundles/org.simantics.scl.runtime/scl/SetClasses.scl [new file with mode: 0644]
bundles/org.simantics.scl.runtime/scl/SetUtils.scl [deleted file]
bundles/org.simantics.scl.runtime/scl/StandardLibrary.scl
bundles/org.simantics.scl.runtime/src/org/simantics/scl/runtime/reporting/SCLReporting.java
bundles/org.simantics.ui/src/org/simantics/ui/selection/WorkbenchSelectionUtils.java
tests/org.simantics.scl.compiler.tests/src/org/simantics/scl/compiler/tests/scl/CHR4.scl
tests/org.simantics.scl.compiler.tests/src/org/simantics/scl/compiler/tests/scl/CHR8.scl

index cc41e8e9b021254899aa2e942c2f8fc69c81a603..35c8822d558974a8fdfad5662e002eb413106561 100644 (file)
@@ -188,8 +188,7 @@ public class Files {
        public static Datatype readFileType(File file) throws IOException {
                BinaryFile rf = new BinaryFile( file, "r" );
                try {
-                       Binding datatype_binding = Bindings.getBindingUnchecked( Datatype.class );
-                       return (Datatype) Bindings.getSerializerUnchecked( datatype_binding ).deserialize( rf );
+                       return (Datatype) Bindings.getSerializerUnchecked( Bindings.DATATYPE ).deserialize( rf );
                } finally {
                        rf.close();
                }               
@@ -208,8 +207,7 @@ public class Files {
        public static Object readFile(File file, Binding binding) throws IOException {
                BinaryFile rf = new BinaryFile( file, "r" );
                try {
-                       Binding datatype_binding = Bindings.getBindingUnchecked( Datatype.class );
-                       Datatype type = (Datatype) Bindings.getSerializerUnchecked( datatype_binding ).deserialize( rf );
+                       Datatype type = (Datatype) Bindings.getSerializerUnchecked( Bindings.DATATYPE ).deserialize( rf );
                        
                        if (type.equals(binding.type())) {
                                return Bindings.getSerializerUnchecked( binding ).deserialize(rf);
@@ -229,6 +227,30 @@ public class Files {
                        rf.close();
                }
        }
+       
+       public static Object readFileTypeAdapting(File file, Binding binding) throws IOException {
+        BinaryFile rf = new BinaryFile( file, "r" );
+        try {
+            Datatype type = (Datatype) Bindings.getSerializerUnchecked( Bindings.DATATYPE ).deserialize( rf );
+            
+            if (type.equals(binding.type())) {
+                return Bindings.getSerializerUnchecked( binding ).deserialize(rf);
+            } else {
+                try {
+                    Binding fileContentBinding = Bindings.getMutableBinding(type);
+                    Adapter adapter = Bindings.getTypeAdapter(fileContentBinding, binding);
+                    Object value = Bindings.getSerializerUnchecked( fileContentBinding ).deserialize(rf);
+                    return adapter.adapt( value );
+                } catch (AdapterConstructionException e) {
+                    throw new IOException(e);
+                } catch (AdaptException e) {
+                    throw new IOException(e);
+                }
+            }
+        } finally {
+            rf.close();
+        }
+    }
 
        /**
         * Read a file to an object.
@@ -241,8 +263,7 @@ public class Files {
        public static void readFile(File file, RecordBinding binding, Object dst) throws IOException {
                BinaryFile rf = new BinaryFile( file, "r" );
                try {
-                       Binding datatype_binding = Bindings.getBindingUnchecked( Datatype.class );
-                       Datatype type = (Datatype) Bindings.getSerializerUnchecked( datatype_binding ).deserialize( rf );
+                       Datatype type = (Datatype) Bindings.getSerializerUnchecked( Bindings.DATATYPE ).deserialize( rf );
                        
                        if (type.equals(binding.type())) {
                                Serializer s = Bindings.getSerializerUnchecked( binding ); 
@@ -276,8 +297,7 @@ public class Files {
         */
        public static Object readFile(InputStream is, Binding binding) throws IOException {
                BinaryReadable readable = InputStreamReadable.readFully( is );
-               Binding datatype_binding = Bindings.getBindingUnchecked( Datatype.class );
-               Datatype type = (Datatype) Bindings.getSerializerUnchecked( datatype_binding ).deserialize( readable );
+               Datatype type = (Datatype) Bindings.getSerializerUnchecked( Bindings.DATATYPE ).deserialize( readable );
                        
                if (!type.equals(binding.type())) {
                        try {
@@ -308,8 +328,7 @@ public class Files {
         */
        public static Object readFile(InputStream is, long streamLength, Binding binding) throws IOException {
                BinaryReadable readable = new InputStreamReadable( is, streamLength );
-               Binding datatype_binding = Bindings.getBindingUnchecked( Datatype.class );
-               Datatype type = (Datatype) Bindings.getSerializerUnchecked( datatype_binding ).deserialize( readable );
+               Datatype type = (Datatype) Bindings.getSerializerUnchecked( Bindings.DATATYPE ).deserialize( readable );
                        
                if (!type.equals(binding.type())) {
                        try {
diff --git a/bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/request/ChildrenByIdentifier.java b/bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/request/ChildrenByIdentifier.java
new file mode 100644 (file)
index 0000000..8b3c076
--- /dev/null
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.db.layer0.request;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.simantics.datatypes.literal.GUID;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.common.request.ResourceRead;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.layer0.Layer0;
+
+/**
+ * Get a map from GUIDs to the children of the given resource.
+ */
+public final class ChildrenByIdentifier extends ResourceRead<Map<GUID, Resource>> {
+       public ChildrenByIdentifier(Resource resource) {
+               super(resource);
+       }
+
+       @Override
+       public Map<GUID, Resource> perform(ReadGraph graph) throws DatabaseException {
+               Map<GUID, Resource> result = new HashMap<>();
+               Layer0 L0 = Layer0.getInstance(graph);
+               Collection<Resource> children = graph.getObjects(resource, L0.ConsistsOf);
+               for (Resource child : children) {
+                       GUID guid = graph.getPossibleRelatedValue(child, L0.identifier, GUID.BINDING);
+                       if (guid != null)
+                               result.put(guid, child);
+               }
+               
+               return result;
+       }
+}
index 9be72d29f53fabb6e8d2d906618a1fb0a2fb8f9f..8f5afab1ac5faee7115d3164827014c3bbd100fe 100644 (file)
@@ -31,6 +31,8 @@ import org.simantics.diagram.content.RouteGraphConnectionPartRequest;
 import org.simantics.diagram.stubs.DiagramResource;
 import org.simantics.diagram.synchronization.ErrorHandler;
 import org.simantics.g2d.canvas.ICanvasContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import gnu.trove.list.array.TIntArrayList;
 import gnu.trove.map.hash.THashMap;
@@ -42,6 +44,8 @@ import gnu.trove.set.hash.THashSet;
  */
 public class DiagramContentRequest extends BaseRequest<Resource, DiagramContents> {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(DiagramContentRequest.class);
+
     int previousElementCount = 32;
     ErrorHandler errorHandler;
 
@@ -58,7 +62,11 @@ public class DiagramContentRequest extends BaseRequest<Resource, DiagramContents
         // These help loading result.elements in the correct order.
         final AtomicInteger index = new AtomicInteger();
         final TIntArrayList unrecognizedElementIndices = new TIntArrayList();
-
+        
+        if (!g.hasStatement(data)) {
+            LOGGER.warn("Most likely diagram is being removed and therefore ordered list can not be found for {}", data);
+            return new DiagramContents(); // or null, I don't know
+        }
         Collection<Resource> components = OrderedSetUtils.toList(g, data);
         DiagramContents res = g.syncRequest((AsyncRead<DiagramContents>)(graph, procedure) -> {
 
index b7b4ba2ff13bb9d9a55fec7a155bbc389bdd8883..7e4cb0e37bc7d448eb3a79fea764a4bcc6d908bf 100644 (file)
@@ -29,12 +29,16 @@ import org.simantics.db.procedure.SetListener;
 import org.simantics.diagram.stubs.DiagramResource;
 import org.simantics.layer0.Layer0;
 import org.simantics.scenegraph.profile.Group;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * @author Tuukka Lehtonen
  */
 public class TypeGroup implements Group {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(TypeGroup.class);
+
     private final Collection<Resource> types;
     private final String               name;
 
@@ -103,14 +107,17 @@ public class TypeGroup implements Group {
 //                for (Resource t : types)
 //                    System.out.println("\t" + NameUtils.getSafeName(graph, t, true));
 
-                Collection<Resource> elements = graph.syncRequest(new OrderedSet(realDiagram));
-                for (Resource element : elements) {
-//                    System.out.println("checking element " + NameUtils.getSafeName(graph, element, true));
-                    Collection<Resource> elementTypes = graph.getTypes(element);
-                    if (!Collections.disjoint(types, elementTypes))
-                        result.add(element);
+                if (graph.hasStatement(realDiagram)) {
+                    Collection<Resource> elements = graph.syncRequest(new OrderedSet(realDiagram));
+                    for (Resource element : elements) {
+    //                    System.out.println("checking element " + NameUtils.getSafeName(graph, element, true));
+                        Collection<Resource> elementTypes = graph.getTypes(element);
+                        if (!Collections.disjoint(types, elementTypes))
+                            result.add(element);
+                    }
+                } else {
+                    LOGGER.warn("Most likely after deleting a diagram or something therefore no ordered set can be found for {}", realDiagram);
                 }
-
                 return result;
             }
 
index 0f7bff3969dbf649e2a4740de2c586312a80a0a2..5f96c2ba9bbfba35461807f0b26958fdec709b49 100644 (file)
@@ -330,12 +330,7 @@ public class DerivedPropertiesSection implements ComponentTypeViewerSection {
         SCLContext sclContext = SCLContext.getCurrent();
         Object oldGraph = sclContext.get("graph");
         try {
-            CompileSCLMonitorRequest compileSCLMonitorRequest = new CompileSCLMonitorRequest(graph, context) {
-                @Override
-                protected String getExpressionText(ReadGraph graph) throws DatabaseException {
-                    return expression;
-                }
-            };
+            CompileSCLMonitorRequest compileSCLMonitorRequest = new ValidationCompilationRequest(graph, context, expression);
             Function1<Variable,Object> exp = graph.syncRequest(compileSCLMonitorRequest);
             sclContext.put("graph", graph);
             //return exp.apply(context.getParent(graph));
@@ -422,4 +417,29 @@ public class DerivedPropertiesSection implements ComponentTypeViewerSection {
         return 100.0;
     }
 
+    private static final class ValidationCompilationRequest extends CompileSCLMonitorRequest {
+        private final String expression;
+    
+        private ValidationCompilationRequest(ReadGraph graph, Variable context, String expression)
+                throws DatabaseException {
+            super(graph, context);
+            this.expression = expression;
+        }
+    
+        @Override
+        protected String getExpressionText(ReadGraph graph) throws DatabaseException {
+            return expression;
+        }
+    
+        @Override
+        public int hashCode() {
+            return super.hashCode() + 37 * expression.hashCode();
+        }
+    
+        @Override
+        public boolean equals(Object obj) {
+            return super.equals(obj) && ((ValidationCompilationRequest)obj).expression.equals(expression);
+        }
+    }
+
 }
\ No newline at end of file
index 9e58624803a8abfd559d230e17ffba77a62e40e0..df95cb90df4f31fed66fbb17a5b4a2991b59719a 100644 (file)
@@ -44,8 +44,12 @@ public class SyncCurrentTypicalInstanceWithTemplate {
             return false;
         if (DatabaseJob.inProgress())
             return false;
-        IResourceEditorInput input = (IResourceEditorInput) activeEditor.getEditorInput();
-        return TypicalPropertyTester.isTypicalInstanceEditor(Simantics.getSession(), input.getResource());
+        if (activeEditor.getEditorInput() instanceof IResourceEditorInput) {
+            IResourceEditorInput input = (IResourceEditorInput) activeEditor.getEditorInput();
+            return TypicalPropertyTester.isTypicalInstanceEditor(Simantics.getSession(), input.getResource());
+        } else {
+            return false;
+        }
     }
     
     @Execute
index 3558aaa62cd9e702273ba6b104066381a4108d0b..9936b603b32395d8cd0cdd7cb69f32f58effe6fa 100644 (file)
@@ -43,8 +43,12 @@ public class SyncCurrentTypicalTemplateToInstances {
             return false;
         if (DatabaseJob.inProgress())
             return false;
-        IResourceEditorInput input = (IResourceEditorInput) activeEditor.getEditorInput();
-        return TypicalPropertyTester.isTypicalMasterEditor(Simantics.getSession(), input.getResource());
+        if (activeEditor.getEditorInput() instanceof IResourceEditorInput) {
+            IResourceEditorInput input = (IResourceEditorInput) activeEditor.getEditorInput();
+            return TypicalPropertyTester.isTypicalMasterEditor(Simantics.getSession(), input.getResource());
+        } else {
+            return false;
+        }
     }
     
     @Execute
index 01bc3f0580a4f366d8a85b00cdb5a322507831f5..2672067c4a8833fa4725f4abc2fc20a48200dff7 100644 (file)
@@ -163,7 +163,7 @@ Example: create a subscription of module PO01 attribute PO11_PRESSURE to the def
     #430121
 
 """
-addSubscription :: Variable -> <WriteGraph,ReadGraph> Subscription
+addSubscription :: Variable -> <WriteGraph> Subscription
 addSubscription variable = do
     model = modelOfVariable variable
     default = defaultSubscriptionFolder model
index eb0b80709b70094fcdfff1f5900a70f35d0dee2e..4e472ded4a5d5198544a2ba6f18189db3d469ad9 100644 (file)
  *******************************************************************************/
 package org.simantics.modeling;
 
-import gnu.trove.set.hash.THashSet;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.simantics.databoard.Bindings;
 import org.simantics.databoard.util.URIStringUtils;
+import org.simantics.datatypes.literal.GUID;
 import org.simantics.db.ReadGraph;
 import org.simantics.db.Resource;
 import org.simantics.db.Statement;
 import org.simantics.db.WriteGraph;
 import org.simantics.db.common.NamedResource;
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;
 import org.simantics.db.common.utils.NameUtils;
 import org.simantics.db.exception.DatabaseException;
 import org.simantics.db.layer0.adapter.Instances;
+import org.simantics.db.layer0.request.ChildrenByIdentifier;
 import org.simantics.db.layer0.util.Layer0Utils;
 import org.simantics.diagram.stubs.DiagramResource;
 import org.simantics.layer0.Layer0;
@@ -36,12 +38,18 @@ import org.simantics.structural.stubs.StructuralResource2;
 import org.simantics.structural2.modelingRules.AllowedConnectionTypes;
 import org.simantics.utils.ObjectUtils;
 import org.simantics.utils.datastructures.Triple;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gnu.trove.set.hash.THashSet;
 
 /**
  * @author Antti Villberg
  */
 public class MigrateModel {
 
+       public static final Logger LOGGER = LoggerFactory.getLogger(MigrateModel.class);
+
        public static class MigrationOperation {
                
                public NamedResource instanceToMigrate;
@@ -74,6 +82,15 @@ public class MigrateModel {
                
                private Resource getPossibleReplacement(ReadGraph graph, Resource type, Resource predicate) throws DatabaseException {
                        Layer0 L0 = Layer0.getInstance(graph);
+                       
+                       // Try to find a relation with the same GUID
+                       GUID guid = graph.getPossibleRelatedValue(predicate, L0.identifier, GUID.BINDING);
+                       Map<GUID, Resource> children = graph.syncRequest(new ChildrenByIdentifier(type), TransientCacheListener.instance());
+                       Resource replacement = children.get(guid);
+                       if (replacement != null)
+                               return replacement;
+                       
+                       // Fall back to using relation name
                        String name = graph.getPossibleRelatedValue(predicate, L0.HasName, Bindings.STRING);
                        if(name == null) return null;
                        Resource child = Layer0Utils.getPossibleChild(graph, type, name);
@@ -110,6 +127,7 @@ public class MigrateModel {
                                                problems.append(" " + name + " was a property in the source type\n");
                                                continue;
                                        }
+                                       
                                        String sourceValueType = graph.getPossibleRelatedValue(predicate, L0.RequiresValueType, Bindings.STRING);
                                        String replacementValueType = graph.getPossibleRelatedValue(replacement, L0.RequiresValueType, Bindings.STRING);
                                        if(!ObjectUtils.objectEquals(sourceValueType, replacementValueType)) {
@@ -167,7 +185,29 @@ public class MigrateModel {
                                        Resource replacement = getPossibleReplacement(graph, type, predicate);
                                        graph.deny(stm);
                                        if(replacement == null) continue;
-                                       graph.claim(stm.getSubject(), replacement, stm.getObject());
+                                       
+                                       // Filter out incompatible literal types.
+                                       // TODO: Show warning in UI
+                                       Resource object = stm.getObject();
+                                       Resource replacementRange = graph.getPossibleObject(replacement, L0.HasRange);
+                                       if (replacementRange != null && !graph.isInstanceOf(object, replacementRange)) {
+                                               String instanceName = graph.getPossibleRelatedValue(instanceToMigrate, L0.HasName);
+                                               if (instanceName == null) instanceName = "<unknown>";
+                                               
+                                               String rangeName = graph.getPossibleRelatedValue(replacementRange, L0.HasName);
+                                               if (rangeName == null) rangeName = "<unknown>";
+                                               
+                                               Resource literalType= graph.getPossibleType(object, L0.Value);
+                                               String literalTypeName = literalType != null ? graph.getPossibleRelatedValue(literalType, L0.HasName) : null;
+                                               if (literalTypeName == null) literalTypeName = "<unknown>";
+                                               
+                                               String replacementName = graph.getRelatedValue(replacement, L0.HasName);
+                                               LOGGER.warn("{}: Ignored incompatible value of type {} for predicate {} with range {}", instanceName, literalTypeName, replacementName, rangeName);
+                                               
+                                               continue;
+                                       }
+                                       
+                                       graph.claim(stm.getSubject(), replacement, object);
                                }
                                
                        }
index 783084565ad850dfe8e3827f6bc8398b0dcebc67..f9feea9e28e532d529423b8bedb4a39636adb8c6 100644 (file)
@@ -195,14 +195,20 @@ public class DiagramToCompositeMapping3 extends MappingBase {
                                 destructiveConnectionRule()
                             ),
                             // If component does not have a corresponding element in the diagram, remove it
-                            and(deny(b(new Tag(MOD.ComponentToElement), Component)), deny(exists(Component)))
+                            and(
+                                    deny(b(new Tag(MOD.ComponentToElement), Component)),
+                                    deny(b(new Tag(STR.IsConnectedTo), Component)),
+                                    deny(exists(Component)))
                         )
                     )
                 ),
                 if_(b(mappedFromConnector, Component), // handle only mapped components
                     query(
                         unless(bf(MOD.ComponentToConnector, Component, Connector),
-                            and(deny(b(new Tag(MOD.ComponentToElement), Component)), deny(exists(Component)))
+                            and(
+                                    deny(b(new Tag(MOD.ComponentToElement), Component)),
+                                    deny(b(new Tag(STR.IsConnectedTo), Component)),
+                                    deny(exists(Component)))
                         )
                     )
                 )
diff --git a/bundles/org.simantics.scl.compiler/scl/SCL/CommandSession.scl b/bundles/org.simantics.scl.compiler/scl/SCL/CommandSession.scl
new file mode 100644 (file)
index 0000000..33b4dd7
--- /dev/null
@@ -0,0 +1,19 @@
+// This module is meant to be imported with namespace
+import "SCL/ModuleRepository"
+import "SafeDynamic"
+
+importJava "org.simantics.scl.compiler.commands.CommandSession" where
+    data CommandSession
+    
+    @JavaName "<init>"
+    create :: ModuleRepository -> <Proc> CommandSession
+
+    execute :: CommandSession -> String -> <Proc> ()
+    
+    @JavaName getVariableValueAndType
+    get :: CommandSession -> String -> <Proc> Maybe SafeDynamic
+    @JavaName setVariable
+    set :: CommandSession -> String -> SafeDynamic -> <Proc> ()
+    
+    @JavaName removeVariable
+    remove :: CommandSession -> String -> <Proc> ()
\ No newline at end of file
index 46cd0cb457d8a2567cfe90b5c13dff6bc55f0a2c..00a7714a63640a3f2ae31bd29df0625c7f455ff4 100644 (file)
@@ -17,6 +17,7 @@ import java.util.Set;
 
 import org.simantics.scl.compiler.common.names.Names;
 import org.simantics.scl.compiler.constants.StringConstant;
+import org.simantics.scl.compiler.dynamic.SafeDynamic;
 import org.simantics.scl.compiler.elaboration.expressions.EApply;
 import org.simantics.scl.compiler.elaboration.expressions.EBlock;
 import org.simantics.scl.compiler.elaboration.expressions.EConstant;
@@ -32,6 +33,7 @@ import org.simantics.scl.compiler.environment.Environment;
 import org.simantics.scl.compiler.environment.LocalEnvironment;
 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
 import org.simantics.scl.compiler.errors.CompilationError;
+import org.simantics.scl.compiler.errors.ErrorSeverity;
 import org.simantics.scl.compiler.errors.Locations;
 import org.simantics.scl.compiler.internal.codegen.utils.NameMangling;
 import org.simantics.scl.compiler.internal.parsing.exceptions.SCLSyntaxErrorException;
@@ -83,6 +85,10 @@ public class CommandSession {
      */
     private boolean validateOnly; 
     
+    public CommandSession(ModuleRepository moduleRepository) {
+        this(moduleRepository, SCLReporting.getCurrentReportingHandler());
+    }
+
     public CommandSession(ModuleRepository moduleRepository, SCLReportingHandler handler) {
         this.moduleRepository = moduleRepository;
         this.defaultHandler = new PrintDecorator(
@@ -123,7 +129,8 @@ public class CommandSession {
                     defaultHandler.printError(failure.toString());
                     if(failure.reason instanceof CompilationError[])
                         for(CompilationError error : (CompilationError[])failure.reason) {
-                            defaultHandler.printError("    " + error.description);
+                            if(error.severity != ErrorSeverity.WARNING)
+                                defaultHandler.printError("    " + error.description);
                         }
                 }
                 for(CommandSessionImportEntry entry : importEntries)
@@ -613,6 +620,11 @@ public class CommandSession {
         variableTypes.put(name, type);
     }
 
+    public void setVariable(String name, SafeDynamic typeAndValue) {
+        variableValues.put(name, typeAndValue.value);
+        variableTypes.put(name, typeAndValue.type_);
+    }
+
     public Object getVariableValue(String name) {
         return variableValues.get(name);
     }
@@ -621,6 +633,14 @@ public class CommandSession {
         return variableTypes.get(name);
     }
     
+    public SafeDynamic getVariableValueAndType(String name) {
+        Type type = variableTypes.get(name);
+        if(type == null)
+            return null;
+        Object value = variableValues.get(name);
+        return new SafeDynamic(type, value);
+    }
+    
     public void removeVariable(String name) {
        variableValues.remove(name);
        variableTypes.remove(name);
@@ -657,7 +677,7 @@ public class CommandSession {
             formatException(handler, e);
         }
     }
-    
+
     public static CompilationError[] validate(ModuleRepository moduleRepository,StringReader commandReader) {
         CommandSession session = new CommandSession(moduleRepository, null);
         return session.validate(commandReader);
index 9e071e9cb9002c700d9c398fec92a3b4eafca8ff..a72b844bae99c89cea35cc1304678da62209df4f 100644 (file)
@@ -56,6 +56,8 @@ public class JavaStaticMethod extends FunctionValue {
                     if(td.equals(TypeDesc.VOID))
                         throw new InternalCompilerError();
         }
+        if( (returnTypeDesc == null) != (parameterTypeDescs == null) )
+            throw new IllegalArgumentException("Either specify both returnTypeDesc and parameterTypeDescs or neither");
         ClassBuilder.checkClassName(className);
         this.className = className;
         this.methodName = methodName;
@@ -73,8 +75,11 @@ public class JavaStaticMethod extends FunctionValue {
     }
     
     @Override
-    public Type applyExact(MethodBuilder mb, Val[] parameters) {        
-        if(returnTypeDesc == null) {
+    public Type applyExact(MethodBuilder mb, Val[] parameters) {
+        if(returnTypeDesc == null || parameterTypeDescs == null) {
+            // This method may be called from multiple threads at the same time when returnTypeDesc
+            // and parameterTypeDescs are uninitialized. Double initialization is OK in this case,
+            // but because there are two fields, we have to check that both are initialized.             
             JavaTypeTranslator tt = mb.getJavaTypeTranslator();
             returnTypeDesc = tt.toTypeDesc(returnType);
             parameterTypeDescs = JavaTypeTranslator.filterVoid(
index 7bee6eae06b08a481652df67e5009d76629dfde4..6b01d99531f9d30db25fe0277e0f98d435632729 100644 (file)
@@ -10,8 +10,11 @@ import org.simantics.scl.compiler.elaboration.contexts.SimplificationContext;
 import org.simantics.scl.compiler.elaboration.contexts.TranslationContext;
 import org.simantics.scl.compiler.elaboration.contexts.TypingContext;
 import org.simantics.scl.compiler.elaboration.expressions.Expression;
+import org.simantics.scl.compiler.elaboration.expressions.ExpressionVisitor;
 import org.simantics.scl.compiler.elaboration.expressions.Variable;
 import org.simantics.scl.compiler.elaboration.expressions.printing.ExpressionToStringVisitor;
+import org.simantics.scl.compiler.elaboration.expressions.visitors.ForVariablesUsesVisitor;
+import org.simantics.scl.compiler.elaboration.expressions.visitors.StandardExpressionVisitor;
 import org.simantics.scl.compiler.errors.Locations;
 import org.simantics.scl.compiler.internal.parsing.Symbol;
 
@@ -92,4 +95,16 @@ public class CHRQuery extends Symbol {
             newLiterals[i] = literals[i].replace(context);
         return new CHRQuery(location, newLiterals);
     }
+    
+    public void accept(ExpressionVisitor visitor) {
+        for(CHRLiteral literal : literals) {
+            if(literal == null || literal.parameters == null)
+                continue; // FIXME why this happens?
+            for(Expression parameter : literal.parameters) {
+                if(parameter == null)
+                    continue; // FIXME why this happens?
+                parameter.accept(visitor);
+            }
+        }
+    }
 }
index 78224ebb0d75d876f0da2528a4ab8fba56a33129..e698d72c618ee79fca251e8689ae0267f057e5a4 100644 (file)
@@ -1,6 +1,7 @@
 package org.simantics.scl.compiler.elaboration.chr;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 
 import org.simantics.scl.compiler.compilation.CompilationContext;
 import org.simantics.scl.compiler.elaboration.chr.plan.CHRSearchPlan;
@@ -9,9 +10,13 @@ import org.simantics.scl.compiler.elaboration.chr.relations.CHRConstraint;
 import org.simantics.scl.compiler.elaboration.contexts.SimplificationContext;
 import org.simantics.scl.compiler.elaboration.contexts.TranslationContext;
 import org.simantics.scl.compiler.elaboration.contexts.TypingContext;
+import org.simantics.scl.compiler.elaboration.expressions.EAsPattern;
 import org.simantics.scl.compiler.elaboration.expressions.EVariable;
+import org.simantics.scl.compiler.elaboration.expressions.Expression;
+import org.simantics.scl.compiler.elaboration.expressions.ExpressionVisitor;
 import org.simantics.scl.compiler.elaboration.expressions.Variable;
 import org.simantics.scl.compiler.elaboration.expressions.printing.ExpressionToStringVisitor;
+import org.simantics.scl.compiler.elaboration.expressions.visitors.StandardExpressionVisitor;
 import org.simantics.scl.compiler.errors.Locations;
 import org.simantics.scl.compiler.internal.parsing.Symbol;
 import org.simantics.scl.compiler.types.Types;
@@ -54,6 +59,52 @@ public class CHRRule extends Symbol {
         context.disallowNewExistentials();
         body.resolve(context);
         existentialVariables = context.popExistentialFrame();
+        
+        warnForExistentialsUsedOnlyOnce(context);
+    }
+
+    private static final Object NEVER_USED = new Object();
+    
+    private void warnForExistentialsUsedOnlyOnce(TranslationContext context) {
+        // Initialize the hash map
+        HashMap<Variable, Object> usageCount = new HashMap<>(existentialVariables.length);
+        for(Variable var : existentialVariables)
+            if(!var.getName().equals("_"))
+                usageCount.put(var, NEVER_USED);
+    
+        // Collect variable uses
+        ExpressionVisitor visitor = new StandardExpressionVisitor() {
+            private void handle(Expression expression, Variable variable) {
+                Object object = usageCount.remove(variable);
+                if(object == NEVER_USED)
+                    usageCount.put(variable, expression);
+            }
+            @Override
+            public void visit(EVariable expression) {
+                if(expression.variable != null)
+                    handle(expression, expression.variable);
+            }
+            @Override
+            public void visit(EAsPattern expression) {
+                expression.pattern.accept(this);
+                handle(expression, expression.var);
+            }
+        };
+        head.accept(visitor);
+        body.accept(visitor);
+        
+        // Report as warnings
+        usageCount.forEach((variable, expression_) -> {
+            if(!(expression_ instanceof Expression))
+                return; // Should never happen
+            Expression expression = (Expression)expression_;
+            if(context.isExpandedFromWildcard(expression))
+                return;
+            
+            context.getErrorLog().logWarning(expression.location,
+                    "Existential variable " + variable.getName() + " is referred only once. Replace by _ if this is a wildcard.");
+        });
+        
     }
 
     public void checkType(TypingContext context) {
index d8346f41f5e889fbc66437a1f9be10a1a3ad803c..e0c256599195097c1e0a5d3650334deebebf4265 100644 (file)
@@ -76,6 +76,8 @@ public class TranslationContext extends TypeTranslationContext implements Enviro
     TIntArrayList chrConstraintFrames = new TIntArrayList();
     ArrayList<CHRConstraintEntry> chrConstraintEntries = new ArrayList<CHRConstraintEntry>();
     
+    private THashSet<Expression> expandedFromWildcard;
+    
     public CHRRuleset currentRuleset;
     
     public ModuleDebugInfo moduleDebugInfo;
@@ -141,7 +143,7 @@ public class TranslationContext extends TypeTranslationContext implements Enviro
             variable = new Variable(name);
             variables.put(name, variable);
             existentialFrame.variables.add(name);
-            return new EVariable(variable);
+            return new EVariable(location, variable);
         }
         case '_': {
             if(name.length()==1) {
@@ -583,7 +585,29 @@ public class TranslationContext extends TypeTranslationContext implements Enviro
         return Environments.getRuleset(environment, name);
     }
 
+    /**
+     * Tells that new existential variables are no longer allowed in this context.
+     */
     public void disallowNewExistentials() {
         getCurrentExistentialFrame().disallowNewExistentials = true;
     }
+
+    /**
+     * Marks that the expression is a result of expanding .. wildcard pattern in records.
+     */
+    public void addExpandedFromWildcard(Expression expression) {
+        if(expandedFromWildcard == null)
+            expandedFromWildcard = new THashSet<>();
+        expandedFromWildcard.add(expression);
+    }
+    
+    /**
+     * Asks if the expression is a result of expanding .. wildcard pattern in records.
+     */
+    public boolean isExpandedFromWildcard(Expression expression) {
+        if(expandedFromWildcard == null)
+            return false;
+        else
+            return expandedFromWildcard.contains(expression);
+    }
 }
index 47ef205df17cbce0d9ff1d74dd8f1154adc1bcfa..cb34d0fb3ea0394035bf61800430465e3d9b4376 100644 (file)
@@ -150,7 +150,9 @@ public class ERecord extends ASTExpression {
                 String variableName = fieldNames[i];
                 if(chrLiteral)
                     variableName = "?" + variableName;
-                parameters[i] = new EVar(wildcardField.location, variableName);
+                EVar expandedVar = new EVar(wildcardField.location, variableName); 
+                parameters[i] = expandedVar;
+                context.addExpandedFromWildcard(expandedVar);
             }
         }
         if(!recordMap.isEmpty()) {
index d78fa8565abf2fa3c8147baf797bd0a10f7920b1..2e4809a0c871160885200f84634db12beb47f769 100644 (file)
@@ -49,7 +49,10 @@ public class EVar extends ASTExpression {
 
     @Override
     public Expression resolve(TranslationContext context) {
-        return context.resolveVariable(location, name);
+        Expression resolved = context.resolveVariable(location, name);
+        if(context.isExpandedFromWildcard(this))
+            context.addExpandedFromWildcard(resolved);
+        return resolved;
     }
     
     @Override
@@ -59,7 +62,10 @@ public class EVar extends ASTExpression {
     
     @Override
     public Expression resolveAsPattern(TranslationContext context) {
-        return context.resolvePattern(this);
+        Expression resolved = context.resolvePattern(this);
+        if(context.isExpandedFromWildcard(this))
+            context.addExpandedFromWildcard(resolved);
+        return resolved;
     }
     
     @Override
index 44e9b6ae7264ae5e02581d3cea3b4c52884820db..af50692fa4ab8a8ec8244e6ef2f3bd90c3615f90 100644 (file)
@@ -1,5 +1,6 @@
 package org.simantics.scl.compiler.elaboration.expressions;
 
+@FunctionalInterface
 public interface VariableProcedure {
     void execute(long location, Variable variable);
 }
index 19e51a0d5836d23cb2447daabdb1d2093e6d093a..b98e14f423aaccb9252883be3d95c569833ce8bf 100644 (file)
@@ -1,3 +1,5 @@
+import "JavaBuiltin" as Java
+
 importJava "java.util.Iterator" where
     data T a
     
@@ -16,6 +18,17 @@ iter f it = loop ()
             loop ()
         else ()
 
+@inline
+iterI :: (Integer -> a -> <e> b) -> T a -> <Proc,e> ()
+iterI f it = loop 0
+  where
+    loop i = 
+        if hasNext it
+        then do
+            f i (next it)
+            loop (Java.iadd i 1)
+        else ()
+        
 @inline
 iterB :: (a -> <e> Boolean) -> T a -> <Proc,e> Boolean
 iterB f it = loop ()
index 80b4d0ad794d8eca76d5210c5783fca6cba9a933..e92603becb8e4300db875532c2b87ba090b742e8 100644 (file)
@@ -963,6 +963,9 @@ A class of monads with zero element satisfying
 """ 
 class (Monad m) => MonadZero m where
     mzero :: m a
+    mfilter :: (a -> Boolean) -> m a -> m a
+    
+    mfilter p m = m >>= (\x -> if p x then return x else mzero)
 
 "Injects a boolean test to a type beloning to `MonadZero`."
 guard :: MonadZero m => Boolean -> m ()
@@ -1105,7 +1108,21 @@ instance MonadE (Either a) where
 
 instance MonadE [] where
     bindE l f = concatMap f l
+
+/// MZeroE ///
+
+class (MonadE m, MonadZero m) => MonadZeroE m where
+    filter :: (a -> <e> Boolean) -> m a -> <e> m a
+    
+    filter p m = m `bindE` (\x -> if p x then return x else mzero)   
     
+instance MonadZeroE [] where
+    filter = filterList
+    
+instance MonadZeroE Maybe where
+    filter p (Just x) | not (p x) = Nothing
+    filter _ m = m 
+
 /// Category ///
 
 "Identity function."
@@ -1504,11 +1521,11 @@ snd :: (a,b) -> b
 snd (x,y) = y
 
 @inline
-mapFst :: (a -> b) -> (a,c) -> (b,c)
+mapFst :: (a -> <e> b) -> (a,c) -> <e> (b,c)
 mapFst f (x,y) = (f x, y)
 
 @inline
-mapSnd :: (a -> b) -> (c,a) -> (c,b)
+mapSnd :: (a -> <e> b) -> (c,a) -> <e> (c,b)
 mapSnd f (x,y) = (x, f y)
 
 instance (Ord a, Ord b) => Ord (a, b) where
@@ -1841,10 +1858,10 @@ foldr1 f l = loop (l!(len-1)) (len-2)
 `filter pred lst` returns those elements of `lst` that the predicate `pred` accepts. For example
 
     filter (> 3) [1, 2, 3, 4, 5, 6] = [4, 5, 6]
-""" 
+"""
 @inline
-filter :: (a -> <e> Boolean) -> [a] -> <e> [a]
-filter p l = build (\empty cons -> foldl (\cur x -> if p x then cons cur x else cur) empty l)
+filterList :: (a -> <e> Boolean) -> [a] -> <e> [a]
+filterList p l = build (\empty cons -> foldl (\cur x -> if p x then cons cur x else cur) empty l)
 
 """
 Takes those elements of the input list that match `(Just x)` and adds the contents to the resulting list. For example,
index 2bf2911c5631cc4e33938711b3a1f6ea86c16007..34033d91ef928ff9e52f2094f1db6c225f22ddd9 100644 (file)
@@ -11,13 +11,31 @@ importJava "java.util.Set" where
     iterator :: T a -> Iterator.T a
 
 @inline
-iter :: (a -> <e> ()) -> T a -> <e> ()
+iter :: (a -> <e> b) -> T a -> <e> ()
 iter f s = runProc (Iterator.iter f (iterator s))
 
 @inline
 iterB :: (a -> <e> Boolean) -> T a -> <e> Boolean
 iterB f s = runProc (Iterator.iterB f (iterator s))
 
+@inline
+iterI :: (Integer -> a -> <e> b) -> T a -> <e> ()
+iterI f s = runProc (Iterator.iterI f (iterator s))
+
 @inline
 fold :: (a -> b  -> <e> a) -> a -> T b -> <e> a
 fold f init s = runProc (Iterator.fold f init (iterator s))
+
+importJava "java.util.Collections" where
+    singleton :: a -> T a
+
+    @JavaName emptySet
+    empty :: T a
+
+importJava "gnu.trove.set.hash.THashSet" where
+    @JavaName "<init>"
+    fromList :: [a] -> T a
+
+importJava "java.util.ArrayList" where
+    @JavaName "<init>"
+    toList :: T a -> [a]
diff --git a/bundles/org.simantics.scl.runtime/scl/SetClasses.scl b/bundles/org.simantics.scl.runtime/scl/SetClasses.scl
new file mode 100644 (file)
index 0000000..8c3d1e6
--- /dev/null
@@ -0,0 +1,61 @@
+import "Prelude"
+import "MSet" as MSet
+import "Set" as Set
+
+instance Functor Set.T where
+    fmap = map
+        
+instance FunctorE Set.T where
+    map f set = runProc do
+        result = MSet.create ()
+        Set.iter (\x -> MSet.add result $ f x) set
+        MSet.freeze result
+        
+    iter = Set.iter
+    iterI = Set.iterI
+    
+instance Monad Set.T where
+    return = Set.singleton
+    (>>=) = bindE
+
+@private
+importJava "java.util.Set" where
+    @JavaName addAll
+    addAll' :: MSet.T a -> Set.T a -> <Proc> Boolean
+        
+instance MonadE Set.T where
+    set `bindE` f = runProc do
+        result = MSet.create ()
+        Set.iter (\x -> addAll' result $ f x) set
+        MSet.freeze result
+        
+instance MonadZero Set.T where
+    mzero = Set.empty
+    
+instance MonadZeroE Set.T where
+    filter p set = runProc do
+        result = MSet.create ()
+        Set.iter (\x ->
+            if p x
+            then ignore $ MSet.add result x
+            else ()
+        ) set
+        MSet.freeze result
+
+instance (Show a) => Show (Set.T a) where
+    sb <+ set = do
+        sb << "{"
+        Set.iterI (\i x -> (if i > 0 then sb << ", " else sb) <+ x) set
+        sb << "}"
+
+instance Additive (Set.T a) where
+    zero = Set.empty
+    a + b = runProc do
+        result = MSet.create ()
+        Set.iter (MSet.add result) a
+        Set.iter (MSet.add result) b
+        MSet.freeze result
+    sum sets = runProc do
+        result = MSet.create ()
+        iter (Set.iter (MSet.add result)) sets
+        MSet.freeze result
\ No newline at end of file
diff --git a/bundles/org.simantics.scl.runtime/scl/SetUtils.scl b/bundles/org.simantics.scl.runtime/scl/SetUtils.scl
deleted file mode 100644 (file)
index 00fa752..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-import "Prelude"
-import "Set" as Set
-import "MSet" as MSet
-import "MList" as MList
-
-fromList :: [a] -> Set.T a
-fromList l = runProc (MSet.freeze $ MSet.fromList l)
-
-toList :: Set.T a -> [a]
-toList s = runProc do
-    result = MList.createC (Set.size s)
-    Set.iter (MList.add result) s
-    MList.freeze result
\ No newline at end of file
index 7234b70be861c80806d752140cb575f5453b153c..e774f97466c7ffca313f5ed4b8284e3c12fe723a 100644 (file)
@@ -10,7 +10,7 @@ include "Lazy" as Lazy
 include "File" as File
 include "Serialization" as Serialization
 include "Set" as Set
-include "SetUtils" as Set
+include "SetClasses"
 //include "Map" as Map
 include "MMap" as MMap
 include "MSet" as MSet
index 97ef75f8b498e0d6a7382c7f84e626d0ceac8ee9..5266b13a21e61c7892a6efc39698abf60a18c4a8 100644 (file)
@@ -18,6 +18,11 @@ public class SCLReporting {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(SCLReporting.class);
 
+    public static SCLReportingHandler getCurrentReportingHandler() {
+        SCLReportingHandler handler = ((SCLReportingHandler)SCLContext.getCurrent().get(SCLReportingHandler.REPORTING_HANDLER));
+        return handler == null ? SCLReportingHandler.DEFAULT : handler;
+    }
+    
     public static void print(String text) {
         SCLReportingHandler handler = ((SCLReportingHandler)SCLContext.getCurrent().get(SCLReportingHandler.REPORTING_HANDLER));
         if(handler != null)
index 111ac8a3a2722b89b7945343830d059e2c3d82f5..e3e6eeafe9270432ab1a1a1dd984d8f8ea3d2306 100644 (file)
@@ -11,6 +11,7 @@ import org.eclipse.core.commands.ExecutionEvent;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.ui.handlers.HandlerUtil;
 import org.simantics.Simantics;
+import org.simantics.databoard.type.Datatype;
 import org.simantics.db.ReadGraph;
 import org.simantics.db.RequestProcessor;
 import org.simantics.db.Resource;
@@ -19,8 +20,8 @@ import org.simantics.db.common.primitiverequest.Supertypes;
 import org.simantics.db.common.primitiverequest.Types;
 import org.simantics.db.exception.DatabaseException;
 import org.simantics.db.layer0.request.PossibleGUID;
-import org.simantics.db.layer0.request.PossibleVariableGUID;
 import org.simantics.db.layer0.request.PossibleURI;
+import org.simantics.db.layer0.request.PossibleVariableGUID;
 import org.simantics.db.layer0.request.PossibleVariableRepresents;
 import org.simantics.db.layer0.request.VariableRead;
 import org.simantics.db.layer0.request.VariableURI;
@@ -108,12 +109,30 @@ public class WorkbenchSelectionUtils {
                if(var != null) {
                        String uri = processor.syncRequest(new VariableURI(var));
                        String guid = processor.syncRequest(new PossibleVariableGUID(var));
+                       
+                       Set<String> classifications = processor.syncRequest(new VariableRead<Set<String>>(var) {
+                               @Override
+                               public Set<String> perform(ReadGraph graph) throws DatabaseException {
+                                       return var.getClassifications(graph);
+                               }
+                       });
+                       String classificationsStr = toJSONStringArray(new ArrayList<>(classifications));
+                       
+                       Datatype datatype = processor.syncRequest(new VariableRead<Datatype>(var) {
+                               @Override
+                               public Datatype perform(ReadGraph graph) throws DatabaseException {
+                                       return var.getPossibleDatatype(graph);
+                               }
+                       });
+                       
                        return toJSONObjectString(
                                        "type", "\"Variable\"",
                                        "uri", safeQuotedString(uri),
                                        "guid", safeQuotedString(guid),
                                        "resourceId", res == null ? "" : Long.toString(res.getResourceId()),
-                                       "typeResources", typesStr);
+                                       "typeResources", typesStr,
+                                       "classifications", classificationsStr,
+                                       "datatype", safeQuotedString(datatype != null ? datatype.getClass().getName() : null));
                }
                if(res != null) {
                    String uri = processor.syncRequest(new PossibleURI(res));
index dc0714973abbbd9da901fe4d91df7e94442bcd0e..90b82e79a016d8c8db7b6eea99e19cf09a834dc0 100644 (file)
@@ -3,7 +3,9 @@ main = ()
     when ?x <- ?y
     then True
 --
+3:10-3:12: Existential variable ?x is referred only once. Replace by _ if this is a wildcard.
 3:10-3:18: Cannot solve the query.
+3:16-3:18: Existential variable ?y is referred only once. Replace by _ if this is a wildcard.
 --
 import "Prelude"
 
index dd915ebb5d10b971f5ec7cccc8cc3d7440a16cca..6929a0b9291a441cf31fd390d419d33b5d4f1308 100644 (file)
@@ -5,4 +5,5 @@ main = ()
   where
     X ?x => Y ?y
 --
+6:7-6:9: Existential variable ?x is referred only once. Replace by _ if this is a wildcard.
 6:15-6:17: New existential variables can be defined only in queries.
\ No newline at end of file