diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/Compiler.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/Compiler.java index ee0320a4222..068e38a61de 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/Compiler.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/Compiler.java @@ -30,7 +30,7 @@ public class Compiler { public static PureModel compile(PureModelContextData model, DeploymentMode deploymentMode, String user) { - return compile(model, deploymentMode, user, null, null); + return compile(model, deploymentMode, user, (String) null, null); } public static PureModel compile(PureModelContextData model, DeploymentMode deploymentMode, String user, String packageOffset) @@ -38,6 +38,11 @@ public static PureModel compile(PureModelContextData model, DeploymentMode deplo return compile(model, deploymentMode, user, packageOffset, null); } + public static PureModel compile(PureModelContextData model, DeploymentMode deploymentMode, String user, Metadata metaData, PureModelProcessParameter pureModelProcessParameter) + { + return new PureModel(model, user, deploymentMode, pureModelProcessParameter, metaData); + } + public static PureModel compile(PureModelContextData model, DeploymentMode deploymentMode, String user, String packageOffset, Metadata metaData) { PureModelProcessParameter pureModelProcessParameter = new PureModelProcessParameter(packageOffset); diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PureModel.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PureModel.java index 7f2e56fbdec..d9fc523ea8d 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PureModel.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PureModel.java @@ -21,6 +21,7 @@ import org.eclipse.collections.api.factory.Lists; import org.eclipse.collections.api.factory.Maps; import org.eclipse.collections.api.factory.Sets; +import org.eclipse.collections.api.list.ImmutableList; import org.eclipse.collections.api.list.MutableList; import org.eclipse.collections.api.map.MutableMap; import org.eclipse.collections.api.set.ImmutableSet; @@ -111,6 +112,7 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinWorkerThread; import java.util.function.Consumer; @@ -149,7 +151,8 @@ public class PureModel implements IPureModel final ConcurrentHashMap connectionsIndex = new ConcurrentHashMap<>(); final ConcurrentHashMap packageableRuntimesIndex = new ConcurrentHashMap<>(); final ConcurrentHashMap runtimesIndex = new ConcurrentHashMap<>(); - private final ForkJoinPool defaultForkJoinPool; + private final ConcurrentLinkedQueue engineExceptions = new ConcurrentLinkedQueue<>(); + private final ForkJoinPool forkJoinPool; public PureModel(PureModelContextData pure, String user, DeploymentMode deploymentMode) { @@ -181,15 +184,14 @@ public PureModel(PureModelContextData pureModelContextData, CompilerExtensions e classLoader = Thread.currentThread().getContextClassLoader(); } - ClassLoader finalClassLoader = classLoader; - defaultForkJoinPool = new ForkJoinPool(1, x -> + if (pureModelProcessParameter.getForkJoinPool() == null) { - ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(x) - { - }; - forkJoinWorkerThread.setContextClassLoader(finalClassLoader); - return forkJoinWorkerThread; - }, null, false); + forkJoinPool = createForkJoinPool(classLoader); + } + else + { + forkJoinPool = pureModelProcessParameter.getForkJoinPool(); + } this.extensions = extensions; this.deploymentMode = deploymentMode; @@ -265,7 +267,7 @@ public PureModel(PureModelContextData pureModelContextData, CompilerExtensions e { Stream.concat(classToElements.removeAll(SectionIndex.class).stream(), classToElements.removeAll(Profile.class).stream()) .parallel() - .forEach(this::processFirstPass); + .forEach(handleEngineExceptions(this::processFirstPass)); MutableMap, Collection>> dependencyGraph = Maps.mutable.empty(); dependencyGraph.put(Class.class, Lists.fixedSize.with(org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.Measure.class)); @@ -282,14 +284,14 @@ public PureModel(PureModelContextData pureModelContextData, CompilerExtensions e MutableSet>> disjointDependencyGraphs = dependencyManagement.getDisjointDependencyGraphs(); disjointDependencyGraphs.parallelStream().forEach(disjointDependencyGraph -> { - processPass(classToElements, dependentToDependencies, this::processElementFirstPass, disjointDependencyGraph); - processPass(classToElements, dependentToDependencies, this::processSecondPass, disjointDependencyGraph); - processPass(classToElements, dependentToDependencies, this::processThirdPass, disjointDependencyGraph); - processPass(classToElements, dependentToDependencies, this::processFourthPass, disjointDependencyGraph); - processPass(classToElements, dependentToDependencies, this::processFifthPass, disjointDependencyGraph); - processPass(classToElements, dependentToDependencies, this::processSixthPass, disjointDependencyGraph); + processPass(classToElements, dependentToDependencies, handleEngineExceptions(this::processElementFirstPass), disjointDependencyGraph); + processPass(classToElements, dependentToDependencies, handleEngineExceptions(this::processSecondPass), disjointDependencyGraph); + processPass(classToElements, dependentToDependencies, handleEngineExceptions(this::processThirdPass), disjointDependencyGraph); + processPass(classToElements, dependentToDependencies, handleEngineExceptions(this::processFourthPass), disjointDependencyGraph); + processPass(classToElements, dependentToDependencies, handleEngineExceptions(this::processFifthPass), disjointDependencyGraph); + processPass(classToElements, dependentToDependencies, handleEngineExceptions(this::processSixthPass), disjointDependencyGraph); }); - }, defaultForkJoinPool); + }, forkJoinPool); try { pureModelCompilationFuture.join(); @@ -313,13 +315,21 @@ else if (cause instanceof RuntimeException) // Post Validation long postValidationStart = System.nanoTime(); - new ProfileValidator().validate(this, pureModelContextData); - new EnumerationValidator().validate(this, pureModelContextData); - new ClassValidator().validate(this, pureModelContextData); - new AssociationValidator().validate(this, pureModelContextData); - new FunctionValidator().validate(getContext(), pureModelContextData); - new org.finos.legend.engine.language.pure.compiler.toPureGraph.validator.MappingValidator().validate(this, pureModelContextData, extensions); - this.extensions.getExtraPostValidators().forEach(validator -> validator.value(this, pureModelContextData)); + try + { + processPostValidation(pureModelContextData, extensions); + } + catch (EngineException e) + { + if (this.pureModelProcessParameter.getEnablePartialCompilation()) + { + engineExceptions.add(e); + } + else + { + throw e; + } + } long postValidationEnd = System.nanoTime(); LOGGER.info("{}", new LogInfo(user, "GRAPH_POST_VALIDATION_COMPLETED", nanosDurationToMillis(postValidationStart, postValidationEnd))); span.log("GRAPH_POST_VALIDATION_COMPLETED"); @@ -339,10 +349,33 @@ else if (cause instanceof RuntimeException) finally { span.finish(); - defaultForkJoinPool.shutdown(); + forkJoinPool.shutdown(); } } + private ForkJoinPool createForkJoinPool(ClassLoader classLoader) + { + return new ForkJoinPool(1, x -> + { + ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(x) + { + }; + forkJoinWorkerThread.setContextClassLoader(classLoader); + return forkJoinWorkerThread; + }, null, false); + } + + private void processPostValidation(PureModelContextData pureModelContextData, CompilerExtensions extensions) + { + new ProfileValidator().validate(this, pureModelContextData); + new EnumerationValidator().validate(this, pureModelContextData); + new ClassValidator().validate(this, pureModelContextData); + new AssociationValidator().validate(this, pureModelContextData); + new FunctionValidator().validate(getContext(), pureModelContextData); + new org.finos.legend.engine.language.pure.compiler.toPureGraph.validator.MappingValidator().validate(this, pureModelContextData, extensions); + this.extensions.getExtraPostValidators().forEach(validator -> validator.value(this, pureModelContextData)); + } + public static PureModel getCorePureModel() { return new PureModel(PureModelContextData.newBuilder().build(), CompilerExtensions.fromExtensions(Lists.mutable.empty()), Identity.getAnonymousIdentity().getName(), null, null, new PureModelProcessParameter(), null); @@ -447,6 +480,25 @@ private void initializePrimitiveTypes() // ------------------------------------------ LOADER ----------------------------------------- + private Consumer handleEngineExceptions(Consumer passConsumer) + { + if (this.pureModelProcessParameter.getEnablePartialCompilation()) + { + return x -> + { + try + { + passConsumer.accept(x); + } + catch (EngineException e) + { + engineExceptions.add(e); + } + }; + } + return passConsumer; + } + private void processPass(FastListMultimap, org.finos.legend.engine.protocol.pure.v1.model.packageableElement.PackageableElement> classToElements, MutableMap, Collection>> dependentToDependencies, Consumer passConsumer, MutableSet> disjointDependencyGraph) { MutableMap, CompletableFuture> tracker = Maps.mutable.empty(); @@ -468,7 +520,7 @@ private void processPass(FastListMultimap { tracker.get(dependent).completeExceptionally(e); @@ -571,7 +623,6 @@ private T visitWithErrorHandling(org.finos.legend.engine.protocol.pure.v1.mo public org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.PackageableElement getPackageableElement(String fullPath) { return getPackageableElement(fullPath, SourceInformation.getUnknownSourceInformation()); - } public org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.PackageableElement getPackageableElement(String fullPath, SourceInformation sourceInformation) @@ -1013,6 +1064,14 @@ public Root_meta_core_runtime_Connection getConnection_safe(String fullPath) return this.connectionsIndex.get(packagePrefix(fullPath)); } + public ImmutableList getEngineExceptions() + { + if (this.pureModelProcessParameter.getEnablePartialCompilation()) + { + return Lists.immutable.withAll(this.engineExceptions); + } + return Lists.immutable.empty(); + } // ------------------------------------------ SUB-ELEMENT GETTER ----------------------------------------- @@ -1264,26 +1323,26 @@ public boolean isImmutable(String s) protected String buildNameForAppliedFunction(String functionName) { - if (pureModelProcessParameter.packagePrefix != null + if (pureModelProcessParameter.getPackagePrefix() != null && !isImmutable(functionName) && !functionName.startsWith("meta::") - && !functionName.startsWith(pureModelProcessParameter.packagePrefix) + && !functionName.startsWith(pureModelProcessParameter.getPackagePrefix()) && functionName.contains("::")) { - return pureModelProcessParameter.packagePrefix + functionName; + return pureModelProcessParameter.getPackagePrefix() + functionName; } return functionName; } private String packagePrefix(String packageName) { - if (pureModelProcessParameter.packagePrefix != null + if (pureModelProcessParameter.getPackagePrefix() != null && !isImmutable(packageName) && !packageName.startsWith("meta::") - && !packageName.startsWith(pureModelProcessParameter.packagePrefix) + && !packageName.startsWith(pureModelProcessParameter.getPackagePrefix()) ) { - return pureModelProcessParameter.packagePrefix + packageName; + return pureModelProcessParameter.getPackagePrefix() + packageName; } return packageName; } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PureModelProcessParameter.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PureModelProcessParameter.java index b4372a65f6f..c39a06098d3 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PureModelProcessParameter.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PureModelProcessParameter.java @@ -14,17 +14,97 @@ package org.finos.legend.engine.language.pure.compiler.toPureGraph; +import java.util.concurrent.ForkJoinPool; + public class PureModelProcessParameter { - String packagePrefix; + private final String packagePrefix; + private final ForkJoinPool forkJoinPool; + private final boolean enablePartialCompilation; PureModelProcessParameter() { - this.packagePrefix = null; + this(null); } public PureModelProcessParameter(String packagePrefix) + { + this(packagePrefix, null, false); + } + + private PureModelProcessParameter(String packagePrefix, ForkJoinPool forkJoinPool, boolean enablePartialCompilation) { this.packagePrefix = packagePrefix; + this.forkJoinPool = forkJoinPool; + this.enablePartialCompilation = enablePartialCompilation; + } + + public String getPackagePrefix() + { + return this.packagePrefix; + } + + public ForkJoinPool getForkJoinPool() + { + return this.forkJoinPool; + } + + public boolean getEnablePartialCompilation() + { + return this.enablePartialCompilation; + } + + public static Builder newBuilder() + { + return new Builder(); + } + + public static class Builder + { + private String packagePrefix; + private ForkJoinPool forkJoinPool; + private boolean enablePartialCompilation; + + public Builder() + { + } + + public void setPackagePrefix(String packagePrefix) + { + this.packagePrefix = packagePrefix; + } + + public Builder withPackagePrefix(String packagePrefix) + { + setPackagePrefix(packagePrefix); + return this; + } + + public void setForkJoinPool(ForkJoinPool forkJoinPool) + { + this.forkJoinPool = forkJoinPool; + } + + public Builder withForkJoinPool(ForkJoinPool forkJoinPool) + { + setForkJoinPool(forkJoinPool); + return this; + } + + public void setEnablePartialCompilation(boolean enablePartialCompilation) + { + this.enablePartialCompilation = enablePartialCompilation; + } + + public Builder withEnablePartialCompilation(boolean enablePartialCompilation) + { + setEnablePartialCompilation(enablePartialCompilation); + return this; + } + + public PureModelProcessParameter build() + { + return new PureModelProcessParameter(this.packagePrefix, this.forkJoinPool, this.enablePartialCompilation); + } } } \ No newline at end of file diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/TestCompilationFromGrammar.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/TestCompilationFromGrammar.java index 3a8c3bbfd16..4389e9a4835 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/TestCompilationFromGrammar.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/TestCompilationFromGrammar.java @@ -17,12 +17,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; + +import org.eclipse.collections.api.factory.Sets; +import org.eclipse.collections.api.set.ImmutableSet; import org.eclipse.collections.api.tuple.Pair; import org.eclipse.collections.impl.tuple.Tuples; import org.finos.legend.engine.language.pure.compiler.Compiler; import org.finos.legend.engine.language.pure.compiler.toPureGraph.HelperModelBuilder; import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; +import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModelProcessParameter; import org.finos.legend.engine.language.pure.compiler.toPureGraph.Warning; import org.finos.legend.engine.language.pure.grammar.from.PureGrammarParser; import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; @@ -124,6 +129,89 @@ public static Pair test(String str, String expe throw new RuntimeException(e); } } + + public static Pair partialCompilationTest(String str) + { + return partialCompilationTest(str, null); + } + + public static Pair partialCompilationTest(String str, List expectedEngineExceptions) + { + return partialCompilationTest(str, expectedEngineExceptions, null); + } + + public static Pair partialCompilationTest(String str, List expectedEngineExceptions, List expectedWarnings) + { + try + { + // do a full re-serialization after parsing to make sure the protocol produced is proper + PureModelContextData modelData = PureGrammarParser.newInstance().parseModel(str); + if ((expectedEngineExceptions == null || expectedEngineExceptions.isEmpty()) && (expectedWarnings == null || expectedWarnings.isEmpty())) + { + ObjectMapper objectMapper = ObjectMapperFactory.getNewStandardObjectMapperWithPureProtocolExtensionSupports(); + String json = objectMapper.writeValueAsString(modelData); + modelData = objectMapper.readValue(json, PureModelContextData.class); + } + PureModelProcessParameter pureModelProcessParameter = PureModelProcessParameter.newBuilder().withEnablePartialCompilation(true).build(); + PureModel pureModel = Compiler.compile(modelData, DeploymentMode.TEST, Identity.getAnonymousIdentity().getName(), null, pureModelProcessParameter); + modelData.getElements().parallelStream().forEach(element -> + { + String fullPath; + if (element instanceof Function) + { + Function function = (Function) element; + String functionSignature = HelperModelBuilder.getSignature(function); + fullPath = pureModel.buildPackageString(function._package, functionSignature); + } + else + { + fullPath = element.getPath(); + } + pureModel.getPackageableElement(fullPath, element.sourceInformation); + }); + + Set engineExceptions = pureModel.getEngineExceptions().stream().map(EngineException::toPretty).collect(Collectors.toSet()); + if (expectedEngineExceptions != null) + { + ImmutableSet expectedEngineExceptionsSet = Sets.immutable.withAll(expectedEngineExceptions); + Assert.assertEquals(expectedEngineExceptionsSet, engineExceptions); + } + else + { + Assert.assertTrue("expected no engine exceptions but found " + engineExceptions, engineExceptions.isEmpty()); + } + + if (expectedWarnings == null) + { + Assert.assertTrue("expected no warnings but found " + pureModel.getWarnings().stream().map(Warning::buildPrettyWarningMessage).collect(Collectors.toList()), pureModel.getWarnings().isEmpty()); + } + + if (expectedWarnings != null) + { + List warnings = pureModel.getWarnings().stream().map(Warning::buildPrettyWarningMessage).sorted().collect(Collectors.toList()); + Collections.sort(expectedWarnings); + Assert.assertEquals(expectedWarnings, warnings); + } + + return Tuples.pair(modelData, pureModel); + } + catch (EngineException e) + { + if (expectedEngineExceptions == null) + { + throw e; + } + Assert.assertEquals("There should only be one expected engine exception but was provided " + expectedEngineExceptions.size(), 1, expectedEngineExceptions.size()); + Assert.assertNotNull("No source information provided in error", e.getSourceInformation()); + MatcherAssert.assertThat(EngineException.buildPrettyErrorMessage(e.getMessage(), e.getSourceInformation(), + e.getErrorType()), CoreMatchers.startsWith(expectedEngineExceptions.get(0))); + return null; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } } @Test diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/fromGrammar/TestDomainCompilationFromGrammar.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/fromGrammar/TestDomainCompilationFromGrammar.java index bf5fb412833..6873d355eb9 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/fromGrammar/TestDomainCompilationFromGrammar.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/fromGrammar/TestDomainCompilationFromGrammar.java @@ -63,6 +63,18 @@ public String getDuplicatedElementTestExpectedErrorMessage() return "COMPILATION error at [5:1-7:1]: Duplicated element 'anything::somethingelse'"; } + @Test + public void testPartialCompilationDuplicatedElement() + { + partialCompilationTest("Class anything::class {}\n" + + "###Mapping\n" + + "Mapping anything::somethingelse ()\n" + + "###Pure\n" + + "Class anything::somethingelse\n" + + "{\n" + + "}\n", Arrays.asList("COMPILATION error at [5:1-7:1]: Duplicated element 'anything::somethingelse'")); + } + @Test public void testDuplicatedDomainElements() { @@ -296,6 +308,26 @@ public void testDuplicateClassPropertyWarning() throws Exception "}\n", null, Arrays.asList("COMPILATION error at [3:4-28]: Found duplicated property 'property' in class 'test::A'", "COMPILATION error at [5:4-21]: Found duplicated property 'other' in class 'test::A'")); } + @Test + public void testPartialCompilationDuplicateAssociationPropertyWarningAndDuplicateClassPropertyWarning() + { + partialCompilationTest("Class test::A {}\n" + + "Class test::B {}\n" + + "Association test::C\n" + + "{\n" + + " property1: test::A[0..1];\n" + + " property1: test::B[1];\n" + + "}\n" + + "Class test::D\n" + + "{\n" + + " property : Integer[0..1];\n" + + " property : String[1];\n" + + " other : String[1];\n" + + " ok : String[1];\n" + + " other: String[1];\n" + + "}\n", null, Arrays.asList("COMPILATION error at [5:4-28]: Found duplicated property 'property1' in association 'test::C'", "COMPILATION error at [10:4-28]: Found duplicated property 'property' in class 'test::D'", "COMPILATION error at [12:4-21]: Found duplicated property 'other' in class 'test::D'")); + } + @Test public void testSuperTypeDuplication() { @@ -570,6 +602,24 @@ public void testComplexConstraint() "}\n"); } + @Test + public void testPartialCompilationComplexConstraint() + { + partialCompilationTest("Class test::A\n" + + "[\n" + + " constraint1\n" + + " (" + + " ~externalId: 'ext ID'\n" + + " ~function: if($this.ok == 'ok', |true, |false)\n" + + " ~enforcementLevel: Warn\n" + + " ~message: $this.ok + ' is not ok'\n" + + " )\n" + + "]\n" + + "{\n" + + " ok: Integer[1..2];\n" + + "}\n"); + } + @Test public void testFunctionWithExpressionInParameter() { @@ -794,7 +844,7 @@ public void testGoodQualifiedProperty() } @Test - public void testFailedToFindQuailifiedProperty() + public void testFailedToFindQualifiedProperty() { test("Class test::Dog\n" + "{\n" + @@ -823,6 +873,31 @@ public void testMissingVariableName() "}\n", "COMPILATION error at [7:22-25]: Can't find variable class for variable 'src' in the graph"); } + @Test + public void testPartialCompilationFailedToFindQualifiedPropertyAndMissingVariableName() + { + partialCompilationTest("Class test::Dog\n" + + "{\n" + + " funcDog(){'my Name is Bobby';}:String[1];\n" + + "}\n" + + "Class test::B\n" + + "{\n" + + " funcB(s: test::Dog[1]){$s.WhoopsfuncDog()}:String[1];\n" + + "}\n" + + "Class test::C\n" + + "{\n" + + " test(s: test::B[1], d: test::Dog[1]){$s.funcB($d) + '!'}:String[1];\n" + + "}\n" + + "Enum test::A\n" + + "{\n" + + " A, a \n" + + "}\n" + + "Class test::D\n" + + "{\n" + + " xza(s: String[1]){$src}:String[1];\n" + + "}\n", Arrays.asList("COMPILATION error at [7:31-43]: Can't find property 'WhoopsfuncDog' in class 'test::Dog'", "COMPILATION error at [19:22-25]: Can't find variable class for variable 'src' in the graph")); + } + @Test public void testMapLambdaInferenceWithPrimitive() { @@ -909,6 +984,32 @@ public void testSortByLambdaInferenceWithClass() "}", "COMPILATION error at [1:84-86]: Can't find property 'nam' in class 'test::A'"); } + @Test + public void testPartialCompilationPackageableElementMismatchWithGetAllAndSortByLambdaInferenceWithClass() + { + partialCompilationTest("###Pure\n" + + "Class test::A" + + "{ prop : String[1];" + + "}" + + "Class test::B" + + "{" + + " z(){test::MyMapping.all()->map(a|$a.nam)}:String[*];" + + "}\n" + + "Class test::C" + + "{" + + " name : String[1];" + + "}" + + "" + + "Class test::D" + + "{" + + " z(){test::C.all()->sortBy(a|$a.nam)}:test::C[*];" + + "}\n" + + "###Mapping\n" + + "Mapping test::MyMapping\n" + + "(\n" + + ")\n", Arrays.asList("COMPILATION error at [2:70-75]: Can't find a match for function 'getAll(Mapping[1])'", "COMPILATION error at [3:84-86]: Can't find property 'nam' in class 'test::C'")); + } + @Test public void testFilterLambdaInferenceWithClass() { @@ -1125,6 +1226,50 @@ public void testProjectWithSubsetColInferenceWithClass() "}", "COMPILATION error at [1:101-105]: Can't find property 'xname' in class 'test::A'"); } + @Test + public void testPartialCompilationProjectColInferenceWithClassAndProjectWithSubsetColInferenceWithClass() + { + partialCompilationTest("Class test::A" + + "{" + + " name : String[1];" + + "}" + + "" + + "Class test::B" + + "{" + + " z(){test::A.all()->project([col(a|$a.name, 'a')])}:meta::pure::tds::TabularDataSet[1];" + + " y(){test::A.all()->project(col(a|$a.name, 'a'))}:meta::pure::tds::TabularDataSet[1];" + + "}", null); + + partialCompilationTest("Class test::C" + + "{" + + " name : String[1];" + + "}" + + "" + + "Class test::D" + + "{" + + " z(){test::C.all()->project([col(c|$c.naxme, 'c')])}:meta::pure::tds::TabularDataSet[1];" + + "}\n" + + "Class test::A" + + "{" + + " name : String[1];" + + "}" + + "" + + "Class test::B" + + "{" + + " z(){test::A.all()->projectWithColumnSubset([col(a|$a.xname, 'a')], ['a','b'])}:meta::pure::tds::TabularDataSet[1];" + + "}", Arrays.asList("COMPILATION error at [1:90-94]: Can't find property 'naxme' in class 'test::C'", "COMPILATION error at [2:106-110]: Can't find property 'xname' in class 'test::A'")); + + partialCompilationTest("Class test::A" + + "{" + + " name : String[1];" + + "}" + + "" + + "Class test::B" + + "{" + + " z(){test::A.all()->project(col(a|$a.naxme, 'a'))}:meta::pure::tds::TabularDataSet[1];" + + "}", Arrays.asList("COMPILATION error at [1:89-93]: Can't find property 'naxme' in class 'test::A'")); + } + @Test public void testExistsLambdaInferenceWithClass() { @@ -1403,6 +1548,35 @@ public void testConstraint() "}", "COMPILATION error at [2:18-19]: Constraint must be of type 'Boolean'"); } + @Test + public void testPartialCompilationMultiplicityErrorInCollectionAndConstraint() + { + partialCompilationTest("Class test::A\n" + + "{\n" + + " names : String[*];\n" + + " prop() {$this.names->at(0) + 'ok'} : String[1];\n" + + "}"); + + partialCompilationTest("Class test::A\n" + + "{\n" + + " names : String[*];\n" + + " prop() {$this.names + 'ok'} : String[1];\n" + + "}", Arrays.asList("COMPILATION error at [4:18-22]: Collection element must have a multiplicity [1] - Context:[Class 'test::A' Fourth Pass, Qualified Property prop, Applying plus], multiplicity:[*]")); + + partialCompilationTest("Class test::A\n" + + "{\n" + + " names : String[0..1];\n" + + " prop() {$this.names + 'ok'} : String[1];\n" + + "}\n" + + "Class test::B\n" + + "[" + + " $this.names->at(0)" + + "]" + + "{\n" + + " names : String[*];\n" + + "}", Arrays.asList("COMPILATION error at [4:18-22]: Collection element must have a multiplicity [1] - Context:[Class 'test::A' Fourth Pass, Qualified Property prop, Applying plus], multiplicity:[0..1]", "COMPILATION error at [7:18-19]: Constraint must be of type 'Boolean'")); + } + @Test public void testReturnTypeErrorInQualifier() { @@ -1703,6 +1877,43 @@ public void testDeepfetchPropertyError() "}\n", "COMPILATION error at [14:17-21]: Can't find property 'first' in [Person, Any]"); } + @Test + public void testPartialCompilationDeepfetchPropertyErrorAndMissingProperty() + { + partialCompilationTest("import anything::*;\n" + + "import test::*;\n" + + "Class test::trial {\n" + + " name: ritual[*];\n" + + " anotherOne(){$this.name->toOne()}: ritual[1];\n" + + " \n" + + "}\n" + + "Class test::trial2 {\n" + + " name: trial[*];\n" + + " anotherOne(){$this.name->toOne().name2}: ritual[1];\n" + + " \n" + + "}\n" + + "\n" + + "Enum anything::ritual {\n" + + " theGoodOne \n" + + "}\n" + + "Class test::Person\n" + + "{\n" + + " firstName:String[1];\n" + + " lastName:String[1];\n" + + "}\n" + + "Class test::Firm\n" + + "{\n" + + " employees:test::Person[*];\n" + + "}\n" + + "Class test::Test\n" + + "{\n" + + // intentionally mess up the spacing in the deep fetch to see if we send the full string (with whitespaces) to the graph fetch tree parser + " x(){test::Person.all()->graphFetch(#{\n" + + " test::Person{\n" + + " first}}#);true;}:Boolean[1];\n" + + "}\n", Arrays.asList("COMPILATION error at [10:37-41]: Can't find property 'name2' in class 'test::trial'", "COMPILATION error at [30:17-21]: Can't find property 'first' in [Person, Any]")); + } + @Test public void testDeepfetchTypeError() {