diff --git a/.gitignore b/.gitignore index e8b7dfc37..5c71c9b05 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,6 @@ rosetta-profiling/reports/ **/xtend-gen/ **/emf-gen/ **/src/generated/ -**/xsemantics-gen/ .antlr-generator-3.2.0-patch.jar com.regnosys.rosetta.web/ diff --git a/README.md b/README.md index 61d70f8b3..820742253 100644 --- a/README.md +++ b/README.md @@ -82,13 +82,6 @@ Install the latest version of the "Eclipse IDE for Java and DSL Developers" usin #### Install the Checkstyle plugin We use [Checkstyle](https://checkstyle.sourceforge.io/) for enforcing good coding practices. The Eclipse plugin for Checkstyle can be found here: [https://checkstyle.org/eclipse-cs/#!/](https://checkstyle.org/eclipse-cs/#!/). -#### Install the Xsemantics plugin -We use the [Xsemantics DSL](https://github.com/eclipse/xsemantics) to define the type system of Rune. To enable language support for it in Eclipse, follow these steps: -1. Find out which version of Xsemantics you need by looking in the `pom.xml` file of the parent project. There should be a property called `xsemantics.version`. -2. Go to Help > Install New Software... -3. In 'Work with' fill in [https://download.eclipse.org/xsemantics/milestones/](https://download.eclipse.org/xsemantics/milestones/). -4. Install the appropriate version of XSemantics. - #### Setup the project 1. **Open the project in Eclipse**: File > Open Projects from File System..., select the right folder, click Finish. 2. **Update Maven dependencies**: right click on the `com.regnosys.rosetta.parent` project > Maven > Update project... and finish. @@ -106,7 +99,6 @@ If you're seeing 1000+ errors in the "Problems" window of Eclipse, try the follo Support for developing Xtext projects in Intellij is limited. It has no support for - editing `Xtend` files - editing the `Xtext` file -- editing the `Xsemantics` file - running `GenerateRosetta.mwe2`. You can however let Maven take care of that, and still edit regular Java files, run tests, etc. diff --git a/pom.xml b/pom.xml index 0f318bcdb..b334e17d5 100644 --- a/pom.xml +++ b/pom.xml @@ -101,7 +101,6 @@ 2.27.0 3.14.0 1.12.0 - 1.22.0 2.2 1.6.0 3.9.8 @@ -275,22 +274,6 @@ org.eclipse.emf.ecore.xcore.lib ${org.eclipse.emf.ecore.xcore.lib.version} - - org.eclipse.xsemantics - org.eclipse.xsemantics.runtime - ${xsemantics.version} - - - ch.qos.reload4j - reload4j - - - - - org.eclipse.xsemantics - org.eclipse.xsemantics.dsl - ${xsemantics.version} - org.yaml snakeyaml @@ -451,7 +434,6 @@ ${project.basedir}/src-gen/main/java ${project.basedir}/emf-gen/main/java ${project.basedir}/xtend-gen/main/java - ${project.basedir}/xsemantics-gen/main/java diff --git a/rosetta-ide/rosetta.tmLanguage.yaml b/rosetta-ide/rosetta.tmLanguage.yaml index d3ef0e765..7bdaced2f 100644 --- a/rosetta-ide/rosetta.tmLanguage.yaml +++ b/rosetta-ide/rosetta.tmLanguage.yaml @@ -36,7 +36,7 @@ variables: unambiguousRootEnd: (?={{unambiguousRootStart}}) sectionStart: '{{wordStart}}((post-)?condition|set|add|inputs|output|alias){{wordEnd}}' sectionEnd: (?={{sectionStart}}|{{rootStart}})(?!\bcondition\b)|(?=\bcondition\b\s*({{identifier}})?:) - functionalOperation: '{{wordStart}}(reduce|filter|map|extract|sort|min|max){{wordEnd}}' + functionalOperation: '{{wordStart}}(reduce|filter|extract|sort|min|max){{wordEnd}}' listOperationWord: '{{functionalOperation}}|{{wordStart}}(single|multiple|exists|is|absent|only-element|count|flatten|distinct|reverse|first|last|sum){{wordEnd}}' listOperation: ->>|->|{{listOperationWord}} synonymAnnotationSimpleSection: '{{wordStart}}(value|meta|definition|pattern|removeHtml|dateFormat|mapper|hint|merge|condition-func|condition-path){{wordEnd}}' diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java index 78d6dd70f..775d325f3 100644 --- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/quickfix/RosettaQuickFixProvider.java @@ -69,15 +69,4 @@ public void fixMandatoryThen(DiagnosticResolutionAcceptor acceptor) { return List.of(edit); }); } - - @QuickFix(RosettaIssueCodes.DEPRECATED_MAP) - public void fixDeprecatedMap(DiagnosticResolutionAcceptor acceptor) { - acceptor.accept("Replace with `extract`.", (Diagnostic diagnostic, EObject object, Document document) -> { - RosettaUnaryOperation op = (RosettaUnaryOperation)object; - Range range = rangeUtils.getRange(op, ROSETTA_OPERATION__OPERATOR); - String edited = "extract"; - TextEdit edit = new TextEdit(range, edited); - return List.of(edit); - }); - } } diff --git a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/contentassist/ContentAssistTest.xtend b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/contentassist/ContentAssistTest.xtend index 41bcf0bba..54c08731e 100644 --- a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/contentassist/ContentAssistTest.xtend +++ b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/contentassist/ContentAssistTest.xtend @@ -45,7 +45,6 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest { is -> is [[9, 14] .. [9, 14]] join -> join [[9, 14] .. [9, 14]] last -> last [[9, 14] .. [9, 14]] - map -> map [[9, 14] .. [9, 14]] max -> max [[9, 14] .. [9, 14]] min -> min [[9, 14] .. [9, 14]] multiple -> multiple [[9, 14] .. [9, 14]] @@ -134,7 +133,6 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest { join -> join [[7, 27] .. [7, 27]] last -> last [[7, 27] .. [7, 27]] library -> library [[7, 27] .. [7, 27]] - map -> map [[7, 27] .. [7, 27]] max -> max [[7, 27] .. [7, 27]] metaType -> metaType [[7, 27] .. [7, 27]] min -> min [[7, 27] .. [7, 27]] @@ -219,7 +217,6 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest { is -> is [[6, 25] .. [6, 25]] join -> join [[6, 25] .. [6, 25]] last -> last [[6, 25] .. [6, 25]] - map -> map [[6, 25] .. [6, 25]] max -> max [[6, 25] .. [6, 25]] min -> min [[6, 25] .. [6, 25]] multiple -> multiple [[6, 25] .. [6, 25]] @@ -414,7 +411,6 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest { item -> item [[19, 2] .. [19, 2]] join -> join [[19, 2] .. [19, 2]] last -> last [[19, 2] .. [19, 2]] - map -> map [[19, 2] .. [19, 2]] max -> max [[19, 2] .. [19, 2]] min -> min [[19, 2] .. [19, 2]] multiple -> multiple [[19, 2] .. [19, 2]] diff --git a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/quickfix/QuickFixTest.xtend b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/quickfix/QuickFixTest.xtend index 5af1e88c8..03bcc52b3 100644 --- a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/quickfix/QuickFixTest.xtend +++ b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/quickfix/QuickFixTest.xtend @@ -29,7 +29,7 @@ class QuickFixTest extends AbstractRosettaLanguageServerTest { ''' testCodeAction[ it.model = model - assertCodeActions = [ + it.assertCodeActions = [ assertEquals(2, size) val sorted = it.sortWith[a,b| ru.comparePositions(a.getRight.diagnostics.head.range.start, b.getRight.diagnostics.head.range.start)] @@ -54,37 +54,4 @@ class QuickFixTest extends AbstractRosettaLanguageServerTest { ] ] } - - @Test - def testQuickFixDeprecatedMap() { - val model = ''' - namespace foo.bar - - type Foo: - a int (1..1) - - func Bar: - inputs: foo Foo (1..1) - output: result int (1..1) - - set result: foo map a - ''' - testCodeAction[ - it.model = model - assertCodeActions = [ - assertEquals(1, size) - - val sorted = it.sortWith[a,b| ru.comparePositions(a.getRight.diagnostics.head.range.start, b.getRight.diagnostics.head.range.start)] - - sorted.get(0).getRight => [ - assertEquals("Replace with `extract`.", title) - edit.changes.values.head.head => [ - assertEquals("extract", newText) - assertEquals(new Position(9, 17), range.start) - assertEquals(new Position(9, 20), range.end) - ] - ] - ] - ] - } } \ No newline at end of file diff --git a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/server/ChangeDetectionTest.xtend b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/server/ChangeDetectionTest.xtend index b3a10c781..2f2d3809f 100644 --- a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/server/ChangeDetectionTest.xtend +++ b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/server/ChangeDetectionTest.xtend @@ -32,7 +32,7 @@ class ChangeDetectionTest extends AbstractRosettaLanguageServerValidationTest { // There should be a type error in func `Foo` val issues = diagnostics.get(funcsURI) assertEquals(1, issues.size) - assertEquals("Expected type 'int' but was 'string'", issues.head.message) + assertEquals("Expected type `int`, but got `string` instead. Cannot assign `string` to output `result`", issues.head.message) } @Test @@ -62,7 +62,7 @@ class ChangeDetectionTest extends AbstractRosettaLanguageServerValidationTest { // There should be a cardinality error in func `Foo` val issues = diagnostics.get(funcsURI) assertEquals(1, issues.size) - assertEquals("Cardinality mismatch - cannot assign list to a single value.", issues.head.message) + assertEquals("Expecting single cardinality. Cannot assign a list to a single value", issues.head.message) } @Test @@ -107,7 +107,7 @@ class ChangeDetectionTest extends AbstractRosettaLanguageServerValidationTest { // There should be a type error in func `Foo` val issues = diagnostics.get(funcsURI) assertEquals(1, issues.size) - assertEquals("Expected type 'MyType' but was 'MyType'", issues.head.message) + assertEquals("Expected type `foo.MyType`, but got `bar.MyType` instead. Cannot assign `bar.MyType` to output `result`", issues.head.message) } @Test @@ -133,8 +133,8 @@ class ChangeDetectionTest extends AbstractRosettaLanguageServerValidationTest { // There should be a type error in rule B val issues = diagnostics.get(ruleBURI) - assertEquals(2, issues.size) - assertEquals("Expected type 'int' but was 'string'", issues.head.message) + assertEquals(1, issues.size) + assertEquals("Expected type `int`, but got `string` instead. Rule `A` cannot be called with type `string`", issues.head.message) } @Test @@ -164,6 +164,6 @@ class ChangeDetectionTest extends AbstractRosettaLanguageServerValidationTest { // There should be a type error in func Foo val issues = diagnostics.get(funcURI) assertEquals(1, issues.size) - assertEquals("Expected type 'int' but was 'string'", issues.head.message) + assertEquals("Expected type `int`, but got `string` instead. Cannot assign `string` to output `result`", issues.head.message) } } diff --git a/rosetta-lang/model/Rosetta.xcore b/rosetta-lang/model/Rosetta.xcore index dac7ee3c6..07c2e8daf 100644 --- a/rosetta-lang/model/Rosetta.xcore +++ b/rosetta-lang/model/Rosetta.xcore @@ -110,7 +110,7 @@ class RosettaExternalFunction extends RosettaRootElement, RosettaTyped, RosettaC } } -class RosettaParameter extends RosettaTyped, RosettaNamed { +class RosettaParameter extends RosettaTyped, RosettaSymbol { boolean isArray } diff --git a/rosetta-lang/pom.xml b/rosetta-lang/pom.xml index 7321b1f45..a444697c4 100644 --- a/rosetta-lang/pom.xml +++ b/rosetta-lang/pom.xml @@ -55,10 +55,6 @@ org.eclipse.xtext org.eclipse.xtext - - org.eclipse.xsemantics - org.eclipse.xsemantics.runtime - org.eclipse.emf org.eclipse.emf.ecore.xcore.lib @@ -124,12 +120,6 @@ **/* - - ${basedir}/../rosetta-lang/xsemantics-gen - - **/* - - ${basedir}/../rosetta-lang/xtend-gen @@ -184,7 +174,7 @@ xtext-maven-plugin - xcore-and-xsemantics-generator + xcore-generator generate-sources generate @@ -194,7 +184,7 @@ ${maven.compiler.release} ${maven.compiler.release} - + true @@ -224,16 +214,6 @@ - - - org.eclipse.xsemantics.dsl.XsemanticsStandaloneSetup - - - - ${project.basedir}/xsemantics-gen/main/java - - - diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext b/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext index 33bdbe341..894fb4960 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext @@ -683,7 +683,7 @@ UnaryOperation returns RosettaExpression: ({ReduceOperation.argument=current} operator='reduce') |({FilterOperation.argument=current} operator='filter') // @Compat: remove `map` - |({MapOperation.argument=current} operator=('map' | 'extract')) + |({MapOperation.argument=current} operator='extract') ) (function=InlineFunction|=>function=ImplicitInlineFunction)? )* | // Without left parameter: @@ -720,7 +720,7 @@ UnaryOperation returns RosettaExpression: ({ReduceOperation} operator='reduce') |({FilterOperation} operator='filter') // @Compat: remove `map` - |({MapOperation} operator=('map' | 'extract')) + |({MapOperation} operator='extract') ) (function=InlineFunction|=>function=ImplicitInlineFunction)? ) ( @@ -758,7 +758,7 @@ UnaryOperation returns RosettaExpression: ({ReduceOperation.argument=current} operator='reduce') |({FilterOperation.argument=current} operator='filter') // @Compat: remove `map` - |({MapOperation.argument=current} operator=('map' | 'extract')) + |({MapOperation.argument=current} operator='extract') ) (function=InlineFunction|=>function=ImplicitInlineFunction)? )* ; diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/RosettaRuntimeModule.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/RosettaRuntimeModule.xtend index a6de2036a..1abc6ea9a 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/RosettaRuntimeModule.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/RosettaRuntimeModule.xtend @@ -23,24 +23,14 @@ import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy import org.eclipse.xtext.parser.IEncodingProvider import com.google.inject.Binder import org.eclipse.xtext.service.DispatchingProvider -import com.regnosys.rosetta.utils.ImplicitVariableUtil -import org.eclipse.xsemantics.runtime.validation.XsemanticsValidatorFilter -import com.regnosys.rosetta.validation.RetainXsemanticsIssuesOnGeneratedInputsFilter import org.eclipse.xtext.conversion.IValueConverterService import com.regnosys.rosetta.parsing.RosettaValueConverterService -import com.regnosys.rosetta.parsing.BigDecimalConverter import com.regnosys.rosetta.transgest.ModelLoader import com.regnosys.rosetta.transgest.ModelLoaderImpl -import com.regnosys.rosetta.formatting2.RosettaExpressionFormatter -import com.regnosys.rosetta.formatting2.FormattingUtil import javax.inject.Provider -import com.regnosys.rosetta.generator.java.util.RecordJavaUtil import com.regnosys.rosetta.serialization.RosettaTransientValueService import org.eclipse.xtext.parsetree.reconstr.ITransientValueService import com.regnosys.rosetta.resource.RosettaResource -import com.regnosys.rosetta.typing.RosettaTyping -import com.regnosys.rosetta.typing.RosettaTypingAuxiliary -import com.regnosys.rosetta.typing.RosettaTypingChecking import org.eclipse.xtext.validation.IResourceValidator import com.regnosys.rosetta.validation.CachingResourceValidator import com.regnosys.rosetta.config.RosettaConfiguration @@ -53,16 +43,6 @@ import com.regnosys.rosetta.formatting2.ResourceFormatterService /* Use this class to register components to be used at runtime / without the Equinox extension registry.*/ class RosettaRuntimeModule extends AbstractRosettaRuntimeModule { - def void configureXsemanticsTypeSystem(Binder binder) { - // During a language server build, the following three classes are injected over and over again - // for each Rosetta resource. This means that code generation is spending up to 54% of its time - // just injecting these classes. By binding them as singletons, this time virtually disappears - // since they will only be instantiated once. - binder.bind(RosettaTyping).asEagerSingleton - binder.bind(RosettaTypingAuxiliary).asEagerSingleton - binder.bind(RosettaTypingChecking).asEagerSingleton - } - override Class bindIFragmentProvider() { RosettaFragmentProvider } @@ -101,19 +81,9 @@ class RosettaRuntimeModule extends AbstractRosettaRuntimeModule { .to(UTF8EncodingProvider); } - def Class bindImplicitVariableUtil() { - ImplicitVariableUtil - } - def Class bindXsemanticsValidatorFilter() { - RetainXsemanticsIssuesOnGeneratedInputsFilter - } - override Class bindIValueConverterService() { RosettaValueConverterService } - def Class bindBigDecimalConverter() { - BigDecimalConverter - } override Class bindXtextResource() { RosettaResource @@ -126,19 +96,6 @@ class RosettaRuntimeModule extends AbstractRosettaRuntimeModule { ModelLoaderImpl } - - def Class bindRosettaExpressionFormatter() { - RosettaExpressionFormatter - } - - def Class bindFormattingUtil() { - FormattingUtil - } - - def Class bindRecordFeatureMap() { - RecordJavaUtil - } - def Class bindIResourceValidator() { CachingResourceValidator } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/DeepPathUtilGenerator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/DeepPathUtilGenerator.xtend index 661b9355a..446cb2620 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/DeepPathUtilGenerator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/DeepPathUtilGenerator.xtend @@ -105,7 +105,7 @@ class DeepPathUtilGenerator { for (a : attrs.reverseView) { val currAcc = acc acc = inputParameter - .attributeCall(choiceType.withEmptyMeta, a, false, a.toMetaJavaType, scope) + .attributeCall(choiceType.withNoMeta, a, false, a.toMetaJavaType, scope) .declareAsVariable(true, a.name.toFirstLower, scope) .mapExpression[attrVar| attrVar.exists(ExistsModifier.NONE, scope) diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/ExpressionGenerator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/ExpressionGenerator.xtend index f93282bd1..7f9e53571 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/ExpressionGenerator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/ExpressionGenerator.xtend @@ -98,7 +98,6 @@ import com.regnosys.rosetta.types.RFunction import com.regnosys.rosetta.types.RMetaAnnotatedType import com.regnosys.rosetta.types.RObjectFactory import com.regnosys.rosetta.types.RShortcut -import com.regnosys.rosetta.types.RosettaOperators import com.regnosys.rosetta.types.RosettaTypeProvider import com.regnosys.rosetta.types.TypeSystem import com.regnosys.rosetta.types.builtin.RBasicType @@ -136,9 +135,9 @@ import org.eclipse.xtext.EcoreUtil2 import org.eclipse.xtext.xbase.lib.Functions.Function3 import static extension com.regnosys.rosetta.generator.java.enums.EnumHelper.convertValue -import static extension com.regnosys.rosetta.types.RMetaAnnotatedType.withEmptyMeta import com.regnosys.rosetta.generator.java.types.RJavaFieldWithMeta import com.regnosys.rosetta.generator.java.types.RJavaWithMetaValue +import static extension com.regnosys.rosetta.types.RMetaAnnotatedType.withNoMeta class ExpressionGenerator extends RosettaExpressionSwitch { @@ -148,7 +147,6 @@ class ExpressionGenerator extends RosettaExpressionSwitch caseTimeType(RBasicType type, Void context) { return typeUtil.LOCAL_TIME; } @Override - protected JavaType caseMissingType(RBasicType type, Void context) { - throw new IllegalArgumentException("Cannot convert a missing type to a Java type."); - } - @Override protected JavaClass caseNothingType(RBasicType type, Void context) { return typeUtil.VOID; } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/interpreter/RosettaValueFactory.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/interpreter/RosettaValueFactory.java index 3dff8ed78..02013008a 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/interpreter/RosettaValueFactory.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/interpreter/RosettaValueFactory.java @@ -29,7 +29,6 @@ import com.regnosys.rosetta.types.RChoiceType; import com.regnosys.rosetta.types.RDataType; import com.regnosys.rosetta.types.REnumType; -import com.regnosys.rosetta.types.RErrorType; import com.regnosys.rosetta.types.RType; import com.regnosys.rosetta.types.builtin.RBasicType; import com.regnosys.rosetta.types.builtin.RBuiltinTypeService; @@ -61,11 +60,6 @@ public RosettaValue createOfType(RType type, Object item) { return createOfType(type, List.of(item)); } - @Override - protected RosettaValue caseErrorType(RErrorType type, List context) { - throw new UnsupportedOperationException("Cannot create a value of error type " + type); - } - @Override protected RosettaValue caseDataType(RDataType type, List context) { throw new UnsupportedOperationException("Data type is unsupported"); @@ -111,11 +105,6 @@ protected RosettaValue caseTimeType(RBasicType type, List context) { // return new RosettaPatternValue(castList(context, Pattern.class)); // } - @Override - protected RosettaValue caseMissingType(RBasicType type, List context) { - throw new UnsupportedOperationException("Cannot create a value of a missing type"); - } - @Override protected RosettaValue caseNothingType(RBasicType type, List context) { if (!context.isEmpty()) { diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/RosettaScopeProvider.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/RosettaScopeProvider.xtend index 972bd0f93..7f2511c88 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/RosettaScopeProvider.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/RosettaScopeProvider.xtend @@ -62,7 +62,7 @@ import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.* -import static extension com.regnosys.rosetta.types.RMetaAnnotatedType.withEmptyMeta +import static extension com.regnosys.rosetta.types.RMetaAnnotatedType.withNoMeta import com.regnosys.rosetta.rosetta.simple.Attribute /** @@ -110,13 +110,13 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { } case CHOICE_OPERATION__ATTRIBUTES: { if (context instanceof ChoiceOperation) { - return createExtendedFeatureScope(context.argument, typeProvider.getRMetaAnnotatedType(context.argument).RType.withEmptyMeta) + return createExtendedFeatureScope(context.argument, typeProvider.getRMetaAnnotatedType(context.argument).RType.withNoMeta) } return IScope.NULLSCOPE } case ROSETTA_ATTRIBUTE_REFERENCE__ATTRIBUTE: { if (context instanceof RosettaAttributeReference) { - return createExtendedFeatureScope(context.receiver, typeProvider.getRTypeOfAttributeReference(context.receiver).withEmptyMeta) + return createExtendedFeatureScope(context.receiver, typeProvider.getRTypeOfAttributeReference(context.receiver).withNoMeta) } return IScope.NULLSCOPE } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/CardinalityProvider.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/CardinalityProvider.xtend index b801396d0..18a75f701 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/CardinalityProvider.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/CardinalityProvider.xtend @@ -72,6 +72,7 @@ import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall import com.regnosys.rosetta.rosetta.expression.DefaultOperation import com.regnosys.rosetta.rosetta.expression.SwitchOperation import com.regnosys.rosetta.rosetta.expression.SwitchCase +import com.regnosys.rosetta.rosetta.RosettaParameter class CardinalityProvider extends RosettaExpressionSwitch { static Logger LOGGER = LoggerFactory.getLogger(CardinalityProvider) @@ -100,6 +101,9 @@ class CardinalityProvider extends RosettaExpressionSwitch { RosettaFeature: { isFeatureMulti(symbol as RosettaFeature, breakOnClosureParameter) } + RosettaParameter: { + false + } ClosureParameter: { isClosureParameterMulti(symbol.function) } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/ExpectedTypeProvider.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/ExpectedTypeProvider.java index d9cd65dc3..ea5e9fb81 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/ExpectedTypeProvider.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/ExpectedTypeProvider.java @@ -78,7 +78,7 @@ import java.util.Objects; import javax.inject.Inject; -import static com.regnosys.rosetta.types.RMetaAnnotatedType.withEmptyMeta; +import static com.regnosys.rosetta.types.RMetaAnnotatedType.withNoMeta; @ImplementedBy(ExpectedTypeProvider.Impl.class) @@ -142,13 +142,13 @@ public RMetaAnnotatedType getExpectedType(EObject owner, EReference reference, i if (operation instanceof ReduceOperation) { return getExpectedTypeFromContainer(operation); } else if (operation instanceof FilterOperation) { - return withEmptyMeta(builtins.BOOLEAN); + return withNoMeta(builtins.BOOLEAN); } else if (operation instanceof MapOperation) { return getExpectedTypeFromContainer(operation); } else if (operation instanceof ThenOperation) { return getExpectedTypeFromContainer(operation); } else if (operation instanceof ComparingFunctionalOperation) { - return withEmptyMeta(builtins.BOOLEAN); + return withNoMeta(builtins.BOOLEAN); } else { LOGGER.debug("Unexpected functional operation of type " + operation.getClass().getCanonicalName()); } @@ -244,7 +244,7 @@ protected RMetaAnnotatedType caseListLiteral(ListLiteral expr, Context context) @Override protected RMetaAnnotatedType caseConditionalExpression(RosettaConditionalExpression expr, Context context) { if (ROSETTA_CONDITIONAL_EXPRESSION__IF.equals(context.reference)) { - return withEmptyMeta(builtins.BOOLEAN); + return withNoMeta(builtins.BOOLEAN); } else if (ROSETTA_CONDITIONAL_EXPRESSION__IFTHEN.equals(context.reference)) { return getExpectedTypeFromContainer(expr); } else if (ROSETTA_CONDITIONAL_EXPRESSION__ELSETHEN.equals(context.reference)) { @@ -306,7 +306,7 @@ protected RMetaAnnotatedType caseSymbolReference(RosettaSymbolReference expr, Co return typeProvider.getRTypeOfSymbol(fun.getInputs().get(context.index)); } else if (symbol instanceof RosettaRule) { RosettaRule rule = (RosettaRule)symbol; - return withEmptyMeta(typeSystem.typeCallToRType(rule.getInput())); + return withNoMeta(typeSystem.typeCallToRType(rule.getInput())); } } return null; @@ -324,27 +324,27 @@ protected RMetaAnnotatedType caseSubtractOperation(ArithmeticOperation expr, Con @Override protected RMetaAnnotatedType caseMultiplyOperation(ArithmeticOperation expr, Context context) { - return withEmptyMeta(builtins.UNCONSTRAINED_NUMBER); + return withNoMeta(builtins.UNCONSTRAINED_NUMBER); } @Override protected RMetaAnnotatedType caseDivideOperation(ArithmeticOperation expr, Context context) { - return withEmptyMeta(builtins.UNCONSTRAINED_NUMBER); + return withNoMeta(builtins.UNCONSTRAINED_NUMBER); } @Override protected RMetaAnnotatedType caseJoinOperation(JoinOperation expr, Context context) { - return withEmptyMeta(builtins.UNCONSTRAINED_STRING); + return withNoMeta(builtins.UNCONSTRAINED_STRING); } @Override protected RMetaAnnotatedType caseAndOperation(LogicalOperation expr, Context context) { - return withEmptyMeta(builtins.BOOLEAN); + return withNoMeta(builtins.BOOLEAN); } @Override protected RMetaAnnotatedType caseOrOperation(LogicalOperation expr, Context context) { - return withEmptyMeta(builtins.BOOLEAN); + return withNoMeta(builtins.BOOLEAN); } @Override @@ -501,7 +501,7 @@ protected RMetaAnnotatedType caseToStringOperation(ToStringOperation expr, Conte @Override protected RMetaAnnotatedType caseToNumberOperation(ToNumberOperation expr, Context context) { if (ROSETTA_UNARY_OPERATION__ARGUMENT.equals(context.reference)) { - return withEmptyMeta(builtins.UNCONSTRAINED_STRING); + return withNoMeta(builtins.UNCONSTRAINED_STRING); } return null; } @@ -509,7 +509,7 @@ protected RMetaAnnotatedType caseToNumberOperation(ToNumberOperation expr, Conte @Override protected RMetaAnnotatedType caseToIntOperation(ToIntOperation expr, Context context) { if (ROSETTA_UNARY_OPERATION__ARGUMENT.equals(context.reference)) { - return withEmptyMeta(builtins.UNCONSTRAINED_STRING); + return withNoMeta(builtins.UNCONSTRAINED_STRING); } return null; } @@ -517,7 +517,7 @@ protected RMetaAnnotatedType caseToIntOperation(ToIntOperation expr, Context con @Override protected RMetaAnnotatedType caseToTimeOperation(ToTimeOperation expr, Context context) { if (ROSETTA_UNARY_OPERATION__ARGUMENT.equals(context.reference)) { - return withEmptyMeta(builtins.UNCONSTRAINED_STRING); + return withNoMeta(builtins.UNCONSTRAINED_STRING); } return null; } @@ -525,7 +525,7 @@ protected RMetaAnnotatedType caseToTimeOperation(ToTimeOperation expr, Context c @Override protected RMetaAnnotatedType caseToEnumOperation(ToEnumOperation expr, Context context) { if (ROSETTA_UNARY_OPERATION__ARGUMENT.equals(context.reference)) { - return withEmptyMeta(builtins.UNCONSTRAINED_STRING); + return withNoMeta(builtins.UNCONSTRAINED_STRING); } return null; } @@ -533,7 +533,7 @@ protected RMetaAnnotatedType caseToEnumOperation(ToEnumOperation expr, Context c @Override protected RMetaAnnotatedType caseToDateOperation(ToDateOperation expr, Context context) { if (ROSETTA_UNARY_OPERATION__ARGUMENT.equals(context.reference)) { - return withEmptyMeta(builtins.UNCONSTRAINED_STRING); + return withNoMeta(builtins.UNCONSTRAINED_STRING); } return null; } @@ -541,7 +541,7 @@ protected RMetaAnnotatedType caseToDateOperation(ToDateOperation expr, Context c @Override protected RMetaAnnotatedType caseToDateTimeOperation(ToDateTimeOperation expr, Context context) { if (ROSETTA_UNARY_OPERATION__ARGUMENT.equals(context.reference)) { - return withEmptyMeta(builtins.UNCONSTRAINED_STRING); + return withNoMeta(builtins.UNCONSTRAINED_STRING); } return null; } @@ -549,7 +549,7 @@ protected RMetaAnnotatedType caseToDateTimeOperation(ToDateTimeOperation expr, C @Override protected RMetaAnnotatedType caseToZonedDateTimeOperation(ToZonedDateTimeOperation expr, Context context) { if (ROSETTA_UNARY_OPERATION__ARGUMENT.equals(context.reference)) { - return withEmptyMeta(builtins.UNCONSTRAINED_STRING); + return withNoMeta(builtins.UNCONSTRAINED_STRING); } return null; } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RErrorType.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RErrorType.java deleted file mode 100644 index 2d7f0483b..000000000 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RErrorType.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2024 REGnosys - * - * 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 com.regnosys.rosetta.types; - -import java.util.Objects; - -import com.rosetta.model.lib.ModelSymbolId; -import com.rosetta.util.DottedPath; - -// TODO: remove this type -public class RErrorType extends RType { - private final String message; - - public RErrorType(final String message) { - super(); - this.message = message; - } - - @Override - public ModelSymbolId getSymbolId() { - return null; - } - - @Override - public String getName() { - return this.message; - } - - @Override - public DottedPath getNamespace() { - return null; - } - - @Override - public DottedPath getQualifiedName() { - return DottedPath.of(message); - } - - - public String getMessage() { - return this.message; - } - - @Override - public int hashCode() { - return 31 * 1 + ((this.message == null) ? 0 : this.message.hashCode()); - } - - @Override - public boolean equals(final Object object) { - if (object == null) return false; - if (this.getClass() != object.getClass()) return false; - - RErrorType other = (RErrorType) object; - return Objects.equals(message, other.message); - } -} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RListType.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RListType.java deleted file mode 100644 index b69dbee75..000000000 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RListType.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2024 REGnosys - * - * 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 com.regnosys.rosetta.types; - -import java.util.Objects; - -import com.regnosys.rosetta.rosetta.RosettaCardinality; - -public class RListType { - private final RType itemType; - private final RosettaCardinality constraint; - - public RListType(RType itemType, RosettaCardinality constraint) { - this.itemType = itemType; - this.constraint = constraint; - } - - public RType getItemType() { - return this.itemType; - } - public RosettaCardinality getConstraint() { - return this.constraint; - } - - public boolean isEmpty() { - return this.constraint.isEmpty(); - } - public boolean isOptional() { - return this.constraint.isOptional(); - } - public boolean isSingular() { - return this.constraint.isSingular(); - } - public boolean isPlural() { - return this.constraint.isPlural(); - } - - @Override - public String toString() { - return this.itemType.toString() + " " + constraint.toConstraintString(); - } - - @Override - public boolean equals(final Object object) { - if (object == null) return false; - if (this.getClass() != object.getClass()) return false; - - final RListType other = (RListType) object; - return Objects.equals(itemType, other.itemType) - && this.constraint.constraintEquals(other.constraint); - } - - @Override - public int hashCode() { - int hash = 3; - hash = 53 * hash + (this.itemType == null ? 0 : this.itemType.hashCode()); - hash = 53 * hash + this.constraint.constraintHashCode(); - return hash; - } -} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RMetaAnnotatedType.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RMetaAnnotatedType.java index 68f01f7aa..5d55a5729 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RMetaAnnotatedType.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RMetaAnnotatedType.java @@ -10,12 +10,12 @@ public class RMetaAnnotatedType { private final RType rType; private final List metaAttributes; - public RMetaAnnotatedType(RType rType, List metaAttributes) { + private RMetaAnnotatedType(RType rType, List metaAttributes) { this.rType = rType; this.metaAttributes = Validate.noNullElements(metaAttributes); } - - public static RMetaAnnotatedType withEmptyMeta(RType rType) { + + public static RMetaAnnotatedType withNoMeta(RType rType) { return new RMetaAnnotatedType(rType, List.of()); } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RObjectFactory.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RObjectFactory.java index 19ca76e1b..6ec46e328 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RObjectFactory.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RObjectFactory.java @@ -74,7 +74,7 @@ public RFunction buildRFunction(Function function) { } private RAttribute createArtificialAttribute(String name, RType type, boolean isMulti) { - RMetaAnnotatedType rAnnotatedType = RMetaAnnotatedType.withEmptyMeta(type); + RMetaAnnotatedType rAnnotatedType = RMetaAnnotatedType.withNoMeta(type); return new RAttribute(false, name, null, Collections.emptyList(), rAnnotatedType, isMulti ? RCardinality.UNBOUNDED : RCardinality.OPTIONAL, null, null, this); } public RFunction buildRFunction(RosettaRule rule) { diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RTypeFunction.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RTypeFunction.java index 0968e1440..064e43256 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RTypeFunction.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RTypeFunction.java @@ -30,20 +30,6 @@ public abstract class RTypeFunction extends AbstractModelSymbol { public RTypeFunction(DottedPath namespace, String name) { super(namespace, name); } - - // TODO: limitation of Xsemantics, which doesn't support anonymous classes. - public static RTypeFunction create(DottedPath namespace, String name, Function, RType> evaluate, Function>> reverse) { - return new RTypeFunction(namespace, name) { - @Override - public RType evaluate(Map arguments) { - return evaluate.apply(arguments); - } - @Override - public Optional> reverse(RType type) { - return reverse.apply(type); - } - }; - } public abstract RType evaluate(Map arguments); diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaOperators.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaOperators.xtend deleted file mode 100644 index 4eb6fe33c..000000000 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaOperators.xtend +++ /dev/null @@ -1,112 +0,0 @@ -package com.regnosys.rosetta.types - -import com.google.inject.Singleton -import com.regnosys.rosetta.types.builtin.RBuiltinTypeService -import com.regnosys.rosetta.types.builtin.RStringType -import java.util.Optional -import com.regnosys.rosetta.types.builtin.RNumberType -import com.regnosys.rosetta.utils.OptionalUtil -import javax.inject.Inject - -@Singleton -class RosettaOperators { - - public static val ARITHMETIC_OPS = #['+', '-', '*', '/'] - public static val COMPARISON_OPS = #['<', '<=', '>', '>='] - public static val EQUALITY_OPS = #['=', '<>', 'contains', 'disjoint'] - public static val LOGICAL_OPS = #['and', 'or'] - public static val JOIN_OP = 'join' - public static val DEFAULT_OP = 'default' - - @Inject extension RBuiltinTypeService service - @Inject extension TypeSystem - - def RType resultType(String op, RType left, RType right) { - if (left == NOTHING || right === NOTHING) { - return NOTHING - } - val resultType = if (op == '+') { - if (left.isSubtypeOf(DATE) && right.isSubtypeOf(TIME)) { - DATE_TIME - } else if (left.isSubtypeOf(UNCONSTRAINED_STRING) && right.isSubtypeOf(UNCONSTRAINED_STRING)) { - keepTypeAliasIfPossible(left, right, [l,r| - val s1 = l as RStringType - val s2 = r as RStringType - val newInterval = s1.interval.add(s2.interval) - new RStringType(newInterval, Optional.empty()) - ]) - } else if (left.isSubtypeOf(UNCONSTRAINED_NUMBER) && right.isSubtypeOf(UNCONSTRAINED_NUMBER)) { - keepTypeAliasIfPossible(left, right, [l,r| - val n1 = l as RNumberType - val n2 = r as RNumberType - val newFractionalDigits = OptionalUtil.zipWith(n1.fractionalDigits, n2.fractionalDigits, [a,b|Math.max(a,b)]) - val newInterval = n1.interval.add(n2.interval) - new RNumberType(Optional.empty(), newFractionalDigits, newInterval, Optional.empty()) - ]) - } - } else if (op == '-') { - if (left.isSubtypeOf(DATE) && right.isSubtypeOf(DATE)) { - UNCONSTRAINED_INT - } else if (left.isSubtypeOf(UNCONSTRAINED_NUMBER) && right.isSubtypeOf(UNCONSTRAINED_NUMBER)) { - keepTypeAliasIfPossible(left, right, [l,r| - val n1 = l as RNumberType - val n2 = r as RNumberType - val newFractionalDigits = OptionalUtil.zipWith(n1.fractionalDigits, n2.fractionalDigits, [a,b|Math.max(a,b)]) - val newInterval = n1.interval.subtract(n2.interval) - new RNumberType(Optional.empty(), newFractionalDigits, newInterval, Optional.empty()) - ]) - } - } else if (op == '*') { - if (left.isSubtypeOf(UNCONSTRAINED_NUMBER) && right.isSubtypeOf(UNCONSTRAINED_NUMBER)) { - keepTypeAliasIfPossible(left, right, [l,r| - val n1 = l as RNumberType - val n2 = r as RNumberType - val newFractionalDigits = OptionalUtil.zipWith(n1.fractionalDigits, n2.fractionalDigits, [a,b|a+b]) - val newInterval = n1.interval.multiply(n2.interval) - new RNumberType(Optional.empty(), newFractionalDigits, newInterval, Optional.empty()) - ]) - } - } else if (op == '/') { - if (left.isSubtypeOf(UNCONSTRAINED_NUMBER) && right.isSubtypeOf(UNCONSTRAINED_NUMBER)) { - UNCONSTRAINED_NUMBER - } - } else if (COMPARISON_OPS.contains(op) || EQUALITY_OPS.contains(op)) { - if (left === null || right === null || left.isComparable(right)) { - BOOLEAN - } - } else if (op == JOIN_OP) { - return bothString(left, right, op) - } else if (LOGICAL_OPS.contains(op)) { - return bothBoolean(left, right, op) - } else if (op == DEFAULT_OP) { - val result = left.join(right) - if (result != ANY) { - result - } - } - - if (resultType === null) { - return new RErrorType( - "Incompatible types: cannot use operator '" + op + "' with " + left.name + " and " + - right.name + ".") - } - else - return resultType - } - - def private bothBoolean(RType left, RType right, String op) { - if (!left.isSubtypeOf(BOOLEAN)) - return new RErrorType('''Left hand side of '«op»' expression must be boolean''') - if (!right.isSubtypeOf(BOOLEAN)) - return new RErrorType('''Right hand side of '«op»' expression must be boolean''') - return BOOLEAN - } - - def private bothString(RType left, RType right, String op) { - if (!left.isSubtypeOf(UNCONSTRAINED_STRING)) - return new RErrorType('''Left hand side of '«op»' expression must be string''') - if (!right.isSubtypeOf(UNCONSTRAINED_STRING)) - return new RErrorType('''Right hand side of '«op»' expression must be string''') - return UNCONSTRAINED_STRING - } -} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaTypeChecking.xsemantics b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaTypeChecking.xsemantics deleted file mode 100644 index 02de96f9c..000000000 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaTypeChecking.xsemantics +++ /dev/null @@ -1,554 +0,0 @@ -system com.regnosys.rosetta.typing.RosettaTypingChecking extends RosettaTyping - -import com.regnosys.rosetta.rosetta.RosettaCardinality -import com.regnosys.rosetta.rosetta.RosettaExternalFunction -import com.regnosys.rosetta.rosetta.expression.ArithmeticOperation -import com.regnosys.rosetta.rosetta.expression.CardinalityModifier -import com.regnosys.rosetta.rosetta.expression.ComparisonOperation -import com.regnosys.rosetta.rosetta.expression.EqualityOperation -import com.regnosys.rosetta.rosetta.expression.ExpressionPackage -import com.regnosys.rosetta.rosetta.expression.ListLiteral -import com.regnosys.rosetta.rosetta.expression.LogicalOperation -import com.regnosys.rosetta.rosetta.expression.ModifiableBinaryOperation -import com.regnosys.rosetta.rosetta.expression.RosettaAbsentExpression -import com.regnosys.rosetta.rosetta.expression.RosettaBinaryOperation -import com.regnosys.rosetta.rosetta.expression.RosettaConditionalExpression -import com.regnosys.rosetta.rosetta.expression.RosettaExistsExpression -import com.regnosys.rosetta.rosetta.expression.RosettaExpression -import com.regnosys.rosetta.rosetta.expression.RosettaOnlyExistsExpression -import com.regnosys.rosetta.rosetta.simple.Function -import com.regnosys.rosetta.types.RDataType -import com.regnosys.rosetta.types.RListType -import com.regnosys.rosetta.types.RType -import com.regnosys.rosetta.types.TypeFactory -import com.regnosys.rosetta.types.TypeValidationUtil -import com.regnosys.rosetta.utils.ExpressionHelper - -import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference -import com.regnosys.rosetta.utils.ImplicitVariableUtil -import org.eclipse.xtext.EcoreUtil2 -import com.regnosys.rosetta.rosetta.expression.OneOfOperation -import com.regnosys.rosetta.rosetta.expression.ChoiceOperation -import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* -import com.regnosys.rosetta.types.builtin.RBuiltinTypeService -import com.regnosys.rosetta.interpreter.RosettaInterpreter -import com.regnosys.rosetta.interpreter.RosettaInterpreterContext -import com.regnosys.rosetta.utils.RosettaSimpleSystemSolver -import com.regnosys.rosetta.rosetta.RosettaRule -import com.regnosys.rosetta.types.RChoiceType - -inject extension TypeFactory typeFactory -inject extension TypeValidationUtil util -inject extension ExpressionHelper exprHelper -inject extension ImplicitVariableUtil implicitVarUtil -inject extension RBuiltinTypeService builtinTypes -inject RosettaInterpreter interpreter -inject RosettaSimpleSystemSolver systemSolver - - -auxiliary { - listSubtypeCheck(RosettaExpression sourceObject, RListType expected) - looseListSubtypeCheck(RosettaExpression sourceObject, RListType expected) - subtypeCheck(RosettaExpression sourceObject, RType expected) - comparableListTypeCheck(RosettaBinaryOperation sourceObject) - comparableTypeCheck(RosettaBinaryOperation sourceObject) - onlyRightIsSingularCheck(ModifiableBinaryOperation sourceObject) - looseOnlyRightIsSingularCheck(ModifiableBinaryOperation sourceObject) - constraintCheck(RosettaExpression sourceObject, RosettaCardinality expected) - looseConstraintCheck(RosettaExpression sourceObject, RosettaCardinality expected) - notConstraintCheck(RosettaExpression sourceObject, RosettaCardinality notExpected) - isLooserConstraintCheck(RosettaExpression sourceObject, RosettaCardinality expected) -} - -/****** TYPE VALIDATION UTILITIES *******/ -auxiliary listSubtypeCheck(RosettaExpression sourceObject, RListType expected) { - var RListType actual - empty |- sourceObject : actual or actual = null - if (expected !== null && actual !== null && !{empty |- actual <| expected}) { - fail error notAListSubtypeMessage(expected, actual) - source sourceObject - } -} -auxiliary looseListSubtypeCheck(RosettaExpression sourceObject, RListType expected) { - var RListType actual - empty |- sourceObject : actual or actual = null - if (expected !== null && actual !== null && !({empty |- actual.itemType <: expected.itemType} && overlap(expected.constraint, actual.constraint))) { - fail error notAListSubtypeMessage(expected, actual) - source sourceObject - } -} -auxiliary subtypeCheck(RosettaExpression sourceObject, RType expected) { - var RListType actual - empty |- sourceObject : actual or actual = null - if (expected !== null && actual !== null && !{empty |- actual.itemType <: expected}) { - fail error notASubtypeMessage(expected, actual.itemType) - source sourceObject - } -} -auxiliary comparableListTypeCheck(RosettaBinaryOperation sourceObject) { - var RListType tl - var RListType tr - empty |- sourceObject.left : tl or tl = null - empty |- sourceObject.right : tr or tr = null - if (tl !== null && tr !== null) { - tl.listComparable(tr) - or - fail error notListComparableMessage(tl, tr) - source sourceObject - } -} -auxiliary comparableTypeCheck(RosettaBinaryOperation sourceObject) { - var RListType tl - var RListType tr - empty |- sourceObject.left : tl or tl = null - empty |- sourceObject.right : tr or tr = null - if (tl !== null && tr !== null) { - tl.itemType.comparable(tr.itemType) - or - fail error notComparableMessage(tl.itemType, tr.itemType) - source sourceObject - } -} -auxiliary onlyRightIsSingularCheck(ModifiableBinaryOperation sourceObject) { - var RListType tl - var RListType tr - empty |- sourceObject.left : tl or tl = null - empty |- sourceObject.right : tr or tr = null - if (tl !== null && tr !== null) { - !tl.constraint.constraintEquals(single) && tr.constraint.constraintEquals(single) - or - { - if (tl.constraint.constraintEquals(single)) { - if (tr.constraint.constraintEquals(single)) { - fail error bothAreSingularMessage(sourceObject) - source sourceObject - feature ExpressionPackage.Literals.MODIFIABLE_BINARY_OPERATION__CARD_MOD - } else { - fail error notRightIsSingularButLeftIsMessage(tr) - source sourceObject.right - } - } else { - fail error notConstraintMessage(single, tr) - source sourceObject.right - } - } - } -} -auxiliary looseOnlyRightIsSingularCheck(ModifiableBinaryOperation sourceObject) { - var RListType tl - var RListType tr - empty |- sourceObject.left : tl or tl = null - empty |- sourceObject.right : tr or tr = null - if (tl !== null && tr !== null) { - !tl.constraint.constraintEquals(single) && single.isSubconstraintOf(tr.constraint) - or - { - if (tl.constraint.constraintEquals(single)) { - if (tr.constraint.constraintEquals(single)) { - fail error bothAreSingularMessage(sourceObject) - source sourceObject - feature ExpressionPackage.Literals.MODIFIABLE_BINARY_OPERATION__CARD_MOD - } else { - fail error notRightIsSingularButLeftIsMessage(tr) - source sourceObject.right - } - } else { - fail error notConstraintMessage(single, tr) - source sourceObject.right - } - } - } -} -auxiliary constraintCheck(RosettaExpression sourceObject, RosettaCardinality expected) { - var RListType actual - empty |- sourceObject : actual or actual = null - if (expected !== null && actual !== null) { - expected.constraintEquals(actual.constraint) - or - fail error notConstraintMessage(expected, actual) - source sourceObject - } -} -auxiliary looseConstraintCheck(RosettaExpression sourceObject, RosettaCardinality expected) { - var RListType actual - empty |- sourceObject : actual or actual = null - if (expected !== null && actual !== null) { - expected.isSubconstraintOf(actual.constraint) - or - fail error notConstraintMessage(expected, actual) - source sourceObject - } -} -auxiliary notConstraintCheck(RosettaExpression sourceObject, RosettaCardinality notExpected) { - var RListType actual - empty |- sourceObject : actual or actual = null - if (notExpected !== null && actual !== null) { - !notExpected.constraintEquals(actual.constraint) - or - fail error wrongConstraintMessage(notExpected, actual) - source sourceObject - } -} -auxiliary isLooserConstraintCheck(RosettaExpression sourceObject, RosettaCardinality expected) { - var RListType actual - empty |- sourceObject : actual or actual = null - if (expected !== null && actual !== null) { - expected.isSubconstraintOf(actual.constraint) - or - fail error notLooserConstraintMessage(expected, actual) - source sourceObject - } -} - -/****** CHECK RULES ********/ -checkrule CheckLeftArithmetic for - ArithmeticOperation op -from { - if (op.operator == '+') { // TODO: improve error messages - { - looseListSubtypeCheck(op.left, singleDate) - } or { - looseListSubtypeCheck(op.left, singleUnconstrainedString) - } or { - looseListSubtypeCheck(op.left, singleUnconstrainedNumber) - } - } else if (op.operator == '-') { - { - looseListSubtypeCheck(op.left, singleDate) - } or { - looseListSubtypeCheck(op.left, singleUnconstrainedNumber) - } - } else { - looseListSubtypeCheck(op.left, singleUnconstrainedNumber) - } -} -checkrule CheckRightArithmetic for - ArithmeticOperation op -from { - if (op.operator == '+') { - { - looseListSubtypeCheck(op.right, singleTime) - } or { - looseListSubtypeCheck(op.right, singleUnconstrainedString) - } or { - looseListSubtypeCheck(op.right, singleUnconstrainedNumber) - } - } else if (op.operator == '-') { - { - looseListSubtypeCheck(op.right, singleDate) - } or { - looseListSubtypeCheck(op.right, singleUnconstrainedNumber) - } - } else { - looseListSubtypeCheck(op.right, singleUnconstrainedNumber) - } -} -checkrule CheckAddition for - ArithmeticOperation op -from { - var RListType tl - var RListType tr - empty |- op.left : tl or tl = null - empty |- op.right : tr or tr = null - if (tl !== null && tr !== null) { - if (op.operator == '+') { - { - empty |- tl.itemType <: DATE - empty |- tr.itemType <: TIME - } - or - { - empty |- tl.itemType <: UNCONSTRAINED_STRING - empty |- tr.itemType <: UNCONSTRAINED_STRING - } - or - { - empty |- tl.itemType <: UNCONSTRAINED_NUMBER - empty |- tr.itemType <: UNCONSTRAINED_NUMBER - } - or - fail error "Expected arguments to be either both a `string` or both a `number`, but got `" + relevantItemTypeDescription(tl, tr) + "` and `" + relevantItemTypeDescription(tr, tl) + "` instead." - source op - } else if (op.operator == '-') { - { - empty |- tl.itemType <: DATE - empty |- tr.itemType <: DATE - } - or - { - empty |- tl.itemType <: UNCONSTRAINED_NUMBER - empty |- tr.itemType <: UNCONSTRAINED_NUMBER - } - or - fail error "Expected arguments to be either both a `date` or both a `number`, but got `" + tl.itemType.toString() + "` and `" + tr.itemType.toString() + "` instead." - source op - } - } -} - -checkrule CheckEqualityOperation for - EqualityOperation op -from { - if (op.cardMod === CardinalityModifier.^NONE) { - comparableListTypeCheck(op) - } else { - looseOnlyRightIsSingularCheck(op) - comparableTypeCheck(op) - } -} - -checkrule CheckLeftLogical for - LogicalOperation op -from { - looseListSubtypeCheck(op.left, singleBoolean) -} -checkrule CheckRightLogical for - LogicalOperation op -from { - looseListSubtypeCheck(op.right, singleBoolean) -} - -checkrule CheckLeftComparison for - ComparisonOperation op -from { - if (op.cardMod === CardinalityModifier.^NONE) { // TODO: improve error messages - { - looseListSubtypeCheck(op.left, singleZonedDateTime) - } or { - looseListSubtypeCheck(op.left, singleDate) - } or { - looseListSubtypeCheck(op.left, singleUnconstrainedNumber) - } - } else { - { - subtypeCheck(op.left, ZONED_DATE_TIME) - } or { - subtypeCheck(op.left, DATE) - } or { - subtypeCheck(op.left, UNCONSTRAINED_NUMBER) - } - } -} -checkrule CheckRightComparison for - ComparisonOperation op -from { - if (op.cardMod === CardinalityModifier.^NONE) { - { - looseListSubtypeCheck(op.left, singleZonedDateTime) - } or { - looseListSubtypeCheck(op.right, singleDate) - } or { - looseListSubtypeCheck(op.right, singleUnconstrainedNumber) - } - } else { - { - subtypeCheck(op.left, ZONED_DATE_TIME) - } or { - subtypeCheck(op.right, DATE) - } or { - subtypeCheck(op.right, UNCONSTRAINED_NUMBER) - } - } -} -checkrule CheckComparison for - ComparisonOperation op -from { - var RListType tl - var RListType tr - empty |- op.left : tl or tl = null - empty |- op.right : tr or tr = null - if (tl !== null && tr !== null) { - { - empty |- tl.itemType <: ZONED_DATE_TIME - empty |- tr.itemType <: ZONED_DATE_TIME - } - or - { - empty |- tl.itemType <: DATE - empty |- tr.itemType <: DATE - } - or - { - empty |- tl.itemType <: UNCONSTRAINED_NUMBER - empty |- tr.itemType <: UNCONSTRAINED_NUMBER - } - or - fail error "Expected arguments to be either both a `date`, both a `number` or both a `zonedDateTime`, but got `" + tl.itemType.toString() + "` and `" + tr.itemType.toString() + "` instead." - source op - } - if (op.cardMod !== CardinalityModifier.^NONE) { - looseOnlyRightIsSingularCheck(op) - } -} - -checkrule CheckIfConditionalExpression for - RosettaConditionalExpression e -from { - looseListSubtypeCheck(e.^if, singleBoolean) -} -checkrule CheckBodyConditionalExpression for - RosettaConditionalExpression e -from { - var RListType tthen - var RListType telse - empty |- e.ifthen : tthen or tthen = null - empty |- e.elsethen : telse or telse = null - if (tthen !== null && telse !== null) { - val joined = listJoin(tthen, telse) - - if ({empty |- ANY <: joined.itemType}) { - fail error "Types `" + tthen.itemType.name + "` and `" + telse.itemType.name + "` do not have a common supertype." - } - } -} - -checkrule CheckListLiteral for - ListLiteral e -from { - val telems = newArrayList - if (e.elements.forall[ - var RListType telem - empty |- it : telem or telem = null - if (telem !== null) { - telems.add(telem) - } - telem !== null - ]) { - telems.fold(emptyNothing, [ RListType acc, RListType telem | - if (acc === null) { - null - } else { - val sup = join(telem.itemType, acc.itemType); - if ({empty |- ANY <: sup}) { - null - } else { - createListType(sup, telem.constraint + acc.constraint) - } - } - ]) !== null - or - fail error "Elements do not have a common supertype: " + telems.join(', ')["`" + it.itemType.name + "`"] + "." - } -} - -checkrule CheckRosettaSymbolReference for RosettaSymbolReference e from { - val f = e.symbol - switch f { - RosettaExternalFunction: { - { - f.parameters.size() == e.args.size() - or - fail error "Expected " + f.parameters.size() + " argument" + (if (f.parameters.size() === 1) "" else "s") + ", but got " + e.args.size() + " instead." - } - (0..> { + +class RosettaTypeProvider extends RosettaExpressionSwitch> { public static String EXPRESSION_RTYPE_CACHE_KEY = RosettaTypeProvider.canonicalName + ".EXPRESSION_RTYPE" - @Inject extension RosettaOperators - @Inject IQualifiedNameProvider qNames @Inject RosettaEcoreUtil extensions @Inject extension ImplicitVariableUtil @Inject extension TypeSystem @@ -165,60 +165,69 @@ class RosettaTypeProvider extends RosettaExpressionSwitch cycleTracker) { + private def RMetaAnnotatedType safeRType(RosettaSymbol symbol, EObject context, Map cycleTracker) { if (!extensions.isResolved(symbol)) { - return NOTHING.withEmptyMeta + return NOTHING_WITH_NO_META } - switch symbol { - RosettaFeature: { - safeRType(symbol as RosettaFeature, context, cycleTracker) - } - ClosureParameter: { - val setOp = symbol.function.eContainer as RosettaFunctionalOperation - if(setOp !== null) { - setOp.argument.safeRType(cycleTracker) - } else - MISSING.withEmptyMeta - } - RosettaEnumeration: { // @Compat: RosettaEnumeration should not be a RosettaSymbol. - symbol.buildREnumType.withMeta(symbol.RMetaAttributesOfSymbol) - } - Function: { - if (symbol.output !== null) { - safeRType(symbol.output as RosettaFeature, context, cycleTracker) - } else { - MISSING.withEmptyMeta + val existing = cycleTracker.get(symbol) + if (existing !== null) { + return existing + } + cycleTracker.put(symbol, NOTHING_WITH_NO_META) + val result = + switch symbol { + RosettaFeature: { + safeRType(symbol as RosettaFeature, context, cycleTracker) } - } - RosettaRule: { - if (symbol.expression !== null) { - safeRType(symbol.expression, cycleTracker) - } else { - MISSING.withEmptyMeta + RosettaParameter: { + symbol.typeCall.typeCallToRType.withNoMeta + } + ClosureParameter: { + val setOp = symbol.function.eContainer as RosettaFunctionalOperation + if(setOp !== null) { + setOp.argument.safeRType(cycleTracker) + } else + NOTHING_WITH_NO_META + } + RosettaEnumeration: { // @Compat: RosettaEnumeration should not be a RosettaSymbol. + symbol.buildREnumType.withMeta(symbol.RMetaAttributesOfSymbol) + } + Function: { + if (symbol.output !== null) { + safeRType(symbol.output as RosettaFeature, context, cycleTracker) + } else { + NOTHING_WITH_NO_META + } + } + RosettaRule: { + if (symbol.expression !== null) { + safeRType(symbol.expression, cycleTracker) + } else { + NOTHING_WITH_NO_META + } + } + RosettaExternalFunction: { + symbol.typeCall.typeCallToRType.withNoMeta + } + ShortcutDeclaration: { + val type = symbol.expression.safeRType(cycleTracker) + type + } + TypeParameter: { + symbol.typeCall.typeCallToRType.withNoMeta } } - RosettaExternalFunction: { - symbol.typeCall.typeCallToRType.withEmptyMeta - } - ShortcutDeclaration: { - cycleTracker.put(symbol, null) - val type = symbol.expression.safeRType(cycleTracker) - cycleTracker.put(symbol, type) - type - } - TypeParameter: { - symbol.typeCall.typeCallToRType.withEmptyMeta - } - } + cycleTracker.put(symbol, result) + return result } - private def RMetaAnnotatedType safeRType(RosettaFeature feature, EObject context, Map cycleTracker) { + private def RMetaAnnotatedType safeRType(RosettaFeature feature, EObject context, Map cycleTracker) { if (!extensions.isResolved(feature)) { - return NOTHING.withEmptyMeta + return NOTHING_WITH_NO_META } switch (feature) { RosettaTypedFeature: { val featureType = if (feature.typeCall === null) { - NOTHING.withEmptyMeta + NOTHING_WITH_NO_META } else { feature.typeCall.typeCallToRType.withMeta(feature.RMetaAttributesOfFeature) } @@ -228,29 +237,19 @@ class RosettaTypeProvider extends RosettaExpressionSwitch cycleTracker) { - getRTypeFromCache(EXPRESSION_RTYPE_CACHE_KEY, expression, [ - if (cycleTracker.containsKey(expression)) { - val computed = cycleTracker.get(expression) - if (computed === null) { - return new RErrorType('''Can't infer type due to a cyclic reference of «qNames.getFullyQualifiedName(expression)»''').withEmptyMeta - } else { - return computed - } - } - if (!extensions.isResolved(expression)) { - return NOTHING.withEmptyMeta - } - doSwitch(expression, cycleTracker) - ]) + private def RMetaAnnotatedType safeRType(RosettaExpression expression, Map cycleTracker) { + if (expression === null) { + return NOTHING_WITH_NO_META + } + getRTypeFromCache(EXPRESSION_RTYPE_CACHE_KEY, expression, [doSwitch(expression, cycleTracker)]) } private def RMetaAnnotatedType getRTypeFromCache(String cacheKey, EObject object, Provider typeProvider) { @@ -264,112 +263,130 @@ class RosettaTypeProvider extends RosettaExpressionSwitch cycleTracker) { + private def RMetaAnnotatedType safeTypeOfImplicitVariable(EObject context, Map cycleTracker) { val definingContainer = context.findContainerDefiningImplicitVariable definingContainer.map [ if (it instanceof Data) { - buildRDataType.withEmptyMeta + buildRDataType.withNoMeta } else if (it instanceof RosettaFunctionalOperation) { safeRType(argument, cycleTracker) } else if (it instanceof RosettaRule) { - input?.typeCallToRType.withEmptyMeta ?: MISSING.withEmptyMeta + input?.typeCallToRType?.withNoMeta ?: NOTHING_WITH_NO_META } else if (it instanceof SwitchCase) { guard.choiceOptionGuard.RTypeOfSymbol } - ].orElse(MISSING.withEmptyMeta) - } - - private def caseBinaryOperation(RosettaBinaryOperation expr, Map cycleTracker) { - val left = expr.left - var leftType = left.safeRType(cycleTracker) - if (leftType.RType instanceof RErrorType) { - return NOTHING.withEmptyMeta - } - val right = expr.right - var rightType = right.safeRType(cycleTracker) - if (rightType.RType instanceof RErrorType) { - return NOTHING.withEmptyMeta + ].orElse(NOTHING_WITH_NO_META) + } + + override protected caseAbsentOperation(RosettaAbsentExpression expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META + } + + override protected caseAddOperation(ArithmeticOperation expr, Map cycleTracker) { + val left = expr.left.safeRType(cycleTracker) + val right = expr.right.safeRType(cycleTracker) + if (left.isSubtypeOf(NOTHING_WITH_NO_META)) { + NOTHING_WITH_NO_META + } else if (left.isSubtypeOf(DATE_WITH_NO_META)) { + DATE_TIME_WITH_NO_META + } else if (left.isSubtypeOf(UNCONSTRAINED_STRING_WITH_NO_META)) { + keepTypeAliasIfPossible(left.RType, right.RType, [l,r| + if (l instanceof RStringType && r instanceof RStringType) { + val s1 = l as RStringType + val s2 = r as RStringType + val newInterval = s1.interval.add(s2.interval) + new RStringType(newInterval, Optional.empty()) + } else { + NOTHING + } + ]).withNoMeta + } else if (left.isSubtypeOf(UNCONSTRAINED_NUMBER_WITH_NO_META)) { + keepTypeAliasIfPossible(left.RType, right.RType, [l,r| + if (l instanceof RNumberType && r instanceof RNumberType) { + val n1 = l as RNumberType + val n2 = r as RNumberType + val newFractionalDigits = OptionalUtil.zipWith(n1.fractionalDigits, n2.fractionalDigits, [a,b|Math.max(a,b)]) + val newInterval = n1.interval.add(n2.interval) + new RNumberType(Optional.empty(), newFractionalDigits, newInterval, Optional.empty()) + } else { + NOTHING + } + ]).withNoMeta + } else { + NOTHING_WITH_NO_META } - expr.operator.resultType(leftType.RType, rightType.RType).withEmptyMeta - } - - override protected caseAbsentOperation(RosettaAbsentExpression expr, Map cycleTracker) { - BOOLEAN.withEmptyMeta - } - - override protected caseAddOperation(ArithmeticOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) } - override protected caseAndOperation(LogicalOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseAndOperation(LogicalOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseAsKeyOperation(AsKeyOperation expr, Map cycleTracker) { + override protected caseAsKeyOperation(AsKeyOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseBooleanLiteral(RosettaBooleanLiteral expr, Map cycleTracker) { - BOOLEAN.withEmptyMeta + override protected caseBooleanLiteral(RosettaBooleanLiteral expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseChoiceOperation(ChoiceOperation expr, Map cycleTracker) { - BOOLEAN.withEmptyMeta + override protected caseChoiceOperation(ChoiceOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseConditionalExpression(RosettaConditionalExpression expr, Map cycleTracker) { + override protected caseConditionalExpression(RosettaConditionalExpression expr, Map cycleTracker) { val ifT = expr.ifthen.safeRType(cycleTracker) - if (ifT.RType instanceof RErrorType) { - return NOTHING.withEmptyMeta - } val elseT = expr.elsethen.safeRType(cycleTracker) - if (elseT.RType instanceof RErrorType) { - return NOTHING.withEmptyMeta - } val joined = joinMetaAnnotatedTypes(ifT, elseT) - if (joined == ANY) { - new RErrorType('''Types `«ifT»` and `«elseT»` do not have a common supertype.''').withEmptyMeta + if (ANY_WITH_NO_META.isSubtypeOf(joined)) { + NOTHING_WITH_NO_META } else { joined } } - override protected caseContainsOperation(RosettaContainsExpression expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseContainsOperation(RosettaContainsExpression expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseDefaultOperation(DefaultOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseDefaultOperation(DefaultOperation expr, Map cycleTracker) { + val left = expr.left.safeRType(cycleTracker) + val right = expr.right.safeRType(cycleTracker) + val result = left.joinMetaAnnotatedTypes(right) + if (ANY_WITH_NO_META.isSubtypeOf(result)) { + NOTHING_WITH_NO_META + } else { + result + } } - override protected caseCountOperation(RosettaCountOperation expr, Map cycleTracker) { - constrainedInt(Optional.empty(), Optional.of(BigInteger.ZERO), Optional.empty()).withEmptyMeta + override protected caseCountOperation(RosettaCountOperation expr, Map cycleTracker) { + constrainedInt(Optional.empty(), Optional.of(BigInteger.ZERO), Optional.empty()).withNoMeta } - override protected caseDisjointOperation(RosettaDisjointExpression expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseDisjointOperation(RosettaDisjointExpression expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseDistinctOperation(DistinctOperation expr, Map cycleTracker) { + override protected caseDistinctOperation(DistinctOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseDivideOperation(ArithmeticOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseDivideOperation(ArithmeticOperation expr, Map cycleTracker) { + UNCONSTRAINED_NUMBER_WITH_NO_META } - override protected caseEqualsOperation(EqualityOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseEqualsOperation(EqualityOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseExistsOperation(RosettaExistsExpression expr, Map cycleTracker) { - BOOLEAN.withEmptyMeta + override protected caseExistsOperation(RosettaExistsExpression expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseFeatureCall(RosettaFeatureCall expr, Map cycleTracker) { + override protected caseFeatureCall(RosettaFeatureCall expr, Map cycleTracker) { val feature = expr.feature if (!extensions.isResolved(feature)) { - return NOTHING.withEmptyMeta + return NOTHING_WITH_NO_META } if (feature instanceof RosettaEnumValue) { expr.receiver.safeRType(cycleTracker) @@ -378,138 +395,168 @@ class RosettaTypeProvider extends RosettaExpressionSwitch cycleTracker) { + override protected caseDeepFeatureCall(RosettaDeepFeatureCall expr, Map cycleTracker) { val feature = expr.feature if (!extensions.isResolved(feature)) { - return NOTHING.withEmptyMeta + return NOTHING_WITH_NO_META } (feature as RosettaFeature).safeRType(expr, cycleTracker) } - override protected caseFilterOperation(FilterOperation expr, Map cycleTracker) { + override protected caseFilterOperation(FilterOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseFirstOperation(FirstOperation expr, Map cycleTracker) { + override protected caseFirstOperation(FirstOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseFlattenOperation(FlattenOperation expr, Map cycleTracker) { + override protected caseFlattenOperation(FlattenOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseGreaterThanOperation(ComparisonOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseGreaterThanOperation(ComparisonOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseGreaterThanOrEqualOperation(ComparisonOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseGreaterThanOrEqualOperation(ComparisonOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseImplicitVariable(RosettaImplicitVariable expr, Map cycleTracker) { + override protected caseImplicitVariable(RosettaImplicitVariable expr, Map cycleTracker) { safeTypeOfImplicitVariable(expr, cycleTracker) } - override protected caseIntLiteral(RosettaIntLiteral expr, Map cycleTracker) { - constrainedInt(if (expr.value.signum >= 0) expr.value.toString.length else expr.value.toString.length - 1, expr.value, expr.value).withEmptyMeta + override protected caseIntLiteral(RosettaIntLiteral expr, Map cycleTracker) { + constrainedInt(if (expr.value.signum >= 0) expr.value.toString.length else expr.value.toString.length - 1, expr.value, expr.value).withNoMeta } - override protected caseJoinOperation(JoinOperation expr, Map cycleTracker) { - UNCONSTRAINED_STRING.withEmptyMeta + override protected caseJoinOperation(JoinOperation expr, Map cycleTracker) { + UNCONSTRAINED_STRING_WITH_NO_META } - override protected caseLastOperation(LastOperation expr, Map cycleTracker) { + override protected caseLastOperation(LastOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseLessThanOperation(ComparisonOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseLessThanOperation(ComparisonOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseLessThanOrEqualOperation(ComparisonOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseLessThanOrEqualOperation(ComparisonOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseListLiteral(ListLiteral expr, Map cycleTracker) { + override protected caseListLiteral(ListLiteral expr, Map cycleTracker) { val types = expr.elements.map[RMetaAnnotatedType].filter[it !== null] val joined = types.joinMetaAnnotatedTypes - val unique = newLinkedHashSet(types) - val StringConcatenationClient failedList = '''«FOR t: unique.take(unique.size-1) SEPARATOR ", "»`«t»`«ENDFOR» and `«unique.last»`''' - if (joined == ANY) { - new RErrorType('''Types «failedList» do not have a common supertype.''').withEmptyMeta + if (ANY_WITH_NO_META.isSubtypeOf(joined)) { + NOTHING_WITH_NO_META } else { joined } } - override protected caseMapOperation(MapOperation expr, Map cycleTracker) { - expr.function?.body?.safeRType(cycleTracker) + override protected caseMapOperation(MapOperation expr, Map cycleTracker) { + expr.function?.body?.safeRType(cycleTracker) ?: NOTHING_WITH_NO_META } - override protected caseMaxOperation(MaxOperation expr, Map cycleTracker) { + override protected caseMaxOperation(MaxOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseMinOperation(MinOperation expr, Map cycleTracker) { + override protected caseMinOperation(MinOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseMultiplyOperation(ArithmeticOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseMultiplyOperation(ArithmeticOperation expr, Map cycleTracker) { + val left = expr.left.safeRType(cycleTracker) + val right = expr.right.safeRType(cycleTracker) + keepTypeAliasIfPossible(left.RType, right.RType, [l,r| + if (l instanceof RNumberType && r instanceof RNumberType) { + val n1 = l as RNumberType + val n2 = r as RNumberType + val newFractionalDigits = OptionalUtil.zipWith(n1.fractionalDigits, n2.fractionalDigits, [a,b|a+b]) + val newInterval = n1.interval.multiply(n2.interval) + new RNumberType(Optional.empty(), newFractionalDigits, newInterval, Optional.empty()) + } else { + NOTHING + } + ]).withNoMeta } - override protected caseNotEqualsOperation(EqualityOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseNotEqualsOperation(EqualityOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseNumberLiteral(RosettaNumberLiteral expr, Map cycleTracker) { + override protected caseNumberLiteral(RosettaNumberLiteral expr, Map cycleTracker) { if (expr.value === null) { // In case of a parse error - return NOTHING.withEmptyMeta + return NOTHING_WITH_NO_META } - constrainedNumber(expr.value.toPlainString.replaceAll("\\.|\\-", "").length, Math.max(0, expr.value.scale), expr.value, expr.value).withEmptyMeta + constrainedNumber(expr.value.toPlainString.replaceAll("\\.|\\-", "").length, Math.max(0, expr.value.scale), expr.value, expr.value).withNoMeta } - override protected caseOneOfOperation(OneOfOperation expr, Map cycleTracker) { - BOOLEAN.withEmptyMeta + override protected caseOneOfOperation(OneOfOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseOnlyElementOperation(RosettaOnlyElement expr, Map cycleTracker) { + override protected caseOnlyElementOperation(RosettaOnlyElement expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseOnlyExists(RosettaOnlyExistsExpression expr, Map cycleTracker) { - BOOLEAN.withEmptyMeta + override protected caseOnlyExists(RosettaOnlyExistsExpression expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseOrOperation(LogicalOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseOrOperation(LogicalOperation expr, Map cycleTracker) { + BOOLEAN_WITH_NO_META } - override protected caseReduceOperation(ReduceOperation expr, Map cycleTracker) { - expr.function?.body?.safeRType(cycleTracker) + override protected caseReduceOperation(ReduceOperation expr, Map cycleTracker) { + expr.function?.body?.safeRType(cycleTracker) ?: NOTHING_WITH_NO_META } - override protected caseReverseOperation(ReverseOperation expr, Map cycleTracker) { + override protected caseReverseOperation(ReverseOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseSortOperation(SortOperation expr, Map cycleTracker) { + override protected caseSortOperation(SortOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseStringLiteral(RosettaStringLiteral expr, Map cycleTracker) { - constrainedString(expr.value.length, expr.value.length).withEmptyMeta - } - - override protected caseSubtractOperation(ArithmeticOperation expr, Map cycleTracker) { - caseBinaryOperation(expr, cycleTracker) + override protected caseStringLiteral(RosettaStringLiteral expr, Map cycleTracker) { + constrainedString(expr.value.length, expr.value.length).withNoMeta + } + + override protected caseSubtractOperation(ArithmeticOperation expr, Map cycleTracker) { + val left = expr.left.safeRType(cycleTracker) + val right = expr.right.safeRType(cycleTracker) + if (left.isSubtypeOf(NOTHING_WITH_NO_META)) { + NOTHING_WITH_NO_META + } else if (left.isSubtypeOf(DATE_WITH_NO_META)) { + UNCONSTRAINED_INT_WITH_NO_META + } else if (left.isSubtypeOf(UNCONSTRAINED_NUMBER_WITH_NO_META)) { + keepTypeAliasIfPossible(left.RType, right.RType, [l,r| + if (l instanceof RNumberType && r instanceof RNumberType) { + val n1 = l as RNumberType + val n2 = r as RNumberType + val newFractionalDigits = OptionalUtil.zipWith(n1.fractionalDigits, n2.fractionalDigits, [a,b|Math.max(a,b)]) + val newInterval = n1.interval.subtract(n2.interval) + new RNumberType(Optional.empty(), newFractionalDigits, newInterval, Optional.empty()) + } else { + NOTHING + } + ]).withNoMeta + } else { + NOTHING_WITH_NO_META + } } - override protected caseSumOperation(SumOperation expr, Map cycleTracker) { + override protected caseSumOperation(SumOperation expr, Map cycleTracker) { expr.argument.safeRType(cycleTracker) } - override protected caseSymbolReference(RosettaSymbolReference expr, Map cycleTracker) { + override protected caseSymbolReference(RosettaSymbolReference expr, Map cycleTracker) { if (expr.symbol instanceof RosettaExternalFunction) { val fun = expr.symbol as RosettaExternalFunction val returnType = fun.safeRType(expr, cycleTracker) @@ -526,49 +573,49 @@ class RosettaTypeProvider extends RosettaExpressionSwitch cycleTracker) { - expr.function?.body?.safeRType(cycleTracker) + override protected caseThenOperation(ThenOperation expr, Map cycleTracker) { + expr.function?.body?.safeRType(cycleTracker) ?: NOTHING_WITH_NO_META } - override protected caseToEnumOperation(ToEnumOperation expr, Map cycleTracker) { - expr.enumeration.buildREnumType.withEmptyMeta + override protected caseToEnumOperation(ToEnumOperation expr, Map cycleTracker) { + expr.enumeration.buildREnumType.withNoMeta } - override protected caseToIntOperation(ToIntOperation expr, Map cycleTracker) { - UNCONSTRAINED_INT.withEmptyMeta + override protected caseToIntOperation(ToIntOperation expr, Map cycleTracker) { + UNCONSTRAINED_INT_WITH_NO_META } - override protected caseToNumberOperation(ToNumberOperation expr, Map cycleTracker) { - UNCONSTRAINED_NUMBER.withEmptyMeta + override protected caseToNumberOperation(ToNumberOperation expr, Map cycleTracker) { + UNCONSTRAINED_NUMBER_WITH_NO_META } - override protected caseToStringOperation(ToStringOperation expr, Map cycleTracker) { - UNCONSTRAINED_STRING.withEmptyMeta + override protected caseToStringOperation(ToStringOperation expr, Map cycleTracker) { + UNCONSTRAINED_STRING_WITH_NO_META } - override protected caseToTimeOperation(ToTimeOperation expr, Map cycleTracker) { - TIME.withEmptyMeta + override protected caseToTimeOperation(ToTimeOperation expr, Map cycleTracker) { + TIME_WITH_NO_META } - override protected caseConstructorExpression(RosettaConstructorExpression expr, Map cycleTracker) { - expr.typeCall.typeCallToRType.withEmptyMeta + override protected caseConstructorExpression(RosettaConstructorExpression expr, Map cycleTracker) { + expr.typeCall.typeCallToRType.withNoMeta } - override protected caseToDateOperation(ToDateOperation expr, Map cycleTracker) { - DATE.withEmptyMeta + override protected caseToDateOperation(ToDateOperation expr, Map cycleTracker) { + DATE_WITH_NO_META } - override protected caseToDateTimeOperation(ToDateTimeOperation expr, Map cycleTracker) { - DATE_TIME.withEmptyMeta + override protected caseToDateTimeOperation(ToDateTimeOperation expr, Map cycleTracker) { + DATE_TIME_WITH_NO_META } - override protected caseToZonedDateTimeOperation(ToZonedDateTimeOperation expr, Map cycleTracker) { - ZONED_DATE_TIME.withEmptyMeta + override protected caseToZonedDateTimeOperation(ToZonedDateTimeOperation expr, Map cycleTracker) { + ZONED_DATE_TIME_WITH_NO_META } - override protected caseSwitchOperation(SwitchOperation expr, Map context) { + override protected caseSwitchOperation(SwitchOperation expr, Map cycleTracker) { expr.cases - .map[it.expression.RMetaAnnotatedType] + .map[it.expression.safeRType(cycleTracker)] .joinMetaAnnotatedTypes } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/SubtypeRelation.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/SubtypeRelation.java index 1889b2def..581b84f5a 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/SubtypeRelation.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/SubtypeRelation.java @@ -105,9 +105,9 @@ public RMetaAnnotatedType join(RMetaAnnotatedType t1, RMetaAnnotatedType t2) { RType t1RType = t1.getRType(); RType t2RType = t2.getRType(); if (t1RType.equals(t2RType)) { - return new RMetaAnnotatedType(t1RType, intersectMeta(t1, t2)); + return RMetaAnnotatedType.withMeta(t1RType, intersectMeta(t1, t2)); } - return new RMetaAnnotatedType(join(t1RType, t2RType), List.of()); + return RMetaAnnotatedType.withNoMeta(join(t1RType, t2RType)); } public RType join(RType t1, RType t2) { diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeFactory.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeFactory.java index 462595e9c..a0f8df801 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeFactory.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeFactory.java @@ -25,8 +25,6 @@ import javax.inject.Inject; import com.regnosys.rosetta.interpreter.RosettaValue; -import com.regnosys.rosetta.rosetta.RosettaCardinality; -import com.regnosys.rosetta.rosetta.RosettaFactory; import com.regnosys.rosetta.types.builtin.RBuiltinTypeService; import com.regnosys.rosetta.types.builtin.RNumberType; import com.regnosys.rosetta.types.builtin.RStringType; @@ -36,48 +34,19 @@ public class TypeFactory { private final RBuiltinTypeService builtinTypes; - public final RosettaCardinality single; - public final RosettaCardinality empty; - - public final RListType singleBoolean; - public final RListType singleDate; - public final RListType singleTime; - public final RListType singlePattern; - public final RListType singleUnconstrainedInt; - public final RListType singleUnconstrainedNumber; - public final RListType singleUnconstrainedString; - public final RListType singleDateTime; - public final RListType singleZonedDateTime; - public final RListType emptyNothing; - @Inject public TypeFactory(RBuiltinTypeService builtinTypes) { this.builtinTypes = builtinTypes; - - this.single = createConstraint(1, 1); - - this.empty = createConstraint(0, 0); - - this.singleBoolean = createListType(builtinTypes.BOOLEAN, single); - this.singleDate = createListType(builtinTypes.DATE, single); - this.singleTime = createListType(builtinTypes.TIME, single); - this.singlePattern = createListType(builtinTypes.PATTERN, single); - this.singleUnconstrainedInt = createListType(builtinTypes.UNCONSTRAINED_INT, single); - this.singleUnconstrainedNumber = createListType(builtinTypes.UNCONSTRAINED_NUMBER, single); - this.singleUnconstrainedString = createListType(builtinTypes.UNCONSTRAINED_STRING, single); - this.singleDateTime = createListType(builtinTypes.DATE_TIME, single); - this.singleZonedDateTime = createListType(builtinTypes.ZONED_DATE_TIME, single); - this.emptyNothing = createListType(builtinTypes.NOTHING, empty); } - public RListType singleInt(Optional digits, Optional min, Optional max) { - return createListType(constrainedInt(digits, min, max), single); + public RMetaAnnotatedType intWithNoMeta(Optional digits, Optional min, Optional max) { + return RMetaAnnotatedType.withNoMeta(constrainedInt(digits, min, max)); } - public RListType singleInt(int digits, BigInteger min, BigInteger max) { - return createListType(constrainedInt(digits, min, max), single); + public RMetaAnnotatedType intWithNoMeta(int digits, BigInteger min, BigInteger max) { + return RMetaAnnotatedType.withNoMeta(constrainedInt(digits, min, max)); } - public RListType singleInt(int digits, String min, String max) { - return createListType(constrainedInt(digits, min, max), single); + public RMetaAnnotatedType intWithNoMeta(int digits, String min, String max) { + return RMetaAnnotatedType.withNoMeta(constrainedInt(digits, min, max)); } public RAliasType constrainedInt(Optional digits, Optional min, Optional max) { RNumberType refersTo = constrainedNumber(digits, Optional.of(0), min.map(BigDecimal::new), max.map(BigDecimal::new), Optional.empty()); @@ -93,19 +62,19 @@ public RAliasType constrainedInt(int digits, String min, String max) { return constrainedInt(Optional.of(digits), Optional.of(new BigInteger(min)), Optional.of(new BigInteger(max))); } - public RListType singleNumber(Optional digits, Optional fractionalDigits, + public RMetaAnnotatedType numberWithNoMeta(Optional digits, Optional fractionalDigits, Optional min, Optional max, Optional scale) { - return createListType(constrainedNumber(digits, fractionalDigits, min, max, scale), single); + return RMetaAnnotatedType.withNoMeta(constrainedNumber(digits, fractionalDigits, min, max, scale)); } - public RListType singleNumber(Optional digits, Optional fractionalDigits, + public RMetaAnnotatedType numberWithNoMeta(Optional digits, Optional fractionalDigits, BigDecimalInterval interval, Optional scale) { - return createListType(constrainedNumber(digits, fractionalDigits, interval, scale), single); + return RMetaAnnotatedType.withNoMeta(constrainedNumber(digits, fractionalDigits, interval, scale)); } - public RListType singleNumber(int digits, int fractionalDigits, BigDecimal min, BigDecimal max) { - return createListType(constrainedNumber(digits, fractionalDigits, min, max), single); + public RMetaAnnotatedType numberWithNoMeta(int digits, int fractionalDigits, BigDecimal min, BigDecimal max) { + return RMetaAnnotatedType.withNoMeta(constrainedNumber(digits, fractionalDigits, min, max)); } - public RListType singleNumber(int digits, int fractionalDigits, String min, String max) { - return createListType(constrainedNumber(digits, fractionalDigits, min, max), single); + public RMetaAnnotatedType numberWithNoMeta(int digits, int fractionalDigits, String min, String max) { + return RMetaAnnotatedType.withNoMeta(constrainedNumber(digits, fractionalDigits, min, max)); } public RNumberType constrainedNumber(Optional digits, Optional fractionalDigits, Optional min, Optional max, Optional scale) { @@ -122,14 +91,14 @@ public RNumberType constrainedNumber(int digits, int fractionalDigits, String mi return constrainedNumber(Optional.of(digits), Optional.of(fractionalDigits), Optional.of(new BigDecimal(min)), Optional.of(new BigDecimal(max)), Optional.empty()); } - public RListType singleString(Optional minLength, Optional maxLength, Optional pattern) { - return createListType(constrainedString(minLength, maxLength, pattern), single); + public RMetaAnnotatedType stringWithNoMeta(Optional minLength, Optional maxLength, Optional pattern) { + return RMetaAnnotatedType.withNoMeta(constrainedString(minLength, maxLength, pattern)); } - public RListType singleString(PositiveIntegerInterval interval, Optional pattern) { - return createListType(constrainedString(interval, pattern), single); + public RMetaAnnotatedType stringWithNoMeta(PositiveIntegerInterval interval, Optional pattern) { + return RMetaAnnotatedType.withNoMeta(constrainedString(interval, pattern)); } - public RListType singleString(int minLength, int maxLength) { - return createListType(constrainedString(minLength, maxLength), single); + public RMetaAnnotatedType stringWithNoMeta(int minLength, int maxLength) { + return RMetaAnnotatedType.withNoMeta(constrainedString(minLength, maxLength)); } public RStringType constrainedString(Optional minLength, Optional maxLength, Optional pattern) { return new RStringType(minLength, maxLength, pattern); @@ -140,27 +109,4 @@ public RStringType constrainedString(PositiveIntegerInterval interval, Optional< public RStringType constrainedString(int minLength, int maxLength) { return new RStringType(Optional.of(minLength), Optional.of(maxLength), Optional.empty()); } - - public RosettaCardinality createConstraint(int inf, int sup) { - RosettaCardinality c = RosettaFactory.eINSTANCE.createRosettaCardinality(); - c.setInf(inf); - c.setSup(sup); - return c; - } - public RosettaCardinality createConstraint(int inf) { - RosettaCardinality c = RosettaFactory.eINSTANCE.createRosettaCardinality(); - c.setInf(inf); - c.setUnbounded(true); - return c; - } - - public RListType createListType(RType itemType, RosettaCardinality constraint) { - return new RListType(itemType, constraint); - } - public RListType createListType(RType itemType, int inf, int sup) { - return createListType(itemType, createConstraint(inf, sup)); - } - public RListType createListType(RType itemType, int inf) { - return createListType(itemType, createConstraint(inf)); - } } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeSystem.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeSystem.java index 2bbbd4cf3..2b66a6120 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeSystem.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeSystem.java @@ -16,13 +16,16 @@ package com.regnosys.rosetta.types; +import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; @@ -30,22 +33,36 @@ import org.apache.commons.lang3.Validate; import com.regnosys.rosetta.cache.IRequestScopedCache; +import com.regnosys.rosetta.interpreter.RosettaInterpreter; import com.regnosys.rosetta.interpreter.RosettaInterpreterContext; +import com.regnosys.rosetta.interpreter.RosettaValue; +import com.regnosys.rosetta.rosetta.RosettaBuiltinType; +import com.regnosys.rosetta.rosetta.RosettaEnumeration; import com.regnosys.rosetta.rosetta.RosettaExternalRuleSource; +import com.regnosys.rosetta.rosetta.RosettaMetaType; import com.regnosys.rosetta.rosetta.RosettaRule; +import com.regnosys.rosetta.rosetta.RosettaType; +import com.regnosys.rosetta.rosetta.RosettaTypeAlias; import com.regnosys.rosetta.rosetta.TypeCall; -import com.regnosys.rosetta.rosetta.expression.RosettaExpression; +import com.regnosys.rosetta.rosetta.TypeParameter; +import com.regnosys.rosetta.rosetta.expression.ExpressionFactory; +import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference; +import com.regnosys.rosetta.rosetta.simple.Choice; +import com.regnosys.rosetta.rosetta.simple.Data; import com.regnosys.rosetta.types.builtin.RBuiltinTypeService; -import com.regnosys.rosetta.typing.RosettaTyping; import com.regnosys.rosetta.utils.ExternalAnnotationUtil; +import com.regnosys.rosetta.utils.ModelIdProvider; +import com.regnosys.rosetta.utils.RosettaSimpleSystemSolver; +import com.regnosys.rosetta.utils.RosettaSimpleSystemSolver.Equation; +import com.rosetta.model.lib.ModelSymbolId; import org.eclipse.xtext.xbase.lib.Pair; public class TypeSystem { public static String RULE_INPUT_TYPE_CACHE_KEY = TypeSystem.class.getCanonicalName() + ".RULE_INPUT_TYPE"; - + @Inject - private RosettaTyping typing; + private RObjectFactory factory; @Inject private RBuiltinTypeService builtins; @Inject @@ -54,12 +71,12 @@ public class TypeSystem { private IRequestScopedCache cache; @Inject private SubtypeRelation subtypeRelation; - - public RListType inferType(RosettaExpression expr) { - Objects.requireNonNull(expr); - - return typing.inferType(expr).getValue(); - } + @Inject + private RosettaInterpreter interpreter; + @Inject + private RosettaSimpleSystemSolver systemSolver; + @Inject + private ModelIdProvider modelIdProvider; public RType getRulesInputType(RDataType data, Optional source) { return getRulesInputType(data, source, new HashSet<>()); @@ -108,11 +125,10 @@ public RMetaAnnotatedType joinMetaAnnotatedTypes(Iterable ty Objects.requireNonNull(types); Validate.noNullElements(types); - RMetaAnnotatedType any = new RMetaAnnotatedType(builtins.ANY, List.of()); - RMetaAnnotatedType acc = new RMetaAnnotatedType(builtins.NOTHING, List.of()); + RMetaAnnotatedType acc = builtins.NOTHING_WITH_NO_META; for (RMetaAnnotatedType t: types) { acc = subtypeRelation.join(acc, t); - if (acc.equals(any)) { + if (acc.equals(builtins.ANY_WITH_NO_META)) { return acc; } } @@ -138,12 +154,6 @@ public RType join(Iterable types) { } return acc; } - public RListType listJoin(RListType t1, RListType t2) { - Objects.requireNonNull(t1); - Objects.requireNonNull(t2); - - return Objects.requireNonNull(typing.listJoin(t1, t2)); - } public RType meet(RType t1, RType t2) { Objects.requireNonNull(t1); @@ -189,24 +199,12 @@ public boolean isSubtypeOf(RType sub, RType sup, boolean treatChoiceTypeAsData) return subtypeRelation.isSubtypeOf(sub, sup, treatChoiceTypeAsData); } - public boolean isListSubtypeOf(RListType sub, RListType sup) { - Objects.requireNonNull(sub); - Objects.requireNonNull(sup); - - return typing.listSubtypeSucceeded(sub, sup); - } - public boolean isComparable(RType t1, RType t2) { + public boolean isComparable(RMetaAnnotatedType t1, RMetaAnnotatedType t2) { Objects.requireNonNull(t1); Objects.requireNonNull(t2); - return typing.comparable(t1, t2); - } - public boolean isListComparable(RListType t1, RListType t2) { - Objects.requireNonNull(t1); - Objects.requireNonNull(t2); - - return typing.listComparable(t1, t2); + return isSubtypeOf(t1, t2) || isSubtypeOf(t2, t1); } public RType typeCallToRType(TypeCall typeCall) { @@ -217,7 +215,77 @@ public RType typeCallToRType(TypeCall typeCall, RosettaInterpreterContext contex Objects.requireNonNull(typeCall); Objects.requireNonNull(context); - return typing.typeCallToRType(typeCall, context); + RosettaType t = typeCall.getType(); + if (t instanceof Choice) { + return factory.buildRChoiceType((Choice) t); + } else if (t instanceof Data) { + return factory.buildRDataType((Data) t); + } else if (t instanceof RosettaEnumeration) { + return factory.buildREnumType((RosettaEnumeration) t); + } else if (t instanceof RosettaBuiltinType) { + Map argMap = typeCall.getArguments().stream() + .collect(Collectors.toMap(arg -> arg.getParameter().getName(), arg -> interpreter.interpret(arg.getValue(), context))); + return builtins.getType(t, argMap).orElse(builtins.NOTHING); + } else if (t instanceof RosettaMetaType) { + Map argMap = typeCall.getArguments().stream() + .collect(Collectors.toMap(arg -> arg.getParameter().getName(), arg -> interpreter.interpret(arg.getValue(), context))); + return builtins.getType(t, argMap) + .orElseGet(() -> typeCallToRType(((RosettaMetaType) t).getTypeCall(), context)); + } else if (t instanceof RosettaTypeAlias) { + RosettaTypeAlias alias = (RosettaTypeAlias) t; + LinkedHashMap args = new LinkedHashMap<>(); + Set absentParameters = new HashSet<>(((RosettaTypeAlias) t).getParameters()); + typeCall.getArguments().forEach(arg -> { + RosettaValue eval = interpreter.interpret(arg.getValue(), context); + args.put(arg.getParameter().getName(), eval); + absentParameters.remove(arg.getParameter()); + }); + absentParameters.forEach(p -> args.put(p.getName(), RosettaValue.empty())); + RType refersTo = typeCallToRType(alias.getTypeCall(), RosettaInterpreterContext.of(args)); + return new RAliasType(typeFunctionOfTypeAlias(alias), args, refersTo); + } + return builtins.NOTHING; + } + + private RTypeFunction typeFunctionOfTypeAlias(RosettaTypeAlias typeAlias) { + if (typeAlias.getName().equals(builtins.INT_NAME)) { + return builtins.INT_FUNCTION; + } + ModelSymbolId symbolId = modelIdProvider.getSymbolId(typeAlias); + List equations = + typeAlias.getTypeCall().getArguments().stream().map(arg -> { + RosettaSymbolReference ref = ExpressionFactory.eINSTANCE.createRosettaSymbolReference(); + ref.setGenerated(true); + ref.setSymbol(arg.getParameter()); + return new Equation(ref, arg.getValue()); + }).collect(Collectors.toList()); + return systemSolver.solve(equations, new HashSet<>(typeAlias.getParameters())).map(solutionSet -> + new RTypeFunction(symbolId.getNamespace(), symbolId.getName()) { + @Override + public RType evaluate(Map arguments) { + return typeCallToRType(typeAlias.getTypeCall(), RosettaInterpreterContext.of(arguments)); + } + @Override + public Optional> reverse(RType type) { + if (!(type instanceof RParametrizedType)) { + return Optional.empty(); + } + RosettaInterpreterContext context = RosettaInterpreterContext.of(((RParametrizedType)type).getArguments()); + return solutionSet.getSolution(context).map(solution -> { + LinkedHashMap newArgs = new LinkedHashMap<>(); + typeAlias.getParameters().forEach(p -> newArgs.put(p.getName(), solution.get(p))); + return newArgs; + }); + } + } + ).orElseGet(() -> + new RTypeFunction(symbolId.getNamespace(), symbolId.getName()) { + @Override + public RType evaluate(Map arguments) { + return typeCallToRType(typeAlias.getTypeCall(), RosettaInterpreterContext.of(arguments)); + } + } + ); } public RType keepTypeAliasIfPossible(RType t1, RType t2, BiFunction combineUnderlyingTypes) { @@ -225,7 +293,45 @@ public RType keepTypeAliasIfPossible(RType t1, RType t2, BiFunctionmap(args -> new RAliasType(typeFunc, args, underlier)) + .orElse(underlier); + } else { + List superAliases = new ArrayList<>(); + RAliasType curr = alias1; + superAliases.add(curr); + while (curr.getRefersTo() instanceof RAliasType) { + curr = (RAliasType) curr.getRefersTo(); + superAliases.add(curr); + } + curr = alias2; + RTypeFunction tf1 = curr.getTypeFunction(); + Optional match = superAliases.stream().filter(a -> tf1.equals(a.getTypeFunction())).findFirst(); + if (match.isPresent()) { + return keepTypeAliasIfPossible(match.get(), curr, combineUnderlyingTypes); + } + while (curr.getRefersTo() instanceof RAliasType) { + curr = (RAliasType) curr.getRefersTo(); + RTypeFunction tf2 = curr.getTypeFunction(); + match = superAliases.stream().filter(a -> tf2.equals(a.getTypeFunction())).findFirst(); + if (match.isPresent()) { + return keepTypeAliasIfPossible(match.get(), curr, combineUnderlyingTypes); + } + } + return keepTypeAliasIfPossible(alias1.getRefersTo(), alias2.getRefersTo(), combineUnderlyingTypes); + } + } else if (t1 instanceof RAliasType) { + return keepTypeAliasIfPossible(((RAliasType) t1).getRefersTo(), t2, combineUnderlyingTypes); + } else if (t2 instanceof RAliasType) { + return keepTypeAliasIfPossible(t1, ((RAliasType) t2).getRefersTo(), combineUnderlyingTypes); + } + return combineUnderlyingTypes.apply(t1, t2); } public RType stripFromTypeAliases(RType t) { diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeValidationUtil.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeValidationUtil.java deleted file mode 100644 index c0f7864eb..000000000 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/TypeValidationUtil.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2024 REGnosys - * - * 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 com.regnosys.rosetta.types; - -import com.regnosys.rosetta.rosetta.expression.ModifiableBinaryOperation; -import com.regnosys.rosetta.types.builtin.RBuiltinTypeService; - -import javax.inject.Inject; - -import com.regnosys.rosetta.rosetta.RosettaCardinality; - -public class TypeValidationUtil { - @Inject - TypeSystem typing; - @Inject - TypeFactory fac; - @Inject - RBuiltinTypeService service; - - public String notAListSubtypeMessage(RListType expected, RListType actual) { - if (!typing.isSubtypeOf(actual.getItemType(), expected.getItemType())) { - if (!actual.getConstraint().isSubconstraintOf(expected.getConstraint()) && !(expected.isPlural() && actual.isPlural())) { - return new StringBuilder() - .append("Expected ") - .append(toCompleteDescription(expected, actual.getItemType())) - .append(", but got ") - .append(toCompleteDescription(actual, expected.getItemType())) - .append(" instead.") - .toString(); - } - return notASubtypeMessage(expected.getItemType(), actual.getItemType()); - } - return notLooserConstraintMessage(expected.getConstraint(), actual); - } - public String notASubtypeMessage(RType expected, RType actual) { - return new StringBuilder() - .append("Expected type `") - .append(relevantItemTypeDescription(expected, actual)) - .append("`, but got `") - .append(relevantItemTypeDescription(actual, expected)) - .append("` instead.") - .toString(); - } - public String notListComparableMessage(RListType left, RListType right) { - if (!typing.isComparable(left.getItemType(), right.getItemType())) { - return notComparableMessage(left.getItemType(), right.getItemType()); - } - StringBuilder b = new StringBuilder() - .append("Cannot compare ") - .append(toConstraintDescription(left.getConstraint())) - .append(" to ") - .append(toConstraintDescription(right.getConstraint())) - .append(", as they cannot be of the same length."); - if (left.isSingular() || right.isSingular()) { - b.append(" Perhaps you forgot to write `all` or `any` in front of the operator?"); - } - return b.toString(); - } - public String notComparableMessage(RType left, RType right) { - return new StringBuilder() - .append("Types `") - .append(relevantItemTypeDescription(left, right)) - .append("` and `") - .append(relevantItemTypeDescription(right, left)) - .append("` are not comparable.") - .toString(); - } - public String bothAreSingularMessage(ModifiableBinaryOperation op) { - return new StringBuilder() - .append("The cardinality operator `") - .append(op.getCardMod()) - .append("` is redundant when comparing two single values.") - .toString(); - } - public String notRightIsSingularButLeftIsMessage(RListType actual) { - return new StringBuilder() - .append("Expected ") - .append(toConstraintDescription(fac.single)) - .append(", but got ") - .append(toConstraintDescription(actual.getConstraint())) - .append(" instead. Perhaps you meant to swap the left and right operands?") - .toString(); - } - public String notConstraintMessage(RosettaCardinality expected, RListType actual) { - return new StringBuilder() - .append("Expected ") - .append(toConstraintDescription(expected)) - .append(", but got ") - .append(toConstraintDescription(actual.getConstraint())) - .append(" instead.") - .toString(); - } - public String wrongConstraintMessage(RosettaCardinality wrong, RListType actual) { - return new StringBuilder() - .append("May not be ") - .append(toConstraintDescription(wrong)) - .append(".") - .toString(); - } - public String notLooserConstraintMessage(RosettaCardinality expected, RListType actual) { - return new StringBuilder() - .append("Expected ") - .append(toConstraintDescription(expected)) - .append(", but got ") - .append(toConstraintDescription(actual.getConstraint())) - .append(" instead.") - .toString(); - } - - public CharSequence relevantItemTypeDescription(RType t, RType context) { - if (t.getName().equals(context.getName())) { - return t.toString(); - } - return t.getName(); - } - public CharSequence relevantItemTypeDescription(RListType t, RType context) { - return relevantItemTypeDescription(t.getItemType(), context); - } - public CharSequence relevantItemTypeDescription(RListType t, RListType context) { - return relevantItemTypeDescription(t.getItemType(), context.getItemType()); - } - public CharSequence toShortDescription(RListType t, RType context) { - StringBuilder b = new StringBuilder(); - if (t.isEmpty()) { - if (t.getItemType().equals(service.NOTHING)) { - return "an empty value"; - } - b.append("an empty value of type"); - } else if (t.isOptional()) { - b.append("an optional"); - } else if (t.isSingular()) { - b.append("a single"); - } else { - return b.append("a list of `") - .append(t.getItemType()) - .append("`s") - .toString(); - } - return b.append(" `") - .append(relevantItemTypeDescription(t, context)) - .append("`") - .toString(); - } - public CharSequence toConstraintDescription(RosettaCardinality c) { - if (c.isEmpty()) { - return "an empty value"; - } else if (c.isOptional()) { - return "an optional value"; - } else if (c.isSingular()) { - return "a single value"; - } else { - StringBuilder b = new StringBuilder(); - if (c.isUnbounded()) { - if (c.getInf() == 0) { - b.append("an unbounded list of any length"); - } else { - b.append("an unbounded list with at least ") - .append(c.getInf()) - .append(" item") - .append(pluralS(c.getInf())); - } - } else { - b.append("a list with "); - if (c.getInf() == c.getSup()) { - b.append(c.getSup()); - } else { - b.append(c.getInf()) - .append(" to ") - .append(c.getSup()); - } - b.append(" item") - .append(pluralS(c.getSup())); - } - return b.toString(); - } - } - public CharSequence toCompleteDescription(RListType t, RType context) { - if (t.isPlural()) { - StringBuilder b = new StringBuilder(); - RosettaCardinality c = t.getConstraint(); - if (c.isUnbounded()) { - b.append("an unbounded list of `") - .append(relevantItemTypeDescription(t, context)) - .append("`s "); - if (c.getInf() == 0) { - b.append("of any length"); - } else { - b.append("with at least ") - .append(c.getInf()) - .append(" item") - .append(pluralS(c.getInf())); - } - } else { - b.append("a list of `") - .append(relevantItemTypeDescription(t, context)) - .append("`s with "); - if (c.getInf() == c.getSup()) { - b.append(c.getSup()); - } else { - b.append(c.getInf()) - .append(" to ") - .append(c.getSup()); - } - b.append(" item") - .append(pluralS(c.getSup())); - } - return b.toString(); - } - return toShortDescription(t, context); - } - private String pluralS(int count) { - return count == 1 ? "" : "s"; - } -} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/builtin/RBuiltinTypeService.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/builtin/RBuiltinTypeService.java index da03a7828..668eca3e7 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/builtin/RBuiltinTypeService.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/builtin/RBuiltinTypeService.java @@ -18,7 +18,6 @@ import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -75,25 +74,28 @@ public Optional> reverse(RType type) { }; public final RBasicType BOOLEAN = registerConstantType(new RBasicType("boolean", true)); + public final RMetaAnnotatedType BOOLEAN_WITH_NO_META = RMetaAnnotatedType.withNoMeta(BOOLEAN); public final RBasicType TIME = registerConstantType(new RBasicType("time", true)); + public final RMetaAnnotatedType TIME_WITH_NO_META = RMetaAnnotatedType.withNoMeta(TIME); public final RBasicType PATTERN = registerConstantType(new RBasicType("pattern", false)); - // TODO: remove the MISSING type - public final RBasicType MISSING = registerConstantType(new RBasicType("missing", true)); + public final RMetaAnnotatedType PATTERN_WITH_NO_META = RMetaAnnotatedType.withNoMeta(PATTERN); public final RBasicType NOTHING = registerConstantType(new RBasicType("nothing", true)); + public final RMetaAnnotatedType NOTHING_WITH_NO_META = RMetaAnnotatedType.withNoMeta(NOTHING); public final RBasicType ANY = registerConstantType(new RBasicType("any", false)); + public final RMetaAnnotatedType ANY_WITH_NO_META = RMetaAnnotatedType.withNoMeta(ANY); public final RAliasType UNCONSTRAINED_INT = new RAliasType(INT_FUNCTION, new LinkedHashMap<>(Map.of(RNumberType.DIGITS_PARAM_NAME, RosettaValue.empty(), RNumberType.MIN_PARAM_NAME, RosettaValue.empty(), RNumberType.MAX_PARAM_NAME, RosettaValue.empty())), new RNumberType(Optional.empty(), Optional.of(0), Optional.empty(), Optional.empty(), Optional.empty())); - public final RMetaAnnotatedType UNCONSTRAINED_INT_WITH_NO_META = new RMetaAnnotatedType(UNCONSTRAINED_INT, List.of()); + public final RMetaAnnotatedType UNCONSTRAINED_INT_WITH_NO_META = RMetaAnnotatedType.withNoMeta(UNCONSTRAINED_INT); public final RNumberType UNCONSTRAINED_NUMBER = new RNumberType(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); - public final RMetaAnnotatedType UNCONSTRAINED_NUMBER_WITH_NO_META = new RMetaAnnotatedType(UNCONSTRAINED_NUMBER, List.of()); + public final RMetaAnnotatedType UNCONSTRAINED_NUMBER_WITH_NO_META = RMetaAnnotatedType.withNoMeta(UNCONSTRAINED_NUMBER); public final RStringType UNCONSTRAINED_STRING = new RStringType(Optional.empty(), Optional.empty(), Optional.empty()); - public final RMetaAnnotatedType UNCONSTRAINED_STRING_WITH_NO_META = new RMetaAnnotatedType(UNCONSTRAINED_STRING, List.of()); + public final RMetaAnnotatedType UNCONSTRAINED_STRING_WITH_NO_META = RMetaAnnotatedType.withNoMeta(UNCONSTRAINED_STRING); public final RDateType DATE = registerConstantType(new RDateType()); - public final RMetaAnnotatedType DATE_WITH_NO_META = new RMetaAnnotatedType(DATE, List.of()); + public final RMetaAnnotatedType DATE_WITH_NO_META = RMetaAnnotatedType.withNoMeta(DATE); public final RDateTimeType DATE_TIME = registerConstantType(new RDateTimeType()); - public final RMetaAnnotatedType DATE_TIME_WITH_NO_META = new RMetaAnnotatedType(DATE_TIME, List.of()); + public final RMetaAnnotatedType DATE_TIME_WITH_NO_META = RMetaAnnotatedType.withNoMeta(DATE_TIME); public final RZonedDateTimeType ZONED_DATE_TIME = registerConstantType(new RZonedDateTimeType()); - public final RMetaAnnotatedType ZONED_DATE_TIME_NO_META = new RMetaAnnotatedType(ZONED_DATE_TIME, List.of()); + public final RMetaAnnotatedType ZONED_DATE_TIME_WITH_NO_META = RMetaAnnotatedType.withNoMeta(ZONED_DATE_TIME); public RBuiltinTypeService() { register("number", (m) -> RNumberType.from(m)); diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/RosettaTypeSwitch.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/RosettaTypeSwitch.java index 6a4d5c030..0d62b9755 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/RosettaTypeSwitch.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/RosettaTypeSwitch.java @@ -20,7 +20,6 @@ import com.regnosys.rosetta.types.RChoiceType; import com.regnosys.rosetta.types.RDataType; import com.regnosys.rosetta.types.REnumType; -import com.regnosys.rosetta.types.RErrorType; import com.regnosys.rosetta.types.RParametrizedType; import com.regnosys.rosetta.types.RType; import com.regnosys.rosetta.types.builtin.RBasicType; @@ -51,8 +50,6 @@ protected Return doSwitch(RType type, Context context) { return caseChoiceType((RChoiceType)type, context); } else if (type instanceof REnumType) { return caseEnumType((REnumType)type, context); - } else if (type instanceof RErrorType) { - return caseErrorType((RErrorType)type, context); } else if (type instanceof RParametrizedType) { return doSwitch((RParametrizedType)type, context); } else if (type instanceof RRecordType) { @@ -78,8 +75,6 @@ protected Return doSwitch(RBasicType type, Context context) { return caseBooleanType(type, context); } else if (type.equals(builtins.TIME)) { return caseTimeType(type, context); - } else if (type.equals(builtins.MISSING)) { - return caseMissingType(type, context); } else if (type.equals(builtins.NOTHING)) { return caseNothingType(type, context); } else if (type.equals(builtins.ANY)) { @@ -97,9 +92,7 @@ protected Return doSwitch(RRecordType type, Context context) { } throw errorMissedCase(type); } - - protected abstract Return caseErrorType(RErrorType type, Context context); - + protected abstract Return caseDataType(RDataType type, Context context); protected abstract Return caseChoiceType(RChoiceType type, Context context); protected abstract Return caseEnumType(REnumType type, Context context); @@ -110,7 +103,6 @@ protected Return doSwitch(RRecordType type, Context context) { protected abstract Return caseStringType(RStringType type, Context context); protected abstract Return caseBooleanType(RBasicType type, Context context); protected abstract Return caseTimeType(RBasicType type, Context context); - protected abstract Return caseMissingType(RBasicType type, Context context); protected abstract Return caseNothingType(RBasicType type, Context context); protected abstract Return caseAnyType(RBasicType type, Context context); diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/StandaloneRosettaTypingValidator.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/ReportValidator.java similarity index 76% rename from rosetta-lang/src/main/java/com/regnosys/rosetta/validation/StandaloneRosettaTypingValidator.java rename to rosetta-lang/src/main/java/com/regnosys/rosetta/validation/ReportValidator.java index 8246c665e..983b80c2a 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/StandaloneRosettaTypingValidator.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/ReportValidator.java @@ -16,26 +16,20 @@ package com.regnosys.rosetta.validation; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import javax.inject.Inject; -import org.eclipse.emf.ecore.EPackage; import org.eclipse.xtext.validation.Check; -import org.eclipse.xtext.validation.EValidatorRegistrar; import com.regnosys.rosetta.rosetta.ExternalValueOperator; -import com.regnosys.rosetta.rosetta.RosettaCardinality; import com.regnosys.rosetta.rosetta.RosettaExternalClass; import com.regnosys.rosetta.rosetta.RosettaExternalRegularAttribute; import com.regnosys.rosetta.rosetta.RosettaExternalRuleSource; import com.regnosys.rosetta.rosetta.RosettaReport; import com.regnosys.rosetta.rosetta.RosettaRule; -import com.regnosys.rosetta.rosetta.expression.ChoiceOperation; -import com.regnosys.rosetta.rosetta.expression.RosettaOnlyElement; import com.regnosys.rosetta.rosetta.simple.Attribute; import com.regnosys.rosetta.rosetta.simple.Data; import com.regnosys.rosetta.rosetta.simple.RosettaRuleReference; @@ -43,29 +37,18 @@ import com.regnosys.rosetta.types.RAttribute; import com.regnosys.rosetta.types.RChoiceType; import com.regnosys.rosetta.types.RDataType; -import com.regnosys.rosetta.types.RListType; import com.regnosys.rosetta.types.RObjectFactory; -import com.regnosys.rosetta.types.TypeFactory; import com.regnosys.rosetta.types.TypeSystem; -import com.regnosys.rosetta.types.TypeValidationUtil; import com.regnosys.rosetta.types.builtin.RBuiltinTypeService; -import com.regnosys.rosetta.typing.validation.RosettaTypingCheckingValidator; import com.regnosys.rosetta.utils.ExternalAnnotationUtil; -import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.*; import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.*; import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.*; -public class StandaloneRosettaTypingValidator extends RosettaTypingCheckingValidator { +public class ReportValidator extends AbstractDeclarativeRosettaValidator { @Inject private TypeSystem ts; - @Inject - private TypeFactory tf; - - @Inject - private TypeValidationUtil tu; - @Inject private RBuiltinTypeService builtins; @@ -75,50 +58,6 @@ public class StandaloneRosettaTypingValidator extends RosettaTypingCheckingValid @Inject private RObjectFactory objectFactory; - @Override - protected List getEPackages() { - List result = new ArrayList(); - result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/Rosetta")); - result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaSimple")); - result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaExpression")); - result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaTranslate")); - return result; - } - - @Override - public void register(EValidatorRegistrar registrar) { - } - - /** - * Xsemantics does not allow raising warnings. See https://github.com/eclipse/xsemantics/issues/149. - */ - @Check - public void checkOnlyElement(RosettaOnlyElement e) { - RListType t = ts.inferType(e.getArgument()); - if (t != null) { - RosettaCardinality minimalConstraint = tf.createConstraint(1, 2); - if (!minimalConstraint.isSubconstraintOf(t.getConstraint())) { - warning(tu.notLooserConstraintMessage(minimalConstraint, t), e, ROSETTA_UNARY_OPERATION__ARGUMENT); - } - } - } - - /** - * Xsemantics does not allow raising errors on a specific index of a multi-valued feature. - * See https://github.com/eclipse/xsemantics/issues/64. - */ - @Check - public void checkChoiceOperationHasNoDuplicateAttributes(ChoiceOperation e) { - for (var i = 1; i < e.getAttributes().size(); i++) { - Attribute attr = e.getAttributes().get(i); - for (var j = 0; j < i; j++) { - if (attr.equals(e.getAttributes().get(j))) { - error("Duplicate attribute.", e, CHOICE_OPERATION__ATTRIBUTES, i); - } - } - } - } - @Check public void checkReport(RosettaReport report) { RType inputType = ts.typeCallToRType(report.getInputType()); diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RetainXsemanticsIssuesOnGeneratedInputsFilter.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RetainXsemanticsIssuesOnGeneratedInputsFilter.java deleted file mode 100644 index cd23ce2c0..000000000 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RetainXsemanticsIssuesOnGeneratedInputsFilter.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2024 REGnosys - * - * 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 com.regnosys.rosetta.validation; - -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Collectors; - -import org.eclipse.emf.ecore.EObject; -import org.eclipse.xsemantics.runtime.ErrorInformation; -import org.eclipse.xsemantics.runtime.RuleFailedException; -import org.eclipse.xsemantics.runtime.validation.XsemanticsValidatorFilter; -import org.eclipse.xtext.nodemodel.util.NodeModelUtils; - -import com.google.common.collect.Lists; -import com.regnosys.rosetta.rosetta.expression.RosettaExpression; - -/** - * By default, Xsemantics removes validation issues when they point to a - * source that isn't represented by a node, e.g., when the source is generated - * by our {@code RosettaDerivedStateComputer}. This class fixes that. - */ -public class RetainXsemanticsIssuesOnGeneratedInputsFilter extends XsemanticsValidatorFilter { - @Override - public Iterable filterRuleFailedExceptions( - RuleFailedException e) { - final RuleFailedException inner = innermostRuleFailedExceptionWithNodeModelSourcesOrGenerated(e); - if (inner != null) { - return Lists.newArrayList(inner); - } - // we must return at least a failure, so we default to the passed one - return Lists.newArrayList(e); - } - - @Override - public Iterable filterErrorInformation( - RuleFailedException e) { - return filteredErrorInformation(e); - } - - /** - * Adjustment of {@code TraceUtils::innermostRuleFailedExceptionWithNodeModelSources} - */ - private RuleFailedException innermostRuleFailedExceptionWithNodeModelSourcesOrGenerated(RuleFailedException e) { - List failures = traceUtils.failureAsList(e); - for (int i=failures.size()-1; i>=0; i--) { - RuleFailedException failure = failures.get(i); - if (!filteredErrorInformation(failure).isEmpty()) { - return failure; - } - } - return null; - } - - /** - * Adjustment of {@code TraceUtils::filteredErrorInformation} - */ - private List filteredErrorInformation(RuleFailedException e) { - return removeNonNodeModelSourcesIfNotGenerated( - traceUtils.removeDuplicateErrorInformation( - traceUtils.allErrorInformation( - e))); - } - - /** - * Adjustment of {@code TraceUtils::removeNonNodeModelSources} - */ - private List removeNonNodeModelSourcesIfNotGenerated(List errorInformations) { - return errorInformations.stream() - .filter(errorInfo -> { - EObject source = errorInfo.getSource(); - if (source instanceof RosettaExpression) { - if (((RosettaExpression)source).isGenerated()) { - return true; - } - } - return NodeModelUtils.getNode(source) != null; - }) - .collect(Collectors.toCollection(LinkedList::new)); - } -} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaIssueCodes.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaIssueCodes.java index ab1f6144b..f5b603e35 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaIssueCodes.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaIssueCodes.java @@ -41,5 +41,4 @@ public interface RosettaIssueCodes { static final String MANDATORY_SQUARE_BRACKETS = PREFIX + "mandatorySquareBrackets"; static final String REDUNDANT_SQUARE_BRACKETS = PREFIX + "redundantSquareBrackets"; static final String MANDATORY_THEN = PREFIX + "mandatoryThen"; - static final String DEPRECATED_MAP = PREFIX + "deprecatedMap"; // @Compat } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend index 9c282a6e8..51a13a67e 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend @@ -7,7 +7,6 @@ import com.regnosys.rosetta.generator.util.RosettaFunctionExtensions import com.regnosys.rosetta.rosetta.ExternalAnnotationSource import com.regnosys.rosetta.rosetta.ParametrizedRosettaType import com.regnosys.rosetta.rosetta.RosettaAttributeReference -import com.regnosys.rosetta.rosetta.RosettaCallableWithArgs import com.regnosys.rosetta.rosetta.RosettaDocReference import com.regnosys.rosetta.rosetta.RosettaEnumSynonym import com.regnosys.rosetta.rosetta.RosettaEnumValueReference @@ -26,45 +25,10 @@ import com.regnosys.rosetta.rosetta.RosettaSynonymBody import com.regnosys.rosetta.rosetta.RosettaSynonymValueBase import com.regnosys.rosetta.rosetta.RosettaType import com.regnosys.rosetta.rosetta.RosettaTyped -import com.regnosys.rosetta.rosetta.expression.AsKeyOperation -import com.regnosys.rosetta.rosetta.expression.CanHandleListOfLists -import com.regnosys.rosetta.rosetta.expression.CardinalityModifier -import com.regnosys.rosetta.rosetta.expression.ClosureParameter -import com.regnosys.rosetta.rosetta.expression.ComparingFunctionalOperation -import com.regnosys.rosetta.rosetta.expression.ConstructorKeyValuePair -import com.regnosys.rosetta.rosetta.expression.DefaultOperation -import com.regnosys.rosetta.rosetta.expression.FilterOperation -import com.regnosys.rosetta.rosetta.expression.FlattenOperation -import com.regnosys.rosetta.rosetta.expression.HasGeneratedInput -import com.regnosys.rosetta.rosetta.expression.InlineFunction -import com.regnosys.rosetta.rosetta.expression.ListLiteral -import com.regnosys.rosetta.rosetta.expression.ListOperation -import com.regnosys.rosetta.rosetta.expression.MandatoryFunctionalOperation -import com.regnosys.rosetta.rosetta.expression.MapOperation -import com.regnosys.rosetta.rosetta.expression.ModifiableBinaryOperation -import com.regnosys.rosetta.rosetta.expression.ParseOperation -import com.regnosys.rosetta.rosetta.expression.ReduceOperation -import com.regnosys.rosetta.rosetta.expression.RosettaBinaryOperation -import com.regnosys.rosetta.rosetta.expression.RosettaConstructorExpression -import com.regnosys.rosetta.rosetta.expression.RosettaCountOperation -import com.regnosys.rosetta.rosetta.expression.RosettaExpression -import com.regnosys.rosetta.rosetta.expression.RosettaFeatureCall -import com.regnosys.rosetta.rosetta.expression.RosettaFunctionalOperation -import com.regnosys.rosetta.rosetta.expression.RosettaImplicitVariable -import com.regnosys.rosetta.rosetta.expression.RosettaOnlyExistsExpression -import com.regnosys.rosetta.rosetta.expression.RosettaOperation -import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference -import com.regnosys.rosetta.rosetta.expression.RosettaUnaryOperation -import com.regnosys.rosetta.rosetta.expression.SumOperation -import com.regnosys.rosetta.rosetta.expression.SwitchOperation -import com.regnosys.rosetta.rosetta.expression.ThenOperation -import com.regnosys.rosetta.rosetta.expression.ToStringOperation -import com.regnosys.rosetta.rosetta.expression.UnaryFunctionalOperation import com.regnosys.rosetta.rosetta.simple.Annotated import com.regnosys.rosetta.rosetta.simple.Annotation import com.regnosys.rosetta.rosetta.simple.AnnotationQualifier import com.regnosys.rosetta.rosetta.simple.Attribute -import com.regnosys.rosetta.rosetta.simple.ChoiceOption import com.regnosys.rosetta.rosetta.simple.Condition import com.regnosys.rosetta.rosetta.simple.Data import com.regnosys.rosetta.rosetta.simple.Function @@ -77,12 +41,10 @@ import com.regnosys.rosetta.types.CardinalityProvider import com.regnosys.rosetta.types.RAttribute import com.regnosys.rosetta.types.RDataType import com.regnosys.rosetta.types.REnumType -import com.regnosys.rosetta.types.RErrorType import com.regnosys.rosetta.types.RObjectFactory import com.regnosys.rosetta.types.RType import com.regnosys.rosetta.types.RosettaTypeProvider import com.regnosys.rosetta.types.TypeSystem -import com.regnosys.rosetta.types.TypeValidationUtil import com.regnosys.rosetta.types.builtin.RBasicType import com.regnosys.rosetta.types.builtin.RBuiltinTypeService import com.regnosys.rosetta.types.builtin.RRecordType @@ -116,17 +78,36 @@ import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.* import static com.regnosys.rosetta.validation.RosettaIssueCodes.* import static extension org.eclipse.emf.ecore.util.EcoreUtil.* -import static extension com.regnosys.rosetta.types.RMetaAnnotatedType.* import org.eclipse.emf.ecore.impl.EClassImpl -import com.regnosys.rosetta.rosetta.expression.RosettaConditionalExpression -import com.regnosys.rosetta.rosetta.RosettaExternalFunction import com.regnosys.rosetta.types.RChoiceType -import com.regnosys.rosetta.interpreter.RosettaInterpreter -import com.google.common.collect.Lists import java.util.Collection import org.eclipse.xtext.resource.IResourceDescriptions import com.regnosys.rosetta.types.RMetaAnnotatedType -import com.regnosys.rosetta.rosetta.RosettaMetaType +import com.regnosys.rosetta.rosetta.expression.RosettaFunctionalOperation +import com.regnosys.rosetta.rosetta.expression.ThenOperation +import com.regnosys.rosetta.rosetta.expression.RosettaOperation +import com.regnosys.rosetta.rosetta.expression.MandatoryFunctionalOperation +import com.regnosys.rosetta.rosetta.expression.RosettaUnaryOperation +import com.regnosys.rosetta.rosetta.expression.InlineFunction +import com.regnosys.rosetta.rosetta.expression.HasGeneratedInput +import com.regnosys.rosetta.rosetta.expression.RosettaImplicitVariable +import com.regnosys.rosetta.rosetta.expression.RosettaFeatureCall +import com.regnosys.rosetta.rosetta.expression.RosettaExpression +import com.regnosys.rosetta.rosetta.expression.ClosureParameter +import com.regnosys.rosetta.rosetta.expression.RosettaConstructorExpression +import com.regnosys.rosetta.rosetta.expression.ParseOperation +import com.regnosys.rosetta.rosetta.expression.ToStringOperation +import com.regnosys.rosetta.rosetta.expression.ListOperation +import com.regnosys.rosetta.rosetta.expression.UnaryFunctionalOperation +import com.regnosys.rosetta.rosetta.expression.FilterOperation +import com.regnosys.rosetta.rosetta.expression.MapOperation +import com.regnosys.rosetta.rosetta.expression.FlattenOperation +import com.regnosys.rosetta.rosetta.expression.ReduceOperation +import com.regnosys.rosetta.rosetta.expression.SumOperation +import com.regnosys.rosetta.rosetta.expression.ComparingFunctionalOperation +import com.regnosys.rosetta.rosetta.expression.AsKeyOperation +import com.regnosys.rosetta.rosetta.expression.ConstructorKeyValuePair +import com.regnosys.rosetta.rosetta.expression.CanHandleListOfLists // TODO: split expression validator // TODO: type check type call arguments @@ -146,29 +127,7 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { @Inject extension RBuiltinTypeService @Inject extension TypeSystem @Inject extension RosettaGrammarAccess - @Inject extension TypeValidationUtil @Inject extension RObjectFactory objectFactory - @Inject extension RosettaInterpreter - - @Check - def void checkOnlyExistsNotUsedOnMeta(RosettaOnlyExistsExpression op) { - val message = "Invalid use of `only exists` on meta feature" - - op.args - .filter(RosettaFeatureCall) - .filter[feature instanceof RosettaMetaType] - .forEach[ - error('''«message» «feature.name»''', it, ROSETTA_FEATURE_CALL__FEATURE) - ] - - op.args - .filter(RosettaSymbolReference) - .filter[it.symbol instanceof RosettaMetaType] - .forEach[ - error('''«message» «it.symbol.name»''', it, ROSETTA_SYMBOL_REFERENCE__SYMBOL) - ] - - } @Check def void deprecatedWarning(EObject object) { @@ -189,114 +148,6 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { } } } - - @Check - def void checkSwitch(SwitchOperation op) { - if (op.argument.multi) { - error("Input to switch must be single cardinality", op.argument, null) - } - val argumentType = op.argument.RMetaAnnotatedType.RType.stripFromTypeAliases - if (argumentType instanceof REnumType) { - checkEnumSwitch(argumentType, op) - } else if (argumentType instanceof RBasicType) { - checkBasicTypeSwitch(argumentType, op) - } else if (argumentType instanceof RChoiceType) { - checkChoiceSwitch(argumentType, op) - } else { - error('''Type `«argumentType»` is not a valid switch argument type. Supported argument types are basic types, enumerations, and choice types.''', op, ROSETTA_UNARY_OPERATION__ARGUMENT) - } - } - - private def void checkEnumSwitch(REnumType argumentType, SwitchOperation op) { - // When the argument is an enum: - // - all guards should be enum guards, - // - there are no duplicate cases, - // - all enum values must be covered. - val seenValues = newHashSet - for (caseStatement : op.cases) { - if (caseStatement.guard.enumGuard === null) { - error('''Case should match an enum value of «argumentType»''', caseStatement, SWITCH_CASE__GUARD) - } else { - if (!seenValues.add(caseStatement.guard.enumGuard)) { - error('''Duplicate case «caseStatement.guard.enumGuard.name»''', caseStatement, SWITCH_CASE__GUARD) - } - } - } - - if (op.^default === null) { - val missingEnumValues = argumentType.allEnumValues.filter[!seenValues.contains(it)] - if (!missingEnumValues.empty) { - error('''Missing the following cases: «missingEnumValues.map[it.name].join(", ")». Either provide all or add a default.''', op, ROSETTA_OPERATION__OPERATOR) - } - } - } - private def void checkBasicTypeSwitch(RBasicType argumentType, SwitchOperation op) { - // When the argument is a basic type: - // - all guards should be literal guards, - // - there are no duplicate cases, - // - all guards should be comparable to the input. - val seenValues = newHashSet - for (caseStatement : op.cases) { - if (caseStatement.guard.literalGuard === null) { - error('''Case should match a literal of type «argumentType»''', caseStatement, SWITCH_CASE__GUARD) - } else { - if (!seenValues.add(caseStatement.guard.literalGuard.interpret)) { - error('''Duplicate case''', caseStatement, SWITCH_CASE__GUARD) - } - val conditionType = caseStatement.guard.literalGuard.RMetaAnnotatedType.RType - if (!conditionType.isComparable(argumentType)) { - error('''Invalid case: «argumentType.notComparableMessage(conditionType)»''', caseStatement, SWITCH_CASE__GUARD) - } - } - } - } - private def void checkChoiceSwitch(RChoiceType argumentType, SwitchOperation op) { - // When the argument is a choice type: - // - all guards should be choice option guards, - // - all cases should be reachable, - // - all choice options should be covered. - val Map includedOptions = newHashMap - for (caseStatement : op.cases) { - if (caseStatement.guard.choiceOptionGuard === null) { - error('''Case should match a choice option of type «argumentType»''', caseStatement, SWITCH_CASE__GUARD) - } else { - val guard = caseStatement.guard.choiceOptionGuard - val alreadyCovered = includedOptions.get(guard) - if (alreadyCovered !== null) { - error('''Case already covered by «alreadyCovered»''', caseStatement, SWITCH_CASE__GUARD) - } else { - val guardType = guard.RTypeOfSymbol - includedOptions.put(guard, guardType) - val valueType = guardType.RType - if (valueType instanceof RChoiceType) { - valueType.allOptions.forEach[includedOptions.put(it.EObject, guardType)] - } - } - } - } - if (op.^default === null) { - val missingOptions = Lists.newArrayList(argumentType.ownOptions.map[type]) - for (guard : includedOptions.values.toSet) { - for (var i=0; i «callable.name»` or to the output variable.''', - element, - ROSETTA_SYMBOL_REFERENCE__SYMBOL - ) - } - } - } - if (element.explicitArguments) { - error( - '''A variable may not be called.''', - element, - ROSETTA_SYMBOL_REFERENCE__EXPLICIT_ARGUMENTS - ) - } - } - } - } - @Check def void checkMergeSynonymAttributeCardinality(Attribute attribute) { for (syn : attribute.synonyms) { @@ -979,7 +738,7 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { // check type val ruleType = rule.expression.RMetaAnnotatedType.RType - if (ruleType !== null && ruleType != MISSING && attrType !== null && attrType != MISSING && !ruleType.isSubtypeOf(attrType)) { + if (!ruleType.isSubtypeOf(attrType)) { val typeError = '''Type mismatch - report field «attr.name» has type «attrType.name» ''' + '''whereas the reporting rule «rule.name» has type «ruleType».''' error(typeError, ruleRef, ROSETTA_RULE_REFERENCE__REPORTING_RULE) @@ -987,40 +746,6 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { } } - @Check - def checkExpressionCardinality(ModifiableBinaryOperation binOp) { - val leftCard = cardinality.isMulti(binOp.left) - val rightCard = cardinality.isMulti(binOp.right) - if (leftCard != rightCard) { - if (binOp.cardMod === CardinalityModifier.NONE) { - warning('''Comparison operator «binOp.operator» should specify 'all' or 'any' when comparing a list to a single value''', - binOp, ROSETTA_OPERATION__OPERATOR) - } - } else if (binOp.cardMod !== CardinalityModifier.NONE) { - warning('''«binOp.cardMod» is only applicable when the sides have differing cardinality''', binOp, - ROSETTA_OPERATION__OPERATOR) - } - } - - @Check - def checkBinaryParamsRightTypes(RosettaBinaryOperation binOp) { - val resultMetaType = binOp.RMetaAnnotatedType - val resultType = resultMetaType.RType - if (resultType instanceof RErrorType) { - error(resultType.message, binOp, ROSETTA_OPERATION__OPERATOR) - } - } - - @Check - def checkDefaultOperationMatchingCardinality(DefaultOperation defOp) { - val leftCard = cardinality.isMulti(defOp.left) - val rightCard = cardinality.isMulti(defOp.right) - if (leftCard != rightCard) { - val typeError = "Cardinality mismatch - default operator requires both sides to have matching cardinality" - error(typeError, defOp, ROSETTA_OPERATION__OPERATOR) - } - } - @Check def checkFuncDispatchAttr(FunctionDispatch ele) { if (ele.attribute.isResolved && ele.attribute.typeCall.isResolved) { @@ -1143,16 +868,7 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { } @Check - def checkListLiteral(ListLiteral ele) { - val metaType = ele.RMetaAnnotatedType - val type = metaType.RType - if (type instanceof RErrorType) { - error('''All collection elements must have the same super type but types were «type.message»''', ele, null) - } - } - - @Check - def checkSynonyMapPath(RosettaMapPathValue ele) { + def checkSynonymMapPath(RosettaMapPathValue ele) { if (!ele.path.nullOrEmpty) { val invalidChar = checkPathChars(ele.path) if (invalidChar !== null) @@ -1170,14 +886,6 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { ele, ROSETTA_SYNONYM_VALUE_BASE__PATH) } } - - @Check - def checkCountOpArgument(RosettaCountOperation ele) { - if (ele.argument.isResolved) { - if (!cardinality.isMulti(ele.argument)) - error('''Count operation multiple cardinality argument.''', ele, ROSETTA_UNARY_OPERATION__ARGUMENT) - } - } @Check def checkParseOpArgument(ParseOperation ele) { @@ -1390,24 +1098,6 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { } } - @Check - def checkOnlyExistsPathsHaveCommonParent(RosettaOnlyExistsExpression e) { - val first = e.args.head - val parent = exprHelper.getParentExpression(first) - for (var i = 1; i < e.args.size; i++) { - val other = e.args.get(i) - val otherParent = exprHelper.getParentExpression(other) - if ((parent === null) !== (otherParent === null) || - parent !== null && otherParent !== null && !EcoreUtil2.equals(parent, otherParent)) { - if (otherParent !== null) { - error('''Only exists paths must have a common parent.''', otherParent, null) - } else { - error('''Only exists paths must have a common parent.''', other, null) - } - } - } - } - @Check def checkUnaryOperation(RosettaUnaryOperation e) { val receiver = e.argument @@ -1577,7 +1267,7 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { private def void checkBodyType(InlineFunction ref, RType type) { val bodyType = ref?.body?.getRMetaAnnotatedType?.RType - if (ref !== null && bodyType !== null && bodyType != MISSING && bodyType != type) { + if (ref !== null && bodyType !== null && bodyType != type) { error('''Expression must evaluate to a «type.name».''', ref, null) } } @@ -1592,30 +1282,6 @@ class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { } } - @Check - def checkOutputOperation(Operation o) { - val expr = o?.expression - if (expr !== null && expr.isOutputListOfLists) { - error('''Assign expression contains a list of lists, use flatten to create a list.''', o, - OPERATION__EXPRESSION) - } - val attr = o.path !== null - ? o.pathAsSegmentList.last.attribute - : o.assignRoot - checkType(attr.RTypeOfSymbol, expr, o, OPERATION__EXPRESSION, INSIGNIFICANT_INDEX) - val isList = cardinality.isSymbolMulti(attr) - if (o.add && !isList) { - error('''Add must be used with a list.''', o, OPERATION__ASSIGN_ROOT) - } - if (!o.add && isList) { - info('''Set used with a list. Any existing list items will be overwritten. Use Add to append items to existing list.''', - o, OPERATION__ASSIGN_ROOT) - } - if (!isList && cardinality.isMulti(o.expression)) { - error('''Cardinality mismatch - cannot assign list to a single value.''', o, OPERATION__ASSIGN_ROOT) - } - } - @Check def checkAlias(ShortcutDeclaration o) { val expr = o?.expression diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaValidator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaValidator.xtend index 25f6946ed..0eb45ff9e 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaValidator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaValidator.xtend @@ -4,6 +4,7 @@ package com.regnosys.rosetta.validation import org.eclipse.xtext.validation.ComposedChecks +import com.regnosys.rosetta.validation.expression.ExpressionValidator /** * This class contains custom validation rules. @@ -12,11 +13,12 @@ import org.eclipse.xtext.validation.ComposedChecks */ @ComposedChecks(validators = #[ RosettaSimpleValidator, - StandaloneRosettaTypingValidator, + ReportValidator, TypeValidator, AttributeValidator, EnumValidator, - ChoiceValidator + ChoiceValidator, + ExpressionValidator ]) class RosettaValidator extends AbstractRosettaValidator { diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/expression/AbstractExpressionValidator.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/expression/AbstractExpressionValidator.java new file mode 100644 index 000000000..c70bbbe97 --- /dev/null +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/expression/AbstractExpressionValidator.java @@ -0,0 +1,200 @@ +package com.regnosys.rosetta.validation.expression; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; + +import com.regnosys.rosetta.rosetta.expression.RosettaBinaryOperation; +import com.regnosys.rosetta.rosetta.expression.RosettaExpression; +import com.regnosys.rosetta.rosetta.expression.RosettaOperation; +import com.regnosys.rosetta.types.CardinalityProvider; +import com.regnosys.rosetta.types.RMetaAnnotatedType; +import com.regnosys.rosetta.types.RType; +import com.regnosys.rosetta.types.RosettaTypeProvider; +import com.regnosys.rosetta.types.TypeSystem; +import com.regnosys.rosetta.types.builtin.RBuiltinTypeService; +import com.regnosys.rosetta.validation.AbstractDeclarativeRosettaValidator; + +public class AbstractExpressionValidator extends AbstractDeclarativeRosettaValidator { + @Inject + protected RosettaTypeProvider typeProvider; + @Inject + protected TypeSystem typeSystem; + @Inject + protected RBuiltinTypeService builtins; + @Inject + protected CardinalityProvider cardinalityProvider; + + protected String relevantTypeDescription(RMetaAnnotatedType type, RMetaAnnotatedType context) { + RType valueType = type.getRType(); + RType valueContext = context.getRType(); + String prepend = valueType.getName().equals(valueContext.getName()) ? valueType.getNamespace() + "." : ""; + if (valueType.equals(valueContext)) { + // Include meta info + return prepend + type.toString(); + } + if (valueType.getName().equals(valueContext.getName())) { + // Include type parameters + return prepend + valueType.toString(); + } + return prepend + valueType.getName(); + } + + protected String notASubtypeMessage(RMetaAnnotatedType expected, RMetaAnnotatedType actual, Function suggestion) { + String actualDescr = relevantTypeDescription(actual, expected); + StringBuilder msg = new StringBuilder() + .append("Expected type `") + .append(relevantTypeDescription(expected, actual)) + .append("`, but got `") + .append(actualDescr) + .append("` instead"); + if (suggestion != null) { + msg.append(". "); + msg.append(suggestion.apply(actualDescr)); + } + return msg.toString(); + } + private Function defaultSubtypeSuggestion(RosettaOperation op) { + return actual -> "Cannot use `" + actual + "` with operator `" + op.getOperator() + "`"; + } + protected boolean subtypeCheck(RMetaAnnotatedType expected, RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, RosettaOperation op) { + return subtypeCheck(expected, typeProvider.getRMetaAnnotatedType(expr), sourceObject, feature, INSIGNIFICANT_INDEX, defaultSubtypeSuggestion(op)); + } + protected boolean subtypeCheck(RMetaAnnotatedType expected, RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, Function suggestion) { + return subtypeCheck(expected, typeProvider.getRMetaAnnotatedType(expr), sourceObject, feature, INSIGNIFICANT_INDEX, suggestion); + } + protected boolean subtypeCheck(RMetaAnnotatedType expected, RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, int featureIndex, Function suggestion) { + return subtypeCheck(expected, typeProvider.getRMetaAnnotatedType(expr), sourceObject, feature, featureIndex, suggestion); + } + protected boolean subtypeCheck(RMetaAnnotatedType expected, RMetaAnnotatedType actual, EObject sourceObject, EStructuralFeature feature, RosettaOperation op) { + return subtypeCheck(expected, actual, sourceObject, feature, INSIGNIFICANT_INDEX, defaultSubtypeSuggestion(op)); + } + protected boolean subtypeCheck(RMetaAnnotatedType expected, RMetaAnnotatedType actual, EObject sourceObject, EStructuralFeature feature, Function suggestion) { + return subtypeCheck(expected, actual, sourceObject, feature, INSIGNIFICANT_INDEX, suggestion); + } + protected boolean subtypeCheck(RMetaAnnotatedType expected, RMetaAnnotatedType actual, EObject sourceObject, EStructuralFeature feature, int featureIndex, Function suggestion) { + if (!typeSystem.isSubtypeOf(actual, expected)) { + error(notASubtypeMessage(expected, actual, suggestion), sourceObject, feature, featureIndex); + return false; + } + return true; + } + + protected String notComparableMessage(RMetaAnnotatedType left, RMetaAnnotatedType right) { + return new StringBuilder() + .append("Types `") + .append(relevantTypeDescription(left, right)) + .append("` and `") + .append(relevantTypeDescription(right, left)) + .append("` are not comparable") + .toString(); + } + protected boolean comparableTypeCheck(RosettaBinaryOperation sourceObject) { + RMetaAnnotatedType tl = typeProvider.getRMetaAnnotatedType(sourceObject.getLeft()); + RMetaAnnotatedType tr = typeProvider.getRMetaAnnotatedType(sourceObject.getRight()); + if (!typeSystem.isComparable(tl, tr)) { + error(notComparableMessage(tl, tr), sourceObject, null); + return false; + } + return true; + } + + protected boolean isMultiCheck(RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, RosettaOperation op) { + String suggestion = "The `" + op.getOperator() + "` operator requires a multi cardinality input"; + return isMultiCheck(expr, sourceObject, feature, INSIGNIFICANT_INDEX, suggestion); + } + protected boolean isMultiCheck(RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, String suggestion) { + return isMultiCheck(expr, sourceObject, feature, INSIGNIFICANT_INDEX, suggestion); + } + protected boolean isMultiCheck(RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, int featureIndex, String suggestion) { + if (!cardinalityProvider.isMulti(expr)) { + String msg = "Expecting multi cardinality"; + if (suggestion != null) { + msg += ". " + suggestion; + } + warning(msg, sourceObject, feature, featureIndex); + return false; + } + return true; + } + protected boolean isSingleCheck(RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, RosettaOperation op) { + String suggestion = "The `" + op.getOperator() + "` operator requires a single cardinality input"; + return isSingleCheck(expr, sourceObject, feature, INSIGNIFICANT_INDEX, suggestion); + } + protected boolean isSingleCheck(RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, String suggestion) { + return isSingleCheck(expr, sourceObject, feature, INSIGNIFICANT_INDEX, suggestion); + } + protected boolean isSingleCheck(RosettaExpression expr, EObject sourceObject, EStructuralFeature feature, int featureIndex, String suggestion) { + if (cardinalityProvider.isMulti(expr)) { + String msg = "Expecting single cardinality"; + if (suggestion != null) { + msg += ". " + suggestion; + } + warning(msg, sourceObject, feature, featureIndex); + return false; + } + return true; + } + + protected boolean commonTypeCheck(RosettaExpression expr1, RosettaExpression expr2, EObject sourceObject, EStructuralFeature feature) { + if (expr1 == null || expr2 == null) { + return true; + } + return commonTypeCheck(List.of(expr1, expr2), sourceObject, feature); + } + protected boolean commonTypeCheck(List expressions, EObject sourceObject, EStructuralFeature feature) { + boolean haveCommonType = true; + if (!expressions.isEmpty()) { + Set types = new LinkedHashSet<>(); + RMetaAnnotatedType firstElemType = typeProvider.getRMetaAnnotatedType(expressions.get(0)); + types.add(firstElemType); + RMetaAnnotatedType commonType = firstElemType; + for (int i=1; i "`" + relevantTypeDescription(t, elemType) + "`").collect(Collectors.joining(", ")) + " and `" + relevantTypeDescription(elemType, newCommonType) + "` do not have a common supertype", + sourceObject, + feature, + feature == null || !feature.isMany() ? INSIGNIFICANT_INDEX : i); + haveCommonType = false; + } else { + types.add(elemType); + commonType = newCommonType; + } + } + } + return haveCommonType; + } + + protected void unsupportedTypeError(RMetaAnnotatedType type, RosettaOperation op, EStructuralFeature feature, RType supportedType1, RType supportedType2, RType... moreSupportedTypes) { + StringBuilder supportedTypesMsg = new StringBuilder(); + supportedTypesMsg.append("Supported types are "); + supportedTypesMsg.append(supportedType1); + if (moreSupportedTypes.length > 0) { + supportedTypesMsg.append(", "); + supportedTypesMsg.append(supportedType2); + for (int i=0; i "A condition must be a boolean"); + } + + @Check + public void checkFunctionOperation(Operation op) { + RosettaExpression expr = op.getExpression(); + if (expr != null && cardinalityProvider.isOutputListOfLists(expr)) { + error("Assign expression contains a list of lists, use flatten to create a list", op, OPERATION__EXPRESSION); + } + List segments = op.pathAsSegmentList(); + AssignPathRoot attr = op.getPath() != null + ? segments.get(segments.size() - 1).getAttribute() + : op.getAssignRoot(); + subtypeCheck(typeProvider.getRTypeOfSymbol(attr), expr, op, OPERATION__EXPRESSION, actual -> "Cannot assign `" + actual + "` to output `" + attr.getName() + "`"); + boolean isList = cardinalityProvider.isSymbolMulti(attr); + if (op.isAdd() && !isList) { + error("`add` must be used with a list", op, OPERATION__ASSIGN_ROOT); + } + if (!op.isAdd() && isList) { + info("`set` used with a list. Any existing list items will be overwritten. Use `add` to append items to existing list", + op, OPERATION__ASSIGN_ROOT); + } + if (!isList) { + isSingleCheck(expr, op, OPERATION__EXPRESSION, "Cannot assign a list to a single value"); + } + } + + @Check + public void checkArithmeticOperation(ArithmeticOperation op) { + RosettaExpression left = op.getLeft(); + RosettaExpression right = op.getRight(); + String operator = op.getOperator(); + RMetaAnnotatedType leftType = typeProvider.getRMetaAnnotatedType(left); + RMetaAnnotatedType rightType = typeProvider.getRMetaAnnotatedType(right); + isSingleCheck(left, op, ROSETTA_BINARY_OPERATION__LEFT, op); + isSingleCheck(right, op, ROSETTA_BINARY_OPERATION__RIGHT, op); + if (operator.equals("+")) { + if (typeSystem.isSubtypeOf(leftType, builtins.NOTHING_WITH_NO_META)) { + // Do not check right type + } else if (typeSystem.isSubtypeOf(leftType, builtins.DATE_WITH_NO_META)) { + subtypeCheck(builtins.TIME_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, actual -> "Cannot add `" + actual + "` to a `date`"); + } else if (typeSystem.isSubtypeOf(leftType, builtins.UNCONSTRAINED_STRING_WITH_NO_META)) { + subtypeCheck(builtins.UNCONSTRAINED_STRING_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, actual -> "Cannot add `" + actual + "` to a `string`"); + } else if (typeSystem.isSubtypeOf(leftType, builtins.UNCONSTRAINED_NUMBER_WITH_NO_META)) { + subtypeCheck(builtins.UNCONSTRAINED_NUMBER_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, actual -> "Cannot add `" + actual + "` to a `number`"); + } else { + unsupportedTypeError(leftType, op, ROSETTA_BINARY_OPERATION__LEFT, builtins.UNCONSTRAINED_NUMBER, builtins.UNCONSTRAINED_STRING, builtins.DATE); + if (!typeSystem.isSubtypeOf(rightType, builtins.TIME_WITH_NO_META) + && !typeSystem.isSubtypeOf(rightType, builtins.UNCONSTRAINED_STRING_WITH_NO_META) + && !typeSystem.isSubtypeOf(rightType, builtins.UNCONSTRAINED_NUMBER_WITH_NO_META)) { + unsupportedTypeError(rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, builtins.UNCONSTRAINED_NUMBER, builtins.UNCONSTRAINED_STRING, builtins.TIME); + } + } + } else if (operator.equals("-")) { + if (typeSystem.isSubtypeOf(leftType, builtins.NOTHING_WITH_NO_META)) { + // Do not check right type + } else if (typeSystem.isSubtypeOf(leftType, builtins.DATE_WITH_NO_META)) { + subtypeCheck(builtins.DATE_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, actual -> "Cannot subtract `" + actual + "` from a `date`"); + } else if (typeSystem.isSubtypeOf(leftType, builtins.UNCONSTRAINED_NUMBER_WITH_NO_META)) { + subtypeCheck(builtins.UNCONSTRAINED_NUMBER_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, actual -> "Cannot subtract `" + actual + "` from a `number`"); + } else { + unsupportedTypeError(leftType, op, ROSETTA_BINARY_OPERATION__LEFT, builtins.UNCONSTRAINED_NUMBER, builtins.DATE); + if (!typeSystem.isSubtypeOf(rightType, builtins.DATE_WITH_NO_META) + && !typeSystem.isSubtypeOf(rightType, builtins.UNCONSTRAINED_NUMBER_WITH_NO_META)) { + unsupportedTypeError(rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, builtins.UNCONSTRAINED_NUMBER, builtins.DATE); + } + } + } else { + subtypeCheck(builtins.UNCONSTRAINED_NUMBER_WITH_NO_META, leftType, op, ROSETTA_BINARY_OPERATION__LEFT, op); + subtypeCheck(builtins.UNCONSTRAINED_NUMBER_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, op); + } + } + + private void checkModifiedBinaryOperation(ModifiableBinaryOperation op) { + RosettaExpression left = op.getLeft(); + RosettaExpression right = op.getRight(); + String removeModifierSuggestion = "Did you mean to remove the `" + op.getCardMod().getLiteral() + "` modifier on the `" + op.getOperator() + "` operator?"; + String flipOperandsSuggestion = "Did you mean to flip around the operands of the `" + op.getOperator() + "` operator?"; + String leftSuggestion; + if (cardinalityProvider.isMulti(right)) { + // Multi and single are flipped + leftSuggestion = flipOperandsSuggestion; + } else { + // Both are single + leftSuggestion = removeModifierSuggestion; + } + String rightSuggestion; + if (cardinalityProvider.isMulti(left)) { + // Both are multi + rightSuggestion = null; + } else { + // Multi and single are flipped + rightSuggestion = flipOperandsSuggestion; + } + isMultiCheck(left, op, ROSETTA_BINARY_OPERATION__LEFT, leftSuggestion); + isSingleCheck(right, op, ROSETTA_BINARY_OPERATION__RIGHT, rightSuggestion); + } + + @Check + public void checkEqualityOperation(EqualityOperation op) { + comparableTypeCheck(op); + if (op.getCardMod() != CardinalityModifier.NONE) { + checkModifiedBinaryOperation(op); + } else { + boolean leftIsMulti = cardinalityProvider.isMulti(op.getLeft()); + boolean rightIsMulti = cardinalityProvider.isMulti(op.getRight()); + if (leftIsMulti != rightIsMulti) { + error("Operator `" + op.getOperator() + "` should specify `all` or `any` when comparing a list to a single value", op, null); + } + } + } + + @Check + public void checkLogicalOperation(LogicalOperation op) { + RosettaExpression left = op.getLeft(); + RosettaExpression right = op.getRight(); + RMetaAnnotatedType leftType = typeProvider.getRMetaAnnotatedType(left); + RMetaAnnotatedType rightType = typeProvider.getRMetaAnnotatedType(right); + isSingleCheck(left, op, ROSETTA_BINARY_OPERATION__LEFT, op); + isSingleCheck(right, op, ROSETTA_BINARY_OPERATION__RIGHT, op); + subtypeCheck(builtins.BOOLEAN_WITH_NO_META, leftType, op, ROSETTA_BINARY_OPERATION__LEFT, op); + subtypeCheck(builtins.BOOLEAN_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, op); + } + + @Check + public void checkComparisonOperation(ComparisonOperation op) { + RosettaExpression left = op.getLeft(); + RosettaExpression right = op.getRight(); + RMetaAnnotatedType leftType = typeProvider.getRMetaAnnotatedType(left); + RMetaAnnotatedType rightType = typeProvider.getRMetaAnnotatedType(right); + if (op.getCardMod() != CardinalityModifier.NONE) { + checkModifiedBinaryOperation(op); + } else { + isSingleCheck(left, op, ROSETTA_BINARY_OPERATION__LEFT, "Did you mean to use `all` or `any` in front of the `" + op.getOperator() + "` operator?"); + isSingleCheck(right, op, ROSETTA_BINARY_OPERATION__RIGHT, "Did you mean to use `all` or `any` in front of the `" + op.getOperator() + "` operator?"); + } + if (typeSystem.isSubtypeOf(leftType, builtins.NOTHING_WITH_NO_META)) { + // Do not check right type + } else if (typeSystem.isSubtypeOf(leftType, builtins.ZONED_DATE_TIME_WITH_NO_META)) { + subtypeCheck(builtins.ZONED_DATE_TIME_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, actual -> "Cannot compare a `" + actual + "` to a `zonedDateTime`"); + } else if (typeSystem.isSubtypeOf(leftType, builtins.DATE_WITH_NO_META)) { + subtypeCheck(builtins.DATE_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, actual -> "Cannot compare a `" + actual + "` to a `date`"); + } else if (typeSystem.isSubtypeOf(leftType, builtins.UNCONSTRAINED_NUMBER_WITH_NO_META)) { + subtypeCheck(builtins.UNCONSTRAINED_NUMBER_WITH_NO_META, rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, actual -> "Cannot compare a `" + actual + "` to a `number`"); + } else { + unsupportedTypeError(leftType, op, ROSETTA_BINARY_OPERATION__LEFT, builtins.UNCONSTRAINED_NUMBER, builtins.DATE, builtins.ZONED_DATE_TIME); + if (!typeSystem.isSubtypeOf(rightType, builtins.ZONED_DATE_TIME_WITH_NO_META) + && !typeSystem.isSubtypeOf(rightType, builtins.DATE_WITH_NO_META) + && !typeSystem.isSubtypeOf(rightType, builtins.UNCONSTRAINED_NUMBER_WITH_NO_META)) { + unsupportedTypeError(rightType, op, ROSETTA_BINARY_OPERATION__RIGHT, builtins.UNCONSTRAINED_NUMBER, builtins.DATE, builtins.ZONED_DATE_TIME); + } + } + } + + @Check + public void checkContainsExpression(RosettaContainsExpression expr) { + isMultiCheck(expr.getLeft(), expr, ROSETTA_BINARY_OPERATION__LEFT, "Did you mean to use the `=` operator instead?"); + comparableTypeCheck(expr); + } + + @Check + public void checkDisjointExpression(RosettaDisjointExpression expr) { + isMultiCheck(expr.getLeft(), expr, ROSETTA_BINARY_OPERATION__LEFT, expr); + isMultiCheck(expr.getRight(), expr, ROSETTA_BINARY_OPERATION__LEFT, expr); + comparableTypeCheck(expr); + } + + @Check + public void checkConditionalExpression(RosettaConditionalExpression expr) { + isSingleCheck(expr.getIf(), expr, ROSETTA_CONDITIONAL_EXPRESSION__IF, "The condition of an if-then-else expression should be single cardinality"); + subtypeCheck(builtins.BOOLEAN_WITH_NO_META, expr.getIf(), expr, ROSETTA_CONDITIONAL_EXPRESSION__IF, actual -> "The condition of an if-then-else expression must be a boolean"); + commonTypeCheck(expr.getIfthen(), expr.getElsethen(), expr, ROSETTA_CONDITIONAL_EXPRESSION__ELSETHEN); + } + + @Check + public void checkListLiteral(ListLiteral expr) { + commonTypeCheck(expr.getElements(), expr, LIST_LITERAL__ELEMENTS); + } + + @Check + public void checkDefaultOperation(DefaultOperation op) { + commonTypeCheck(op.getLeft(), op.getRight(), op, ROSETTA_BINARY_OPERATION__RIGHT); + } + + @Check + public void checkSymbolReference(RosettaSymbolReference expr) { + RosettaSymbol s = expr.getSymbol(); + if (ecoreUtil.isResolved(s)) { + if (s instanceof RosettaCallableWithArgs) { + RosettaCallableWithArgs callable = (RosettaCallableWithArgs) s; + int paramCount = callable.numberOfParameters(); + int argCount = expr.getArgs().size(); + if (paramCount != argCount) { + error("Expected " + paramCount + " argument" + (paramCount == 1 ? "" : "s") + ", but got " + argCount + " instead", expr, ROSETTA_SYMBOL_REFERENCE__SYMBOL); + } + int minCount = Math.min(paramCount, argCount); + + if (callable instanceof RosettaExternalFunction) { + RosettaExternalFunction f = (RosettaExternalFunction) callable; + for (int i=0; i "Cannot assign `" + actual + "` to parameter `" + param.getName() + "`"); + } + } else if (callable instanceof Function) { + Function f = (Function) callable; + for (int i=0; i "Cannot assign `" + actual + "` to input `" + param.getName() + "`"); + } + } else if (callable instanceof RosettaRule) { + RosettaRule f = (RosettaRule) callable; + if (minCount >= 1) { + RMetaAnnotatedType paramType = RMetaAnnotatedType.withNoMeta(typeSystem.typeCallToRType(f.getInput())); + RosettaExpression arg = expr.getArgs().get(0); + isSingleCheck(arg, expr, ROSETTA_SYMBOL_REFERENCE__RAW_ARGS, 0, null); + subtypeCheck(paramType, arg, expr, ROSETTA_SYMBOL_REFERENCE__RAW_ARGS, 0, actual -> "Rule `" + f.getName() + "` cannot be called with type `" + actual + "`"); + } + } + } else { + if (s instanceof Attribute) { + if (functionExtensions.isOutput((Attribute) s)) { + RMetaAnnotatedType implicitType = typeProvider.typeOfImplicitVariable(expr); + Iterable implicitFeatures = ecoreUtil.allFeatures(implicitType, expr); + if (Iterables.any(implicitFeatures, f -> f.getName().equals(s.getName()))) { + error( + "Ambiguous reference. `" + s.getName() + "` may either refer to `item -> " + s.getName() + "` or to the output variable.", + expr, + ROSETTA_SYMBOL_REFERENCE__SYMBOL + ); + } + } + } + if (expr.isExplicitArguments()) { + error( + "A variable may not be called", + expr, + ROSETTA_SYMBOL_REFERENCE__EXPLICIT_ARGUMENTS + ); + } + } + } + } + + @Check + public void checkExistsExpression(RosettaExistsExpression expr) { + if (expr.getModifier() == ExistsModifier.MULTIPLE || expr.getModifier() == ExistsModifier.SINGLE) { + isMultiCheck(expr.getArgument(), expr, ROSETTA_UNARY_OPERATION__ARGUMENT, expr); + } + } + + private boolean mayBeEmpty(RType t) { + return t instanceof RDataType && ((RDataType) t).getAllAttributes().stream().allMatch(a -> a.getCardinality().isOptional()) || t instanceof RChoiceType; + } + @Check + public void checkOnlyExistsExpression(RosettaOnlyExistsExpression expr) { + if (expr.getArgs().size() > 0) { + for (RosettaExpression input : expr.getArgs()) { + RosettaNamed invalidMetaFeature = null; + EStructuralFeature structFeature = null; + if (input instanceof RosettaFeatureCall && ((RosettaFeatureCall)input).getFeature() instanceof RosettaMetaType) { + invalidMetaFeature = ((RosettaFeatureCall)input).getFeature(); + structFeature = ROSETTA_FEATURE_CALL__FEATURE; + } else if (input instanceof RosettaSymbolReference && ((RosettaSymbolReference)input).getSymbol() instanceof RosettaMetaType) { + invalidMetaFeature = ((RosettaSymbolReference)input).getSymbol(); + structFeature = ROSETTA_SYMBOL_REFERENCE__SYMBOL; + } + if (invalidMetaFeature != null) { + error("Invalid use of `only exists` on meta feature " + invalidMetaFeature.getName(), input, structFeature); + } + } + + RosettaExpression first = expr.getArgs().get(0); + RosettaExpression parent = exprHelper.getParentExpression(first); + for (int i=1; i seen = new HashSet<>(); + for (var i = 0; i < op.getAttributes().size(); i++) { + Attribute attr = op.getAttributes().get(i); + if (!seen.add(attr)) { + error("Duplicate attribute.", op, CHOICE_OPERATION__ATTRIBUTES, i); + } + } + } + + @Check + public void checkJoinOperation(JoinOperation op) { + isMultiCheck(op.getLeft(), op, ROSETTA_BINARY_OPERATION__LEFT, op); + isSingleCheck(op.getRight(), op, ROSETTA_BINARY_OPERATION__RIGHT, op); + subtypeCheck(builtins.UNCONSTRAINED_STRING_WITH_NO_META, op.getLeft(), op, ROSETTA_BINARY_OPERATION__LEFT, op); + subtypeCheck(builtins.UNCONSTRAINED_STRING_WITH_NO_META, op.getRight(), op, ROSETTA_BINARY_OPERATION__RIGHT, op); + } + + @Check + public void checkOnlyElement(RosettaOnlyElement e) { + // TODO: restore +// RListType t = ts.inferType(e.getArgument()); +// if (t != null) { +// RosettaCardinality minimalConstraint = tf.createConstraint(1, 2); +// if (!minimalConstraint.isSubconstraintOf(t.getConstraint())) { +// warning(tu.notLooserConstraintMessage(minimalConstraint, t), e, ROSETTA_UNARY_OPERATION__ARGUMENT); +// } +// } + } +} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/expression/SwitchValidator.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/expression/SwitchValidator.java new file mode 100644 index 000000000..f31e6c731 --- /dev/null +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/expression/SwitchValidator.java @@ -0,0 +1,150 @@ +package com.regnosys.rosetta.validation.expression; + +import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.eclipse.xtext.validation.Check; + +import com.regnosys.rosetta.interpreter.RosettaInterpreter; +import com.regnosys.rosetta.interpreter.RosettaValue; +import com.regnosys.rosetta.rosetta.RosettaEnumValue; +import com.regnosys.rosetta.rosetta.expression.RosettaLiteral; +import com.regnosys.rosetta.rosetta.expression.SwitchCase; +import com.regnosys.rosetta.rosetta.expression.SwitchOperation; +import com.regnosys.rosetta.rosetta.simple.ChoiceOption; +import com.regnosys.rosetta.types.RChoiceType; +import com.regnosys.rosetta.types.REnumType; +import com.regnosys.rosetta.types.RMetaAnnotatedType; +import com.regnosys.rosetta.types.RType; +import com.regnosys.rosetta.types.builtin.RBasicType; + +public class SwitchValidator extends ExpressionValidator { + @Inject + private RosettaInterpreter interpreter; + + @Check + public void checkSwitch(SwitchOperation op) { + isSingleCheck(op.getArgument(), op, ROSETTA_UNARY_OPERATION__ARGUMENT, op); + RMetaAnnotatedType argumentType = typeProvider.getRMetaAnnotatedType(op.getArgument()); + RType rType = typeSystem.stripFromTypeAliases(argumentType.getRType()); + if (rType instanceof REnumType) { + checkEnumSwitch((REnumType) rType, op); + } else if (rType instanceof RBasicType) { + checkBasicTypeSwitch((RBasicType) rType, op); + } else if (rType instanceof RChoiceType) { + checkChoiceSwitch((RChoiceType) rType, op); + } else { + unsupportedTypeError(argumentType, op.getOperator(), op, ROSETTA_UNARY_OPERATION__ARGUMENT, "Supported argument types are basic types, enumerations, and choice types"); + } + } + private void checkEnumSwitch(REnumType argumentType, SwitchOperation op) { + // When the argument is an enum: + // - all guards should be enum guards, + // - there are no duplicate cases, + // - all enum values must be covered. + Set seenValues = new HashSet<>(); + for (SwitchCase caseStatement : op.getCases()) { + RosettaEnumValue guard = caseStatement.getGuard().getEnumGuard(); + if (guard == null) { + error("Case should match an enum value of " + argumentType, caseStatement, SWITCH_CASE__GUARD); + } else { + if (!seenValues.add(guard)) { + error("Duplicate case " + guard.getName(), caseStatement, SWITCH_CASE__GUARD); + } + } + } + + if (op.getDefault() == null) { + List missingEnumValues = new ArrayList<>(argumentType.getAllEnumValues()); + missingEnumValues.removeAll(seenValues); + if (!missingEnumValues.isEmpty()) { + String missingValuesMsg = missingEnumValues.stream().map(v -> v.getName()).collect(Collectors.joining(", ")); + error("Missing the following cases: " + missingValuesMsg + ". Either provide all or add a default.", op, ROSETTA_OPERATION__OPERATOR); + } + } + } + private void checkBasicTypeSwitch(RBasicType argumentType, SwitchOperation op) { + // When the argument is a basic type: + // - all guards should be literal guards, + // - there are no duplicate cases, + // - all guards should be comparable to the input. + Set seenValues = new HashSet<>(); + RMetaAnnotatedType argumentTypeWithoutMeta = RMetaAnnotatedType.withNoMeta(argumentType); + for (SwitchCase caseStatement : op.getCases()) { + RosettaLiteral guard = caseStatement.getGuard().getLiteralGuard(); + if (guard == null) { + error("Case should match a literal of type " + argumentType, caseStatement, SWITCH_CASE__GUARD); + } else { + if (!seenValues.add(interpreter.interpret(guard))) { + error("Duplicate case", caseStatement, SWITCH_CASE__GUARD); + } + RMetaAnnotatedType conditionType = typeProvider.getRMetaAnnotatedType(guard); + if (!typeSystem.isComparable(conditionType, argumentTypeWithoutMeta)) { + error("Invalid case: " + notComparableMessage(conditionType, argumentTypeWithoutMeta), caseStatement, SWITCH_CASE__GUARD); + } + } + } + } + private void checkChoiceSwitch(RChoiceType argumentType, SwitchOperation op) { + // When the argument is a choice type: + // - all guards should be choice option guards, + // - all cases should be reachable, + // - all choice options should be covered. + Map includedOptions = new HashMap<>(); + for (SwitchCase caseStatement : op.getCases()) { + ChoiceOption guard = caseStatement.getGuard().getChoiceOptionGuard(); + if (guard == null) { + error("Case should match a choice option of type " + argumentType, caseStatement, SWITCH_CASE__GUARD); + } else { + RMetaAnnotatedType alreadyCovered = includedOptions.get(guard); + if (alreadyCovered != null) { + error("Case already covered by " + alreadyCovered, caseStatement, SWITCH_CASE__GUARD); + } else { + RMetaAnnotatedType guardType = typeProvider.getRTypeOfSymbol(guard); + includedOptions.put(guard, guardType); + RType valueType = guardType.getRType(); + if (valueType instanceof RChoiceType) { + ((RChoiceType)valueType).getAllOptions().forEach(it -> includedOptions.put(it.getEObject(), guardType)); + } + } + } + } + if (op.getDefault() == null) { + List missingOptions = new ArrayList<>(); + argumentType.getOwnOptions().forEach(opt -> missingOptions.add(opt.getType())); + for (RMetaAnnotatedType guard : new LinkedHashSet<>(includedOptions.values())) { + for (var i=0; i missingOptions.add(o.getType())); + } + } + } + } + if (!missingOptions.isEmpty()) { + String missingOptsMsg = missingOptions.stream() + .map(opt -> opt.toString()) + .collect(Collectors.joining(", ")); + error("Missing the following cases: " + missingOptsMsg + ". Either provide all or add a default.", op, ROSETTA_OPERATION__OPERATOR); + } + } + } +} diff --git a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/RosettaTestInjectorProvider.java b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/RosettaTestInjectorProvider.java new file mode 100644 index 000000000..0103156f8 --- /dev/null +++ b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/RosettaTestInjectorProvider.java @@ -0,0 +1,26 @@ +package com.regnosys.rosetta.tests; + +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.eclipse.xtext.testing.validation.ValidationTestHelper.Mode; + +import com.google.inject.Binder; +import com.regnosys.rosetta.RosettaRuntimeModule; + +public class RosettaTestInjectorProvider extends RosettaInjectorProvider { + protected RosettaRuntimeModule createRuntimeModule() { + // make it work also with Maven/Tycho and OSGI + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=493672 + return new RosettaRuntimeModule() { + @Override + public ClassLoader bindClassLoaderToInstance() { + return RosettaTestInjectorProvider.class + .getClassLoader(); + } + + // Make sure validation tests properly check the message + public void configureValidationTestHelper(Binder binder) { + binder.bind(ValidationTestHelper.class).toInstance(new ValidationTestHelper(Mode.EXACT)); + } + }; + } +} diff --git a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/CustomConfigTestHelper.xtend b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/CustomConfigTestHelper.xtend index 562c07f54..6d036b6c9 100644 --- a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/CustomConfigTestHelper.xtend +++ b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/CustomConfigTestHelper.xtend @@ -5,7 +5,7 @@ import java.util.List import java.util.HashMap import com.google.inject.Injector import com.regnosys.rosetta.RosettaRuntimeModule -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider class CustomConfigTestHelper { static def compileToClassesForModel(List> code, @@ -48,7 +48,7 @@ class CustomConfigTestHelper { new RosettaRuntimeModule() { override ClassLoader bindClassLoaderToInstance() { - RosettaInjectorProvider.getClassLoader() + RosettaTestInjectorProvider.getClassLoader() } def Class bindRosettaConfigurationFileProvider() { diff --git a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/ExpressionValidationHelper.xtend b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/ExpressionValidationHelper.xtend index 06eceee69..6fbfd274b 100644 --- a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/ExpressionValidationHelper.xtend +++ b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/ExpressionValidationHelper.xtend @@ -2,7 +2,6 @@ package com.regnosys.rosetta.tests.util import com.regnosys.rosetta.rosetta.expression.RosettaExpression import com.regnosys.rosetta.validation.AbstractRosettaValidator -import com.regnosys.rosetta.validation.StandaloneRosettaTypingValidator import java.util.List import org.eclipse.emf.common.util.BasicDiagnostic import org.eclipse.emf.common.util.Diagnostic @@ -16,10 +15,11 @@ import static com.google.common.collect.Iterables.isEmpty import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject import javax.inject.Named +import com.regnosys.rosetta.validation.ReportValidator class ExpressionValidationHelper { @Inject - extension StandaloneRosettaTypingValidator // TODO: replace this with RosettaValidator once old type system has been removed + extension ReportValidator // TODO: replace this with RosettaValidator once old type system has been removed @Inject@Named(Constants.LANGUAGE_NAME) String languageName; diff --git a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/RosettaCustomConfigInjectorProvider.java b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/RosettaCustomConfigInjectorProvider.java index ce00fc4af..a09de71e6 100644 --- a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/RosettaCustomConfigInjectorProvider.java +++ b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/RosettaCustomConfigInjectorProvider.java @@ -12,7 +12,7 @@ import com.regnosys.rosetta.RosettaRuntimeModule; import com.regnosys.rosetta.RosettaStandaloneSetup; import com.regnosys.rosetta.config.file.RosettaConfigurationFileProvider; -import com.regnosys.rosetta.tests.RosettaInjectorProvider; +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider; public class RosettaCustomConfigInjectorProvider implements IInjectorProvider, IRegistryConfigurator { protected GlobalStateMemento stateBeforeInjectorCreation; @@ -47,7 +47,7 @@ protected RosettaRuntimeModule createRuntimeModule() { return new RosettaRuntimeModule() { @Override public ClassLoader bindClassLoaderToInstance() { - return RosettaInjectorProvider.class + return RosettaTestInjectorProvider.class .getClassLoader(); } diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/docrefs/DocReferenceTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/docrefs/DocReferenceTest.xtend index 41e2350c6..521300466 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/docrefs/DocReferenceTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/docrefs/DocReferenceTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.docrefs -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.^extension.ExtendWith import javax.inject.Inject -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) @ExtendWith(InjectionExtension) class DocReferenceTest { diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/docs/DocumentationSamples.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/docs/DocumentationSamples.xtend index 5e3f37bed..613927bc4 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/docs/DocumentationSamples.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/docs/DocumentationSamples.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.docs import org.eclipse.xtext.testing.InjectWith -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.junit.jupiter.api.^extension.ExtendWith import org.eclipse.xtext.testing.extensions.InjectionExtension import javax.inject.Inject @@ -22,7 +22,7 @@ import com.rosetta.util.types.generated.GeneratedJavaClass * If one of these tests fail, then the documentation should be reviewed as well, * as this probably means a code sample is out-dated. */ -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) @ExtendWith(InjectionExtension) class DocumentationSamples { diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java index 0a5076583..9d970a1a0 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/ResourceFormatterServiceTest.java @@ -22,10 +22,10 @@ import java.util.List; import com.google.common.io.Resources; -import com.regnosys.rosetta.tests.RosettaInjectorProvider; +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider; @ExtendWith(InjectionExtension.class) -@InjectWith(RosettaInjectorProvider.class) +@InjectWith(RosettaTestInjectorProvider.class) public class ResourceFormatterServiceTest { @Inject ResourceFormatterService formatterService; diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormattingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormattingTest.xtend index 8761c7826..0f1e17055 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormattingTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaExpressionFormattingTest.xtend @@ -6,10 +6,10 @@ import javax.inject.Inject import org.junit.jupiter.api.^extension.ExtendWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.eclipse.xtext.testing.InjectWith -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaExpressionFormattingTest { @Inject extension ExpressionFormatterTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaFormattingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaFormattingTest.xtend index 6f30341f4..500983d3f 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaFormattingTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/formatting2/RosettaFormattingTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.formatting2 -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.junit.jupiter.api.Test @@ -12,7 +12,7 @@ import org.eclipse.xtext.testing.formatter.FormatterTestRequest import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaFormattingTest { @Inject extension FormatterTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/ChoiceRuleGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/ChoiceRuleGeneratorTest.xtend index 67e5b35da..07330a389 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/ChoiceRuleGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/ChoiceRuleGeneratorTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.condition -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import java.util.List import java.util.Map @@ -15,7 +15,7 @@ import javax.inject.Inject import com.regnosys.rosetta.RosettaEcoreUtil @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ChoiceRuleGeneratorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/ConditionGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/ConditionGeneratorTest.xtend index 2439e46fc..44ae1e368 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/ConditionGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/ConditionGeneratorTest.xtend @@ -3,7 +3,7 @@ package com.regnosys.rosetta.generator.java.condition import org.junit.jupiter.api.^extension.ExtendWith import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.junit.jupiter.api.Test import javax.inject.Inject import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper @@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.* import com.rosetta.model.lib.records.Date @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ConditionGeneratorTest { @Inject extension CodeGeneratorTestHelper @Inject extension ConditionTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/DataRuleGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/DataRuleGeneratorTest.xtend index ee1037259..fd1b7a3a8 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/DataRuleGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/DataRuleGeneratorTest.xtend @@ -2,7 +2,7 @@ package com.regnosys.rosetta.generator.java.condition import com.google.common.collect.ImmutableList import com.regnosys.rosetta.generator.java.RosettaJavaPackages.RootPackage -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import java.io.File import java.math.BigDecimal @@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class DataRuleGeneratorTest { @Inject extension CodeGeneratorTestHelper @@ -552,7 +552,7 @@ class DataRuleGeneratorTest { val validationResult = classes.runCondition(fooInstance, 'FooListDataRule') assertTrue(validationResult.isSuccess) - assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue = 1.0")) + assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue all = 1.0")) } @Test @@ -568,7 +568,7 @@ class DataRuleGeneratorTest { val validationResult = classes.runCondition(fooInstance, 'FooListDataRule') assertFalse(validationResult.isSuccess) - assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue = 1.0")) + assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue all = 1.0")) assertThat(validationResult.failureReason.orElse(""), is("[Foo->getBar[0]->getBaz[0]->getBazValue] [2.0] does not equal [BigDecimal] [1.0]")) } @@ -589,7 +589,7 @@ class DataRuleGeneratorTest { val validationResult = classes.runCondition(fooInstance, 'FooListDataRule') assertTrue(validationResult.isSuccess) - assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue = 1.0")) + assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue all = 1.0")) } @Test @@ -606,7 +606,7 @@ class DataRuleGeneratorTest { val validationResult = classes.runCondition(fooInstance, 'FooListDataRule') assertFalse(validationResult.isSuccess) - assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue = 1.0")) + assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue all = 1.0")) assertThat(validationResult.failureReason.orElse(""), is("[Foo->getBar[0]->getBaz[0]->getBazValue, Foo->getBar[0]->getBaz[1]->getBazValue] [1.0, 2.0] does not equal [BigDecimal] [1.0]")) } @@ -625,7 +625,7 @@ class DataRuleGeneratorTest { val validationResult = classes.runCondition(fooInstance, 'FooListDataRule') assertFalse(validationResult.isSuccess) - assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue = 1.0")) + assertThat(validationResult.definition, is("if bar -> baz exists then bar -> baz -> bazValue all = 1.0")) assertThat(validationResult.failureReason.orElse(""), is("[Foo->getBar[0]->getBaz[0]->getBazValue, Foo->getBar[1]->getBaz[0]->getBazValue] [1.0, 2.0] does not equal [BigDecimal] [1.0]")) } @@ -636,7 +636,7 @@ class DataRuleGeneratorTest { condition ListDataRule: if bar -> baz exists - then bar -> baz -> bazValue = 1.0 + then bar -> baz -> bazValue all = 1.0 type Bar: baz Baz (0..*) diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/OneOfRuleGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/OneOfRuleGeneratorTest.xtend index 0da6a943d..cd14549dd 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/OneOfRuleGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/OneOfRuleGeneratorTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.condition -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.RosettaModelObject import com.rosetta.model.lib.path.RosettaPath @@ -20,7 +20,7 @@ import com.rosetta.model.lib.validation.ValidationResult import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class OneOfRuleGeneratorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/RosettaConditionTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/RosettaConditionTest.xtend index 8adc2fba4..1fc4b64cd 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/RosettaConditionTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/condition/RosettaConditionTest.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.generator.java.condition import com.google.common.collect.ImmutableList -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.RosettaModelObject import com.rosetta.model.lib.validation.ValidationResult @@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaConditionTest { @Inject extension CodeGeneratorTestHelper @@ -37,11 +37,11 @@ class RosettaConditionTest { condition FeatureCallComparisonDecreasing: if bar exists - then bar -> before > bar -> after + then bar first -> before > bar first -> after condition BarFeatureCallGreaterThanLiteralZero: if bar exists - then bar -> after > 0 + then bar first -> after > 0 condition BazFeatureCallGreaterThanLiteralFive: if baz exists @@ -71,10 +71,10 @@ class RosettaConditionTest { val fooInstance = RosettaModelObject.cast(classes.createInstanceUsingBuilder('Foo', of('baz', bazInstance), of('bar', ImmutableList.of(barInstance)))) // FeatureCallComparisonDecreasing (success) - assertCondition(fooInstance, 'FooFeatureCallComparisonDecreasing', true, "if bar exists then bar -> before > bar -> after") + assertCondition(fooInstance, 'FooFeatureCallComparisonDecreasing', true, "if bar exists then bar first -> before > bar first -> after") // BarFeatureCallGreaterThanLiteralZero (success) - assertCondition(fooInstance, 'FooBarFeatureCallGreaterThanLiteralZero', true, "if bar exists then bar -> after > 0") + assertCondition(fooInstance, 'FooBarFeatureCallGreaterThanLiteralZero', true, "if bar exists then bar first -> after > 0") // BazFeatureCallGreaterThanLiteralZero (success) assertCondition(fooInstance, 'FooBazFeatureCallGreaterThanLiteralZero', true, "if baz exists then baz -> other > 0") @@ -90,10 +90,10 @@ class RosettaConditionTest { val fooInstance = RosettaModelObject.cast(classes.createInstanceUsingBuilder('Foo', of('baz', bazInstance), of('bar', ImmutableList.of(barInstance)))) // FeatureCallComparisonDecreasing (success) - assertCondition(fooInstance, 'FooFeatureCallComparisonDecreasing', true, "if bar exists then bar -> before > bar -> after") + assertCondition(fooInstance, 'FooFeatureCallComparisonDecreasing', true, "if bar exists then bar first -> before > bar first -> after") // BarFeatureCallGreaterThanLiteralZero (fail) - assertCondition(fooInstance, 'FooBarFeatureCallGreaterThanLiteralZero', false, "if bar exists then bar -> after > 0") + assertCondition(fooInstance, 'FooBarFeatureCallGreaterThanLiteralZero', false, "if bar exists then bar first -> after > 0") // BazFeatureCallGreaterThanLiteralZero (success) assertCondition(fooInstance, 'FooBazFeatureCallGreaterThanLiteralZero', true, "if baz exists then baz -> other > 0") @@ -111,13 +111,13 @@ class RosettaConditionTest { // FeatureCallComparisonDecreasing (fail) val conditionBarDescreasing = ValidationResult.cast(classes.runCondition(fooInstance, 'FooFeatureCallComparisonDecreasing')) assertFalse(conditionBarDescreasing.success) - assertThat(conditionBarDescreasing.definition, is("if bar exists then bar -> before > bar -> after")) + assertThat(conditionBarDescreasing.definition, is("if bar exists then bar first -> before > bar first -> after")) assertThat(conditionBarDescreasing.failureReason.orElse(""), is("all elements of paths [Foo->getBar[0]->getBefore] values [-10] are not > than all elements of paths [Foo->getBar[0]->getAfter] values [0]")) // BarFeatureCallGreaterThanLiteralZero (fail) val conditionBarGreaterThanZero = ValidationResult.cast(classes.runCondition(fooInstance, 'FooBarFeatureCallGreaterThanLiteralZero')) assertFalse(conditionBarGreaterThanZero.success) - assertThat(conditionBarGreaterThanZero.getDefinition(), is("if bar exists then bar -> after > 0")) + assertThat(conditionBarGreaterThanZero.getDefinition(), is("if bar exists then bar first -> after > 0")) assertThat(conditionBarGreaterThanZero.failureReason.orElse(""), is("all elements of paths [Foo->getBar[0]->getAfter] values [0] are not > than all elements of paths [BigDecimal] values [0]")) // BazFeatureCallGreaterThanLiteralZero (fail) diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/ExpressionGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/ExpressionGeneratorTest.xtend index 80e696297..2ede54d3b 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/ExpressionGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/ExpressionGeneratorTest.xtend @@ -2,7 +2,7 @@ package com.regnosys.rosetta.generator.java.expression import com.regnosys.rosetta.generator.java.expression.ExpressionGenerator import com.regnosys.rosetta.rosetta.RosettaModel -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.junit.jupiter.api.Test @@ -26,7 +26,7 @@ import com.regnosys.rosetta.generator.java.JavaIdentifierRepresentationService import org.eclipse.xtend2.lib.StringConcatenationClient @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ExpressionGeneratorTest { @Inject extension ExpressionGenerator expressionGenerator @Inject extension ExpressionParser diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/ListOperationTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/ListOperationTest.xtend index 23958d63c..dd73d9b4b 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/ListOperationTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/ListOperationTest.xtend @@ -2,7 +2,7 @@ package com.regnosys.rosetta.generator.java.expression import com.google.common.collect.ImmutableList import com.regnosys.rosetta.generator.java.function.FunctionGeneratorHelper -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.RosettaModelObject import com.rosetta.model.lib.records.Date @@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ListOperationTest { @Inject extension FunctionGeneratorHelper @@ -961,7 +961,7 @@ class ListOperationTest { set foos: bar -> foos - map [ if item -> include = True then Foo { include: include, attr: attr + "_bar" } else item ] + extract [ if item -> include = True then Foo { include: include, attr: attr + "_bar" } else item ] ''' val code = model.generateCode val f = code.get("com.rosetta.test.model.functions.FuncFoo") @@ -1073,7 +1073,7 @@ class ListOperationTest { add updatedBar -> foos: bar -> foos - map [ if item -> include = True then Create_Foo( item -> include, Create_Attr( item -> attr, "_bar" ) ) else item ] + extract [ if item -> include = True then Create_Foo( item -> include, Create_Attr( item -> attr, "_bar" ) ) else item ] func Create_Foo: inputs: @@ -1266,7 +1266,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapList() { + def void shouldGenerateFunctionWithExtractList() { val model = ''' type Foo: attr string (1..1) @@ -1279,7 +1279,7 @@ class ListOperationTest { set strings: foos - map [ item -> attr ] + extract [ item -> attr ] ''' val code = model.generateCode val f = code.get("com.rosetta.test.model.functions.FuncFoo") @@ -1350,7 +1350,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapList2() { + def void shouldGenerateFunctionWithExtractList2() { val model = ''' type Foo: attr string (1..1) @@ -1363,7 +1363,7 @@ class ListOperationTest { set strings: foos - map foo [ foo -> attr ] + extract foo [ foo -> attr ] ''' val code = model.generateCode val classes = code.compileToClasses @@ -1384,7 +1384,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapListOfListThenMapToListOfCounts() { + def void shouldGenerateFunctionWithExtractListOfListThenExtractToListOfCounts() { val model = ''' type Bar: foos Foo (0..*) @@ -1400,8 +1400,8 @@ class ListOperationTest { set fooCounts: bars - map bar [ bar -> foos ] - then map fooListItem [ fooListItem count ] + extract bar [ bar -> foos ] + then extract fooListItem [ fooListItem count ] ''' val code = model.generateCode val f = code.get("com.rosetta.test.model.functions.FuncFoo") @@ -1476,7 +1476,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapListOfListThenMapToListOfCounts2() { + def void shouldGenerateFunctionWithExtractListOfListThenExtractToListOfCounts2() { val model = ''' type Bar: foos Foo (0..*) @@ -1492,8 +1492,8 @@ class ListOperationTest { set fooCounts: bars - map [ item -> foos ] - then map [ item count ] + extract [ item -> foos ] + then extract [ item count ] ''' val code = model.generateCode val classes = code.compileToClasses @@ -1513,7 +1513,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapListOfListThenFilterOnCount() { + def void shouldGenerateFunctionWithExtractListOfListThenFilterOnCount() { val model = ''' type Bar: foos Foo (0..*) @@ -1529,9 +1529,9 @@ class ListOperationTest { set fooCounts: bars - map [ item -> foos ] + extract [ item -> foos ] then filter [ item count > 1 ] - then map [ item count ] + then extract [ item count ] ''' val code = model.generateCode val classes = code.compileToClasses @@ -1567,9 +1567,9 @@ class ListOperationTest { set fooCounts: bars - map a [ a -> foos ] + extract a [ a -> foos ] then filter b [ b count > 1 ] - then map c [ c count ] + then extract c [ c count ] ''' val code = model.generateCode val classes = code.compileToClasses @@ -1589,7 +1589,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapListOfListsThenFlatten() { + def void shouldGenerateFunctionWithExtractListOfListsThenFlatten() { val model = ''' type Bar: foos Foo (0..*) @@ -1605,7 +1605,7 @@ class ListOperationTest { set foos: bars - map bar [ bar -> foos ] + extract bar [ bar -> foos ] then flatten ''' val code = model.generateCode @@ -1700,7 +1700,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapListOfListsThenFlatten2() { + def void shouldGenerateFunctionWithExtractListOfListsThenFlatten2() { val model = ''' type Bar: foos Foo (0..*) @@ -1716,7 +1716,7 @@ class ListOperationTest { set foos: bars - map [ item -> foos ] + extract [ item -> foos ] then flatten ''' val code = model.generateCode @@ -1740,7 +1740,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapListOfListsThenFlatten3() { + def void shouldGenerateFunctionWithExtractListOfListsThenFlatten3() { val model = ''' type Bar: foos Foo (0..*) @@ -1756,9 +1756,9 @@ class ListOperationTest { set attrs: bars - map [ item -> foos ] + extract [ item -> foos ] then flatten - then map [ item -> attr ] + then extract [ item -> attr ] ''' val code = model.generateCode val f = code.get("com.rosetta.test.model.functions.FuncFoo") @@ -1837,7 +1837,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapListCount() { + def void shouldGenerateFunctionWithExtractListCount() { val model = ''' type Bar: foos Foo (0..*) @@ -1853,7 +1853,7 @@ class ListOperationTest { set fooCounts: bars - map [ item -> foos count ] + extract [ item -> foos count ] ''' val code = model.generateCode val classes = code.compileToClasses @@ -1889,7 +1889,7 @@ class ListOperationTest { set fooCounts: bars - map bar [ bar -> foos count ] + extract bar [ bar -> foos count ] ''' val code = model.generateCode val classes = code.compileToClasses @@ -1909,7 +1909,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithNestedMaps() { + def void shouldGenerateFunctionWithNestedExtracts() { val model = ''' type Bar: foos Foo (0..*) @@ -1925,10 +1925,10 @@ class ListOperationTest { set updatedBars: bars - map bar [ bar -> foos - map foo [ NewFoo( foo -> attr + "_bar" ) ] + extract bar [ bar -> foos + extract foo [ NewFoo( foo -> attr + "_bar" ) ] ] - then map updatedFoos [ NewBar( updatedFoos ) ] + then extract updatedFoos [ NewBar( updatedFoos ) ] func NewBar: inputs: @@ -2070,9 +2070,9 @@ class ListOperationTest { set updatedBars: bars - map bar [ + extract bar [ NewBar( bar -> foos - map foo [ NewFoo( foo -> attr + "_bar" ) ] ) + extract foo [ NewFoo( foo -> attr + "_bar" ) ] ) ] func NewBar: @@ -2196,7 +2196,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithMapListModifyItemFunc() { + def void shouldGenerateFunctionWithExtractListModifyItemFunc() { val model = ''' type Foo: attr string (1..1) @@ -2209,7 +2209,7 @@ class ListOperationTest { set updatedFoos: foos - map [ NewFoo( item -> attr + "_1" ) ] + extract [ NewFoo( item -> attr + "_1" ) ] func NewFoo: inputs: @@ -2244,7 +2244,7 @@ class ListOperationTest { } @Test - def void shouldGenerateFunctionWithFilterThenMap() { + def void shouldGenerateFunctionWithFilterThenExtract() { val model = ''' type Foo: include boolean (1..1) @@ -2259,7 +2259,7 @@ class ListOperationTest { set newFoos: foos filter [ item -> include = True ] - then map [ item -> attr ] + then extract [ item -> attr ] ''' val code = model.generateCode @@ -2360,8 +2360,8 @@ class ListOperationTest { set strings: bars - map [ GetFoo( item -> barAttr ) ] - then map [ item -> fooAttr ] + extract [ GetFoo( item -> barAttr ) ] + then extract [ item -> fooAttr ] ''' val code = model.generateCode val f = code.get("ns1.functions.FuncFoo") @@ -2449,9 +2449,9 @@ class ListOperationTest { set strings: bars - map [ item -> foos ] + extract [ item -> foos ] then flatten - then map [ item -> attr ] + then extract [ item -> attr ] '''] val code = model.generateCode val f = code.get("ns2.functions.FuncFoo") @@ -2542,8 +2542,8 @@ class ListOperationTest { set strings: bars - map [ GetFoo( item -> barAttr ) ] - then map [ item -> fooAttr ] + extract [ GetFoo( item -> barAttr ) ] + then extract [ item -> fooAttr ] '''] val code = model.generateCode val f = code.get("ns2.functions.FuncFoo") @@ -2647,8 +2647,8 @@ class ListOperationTest { set strings: bars - map [ GetFoo( GetBaz( item -> barAttr ) ) ] - then map [ item -> fooAttr ] + extract [ GetFoo( GetBaz( item -> barAttr ) ) ] + then extract [ item -> fooAttr ] '''] val code = model.generateCode val f = code.get("ns2.functions.FuncFoo") @@ -2731,11 +2731,11 @@ class ListOperationTest { set strings: if test = "a" - then foos map [ item -> attr + "_a" ] + then foos extract [ item -> attr + "_a" ] else if test = "b" - then foos map [ item -> attr + "_b" ] + then foos extract [ item -> attr + "_b" ] else if test = "c" - then foos map [ item -> attr + "_c" ] + then foos extract [ item -> attr + "_c" ] // default else ''' val code = model.generateCode @@ -3475,7 +3475,7 @@ class ListOperationTest { set fooCount: bars reduce bar1, bar2 [ if bar1 -> foos count > bar2 -> foos count then bar1 else bar2 ] - then map [ item -> foos count ] + then extract [ item -> foos count ] ''' val code = model.generateCode val classes = code.compileToClasses @@ -3502,7 +3502,7 @@ class ListOperationTest { } @Test - def void shouldGenerateListReduceThenMapList() { + def void shouldGenerateListReduceThenExtractList() { val model = ''' type Bar: foos Foo (0..*) @@ -3519,8 +3519,8 @@ class ListOperationTest { set attrs: bars reduce bar1, bar2 [ if bar1 -> foos count > bar2 -> foos count then bar1 else bar2 ] // max by foo count - then map [ item -> foos ] - then map [ item -> attr ] + then extract [ item -> foos ] + then extract [ item -> attr ] ''' val code = model.generateCode val classes = code.compileToClasses diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaBinaryOperationTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaBinaryOperationTest.xtend index 91a6970ad..4782c92bd 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaBinaryOperationTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaBinaryOperationTest.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.generator.java.expression import com.google.common.collect.ImmutableList -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.RosettaModelObject import java.math.BigDecimal @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Disabled import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaBinaryOperationTest { @Inject extension CodeGeneratorTestHelper @@ -52,13 +52,13 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before = 5 + foo -> bar -> before any = 5 func FeatureCallNotEqualToLiteral: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before <> 5 + foo -> bar -> before all <> 5 func FeatureCallEqualToFeatureCall: inputs: foo Foo (1..1) @@ -70,7 +70,7 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before = foo -> baz -> other + foo -> bar -> before any = foo -> baz -> other func FeatureCallNotEqualToFeatureCall: inputs: foo Foo (1..1) @@ -82,19 +82,19 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before <> foo -> baz -> other + foo -> bar -> before all <> foo -> baz -> other func FeatureCallsEqualToLiteralOr: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before = 5 or foo -> baz -> other = 5 + foo -> bar -> before any = 5 or foo -> baz -> other = 5 func FeatureCallsEqualToLiteralAnd: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before = 5 and foo -> bar -> after = 5 + foo -> bar -> before any = 5 and foo -> bar -> after any = 5 ««« TODO tests compilation only, add unit test @@ -112,8 +112,7 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - // (foo -> bar -> before and foo -> baz -> other) = (foo -> bar -> after and foo -> baz -> bazValue) - [foo -> bar -> before, foo -> baz -> other] = [foo -> bar -> after, foo -> baz -> bazValue] + [foo -> bar -> before, foo -> baz -> other] = [foo -> bar -> after, foo -> baz -> bazValue] ««« TODO tests compilation only, add unit test @@ -121,7 +120,7 @@ class RosettaBinaryOperationTest { inputs: foo Foo(1..1) output: result boolean (1..1) set result: - (foo -> bar -> before = foo -> baz -> other) or (foo -> bar -> after = foo -> baz -> bazValue) + (foo -> bar -> before any = foo -> baz -> other) or (foo -> bar -> after any = foo -> baz -> bazValue) ««« TODO tests compilation only, add unit test @@ -129,7 +128,7 @@ class RosettaBinaryOperationTest { inputs: foo Foo(1..1) output: result boolean (1..1) set result: - (foo -> bar -> before = foo -> baz -> other) and (foo -> bar -> after = foo -> baz -> bazValue) + (foo -> bar -> before any = foo -> baz -> other) and (foo -> bar -> after any = foo -> baz -> bazValue) ««« TODO tests compilation only, add unit test @@ -137,7 +136,6 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - // (foo -> bar -> before or foo -> bar -> after or foo -> baz -> other) = 5.0 [foo -> bar -> before, foo -> bar -> after, foo -> baz -> other] contains 5.0 ««« TODO tests compilation only, add unit test @@ -146,8 +144,7 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - // (foo -> bar -> before and foo -> bar -> after and foo -> baz -> other) = 5.0 - [foo -> bar -> before, foo -> bar -> after, foo -> baz -> other] = 5.0 + [foo -> bar -> before, foo -> bar -> after, foo -> baz -> other] any = 5.0 ««« TODO tests compilation only, add unit test @@ -155,7 +152,7 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - AliasBefore(foo) -> numbers = 5 + AliasBefore(foo) -> numbers any = 5 ««« TODO tests compilation only, add unit test @@ -171,7 +168,7 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - AliasBefore(foo) -> numbers = 5 or AliasOther(foo) -> numbers = 5 + AliasBefore(foo) -> numbers any = 5 or AliasOther(foo) -> numbers any = 5 ««« TODO tests compilation only, add unit test @@ -179,7 +176,7 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - AliasBefore(foo) -> numbers = 5 and AliasOther(foo) -> numbers = 5 + AliasBefore(foo) -> numbers any = 5 and AliasOther(foo) -> numbers any = 5 ««« TODO tests compilation only, add unit test @@ -205,15 +202,15 @@ class RosettaBinaryOperationTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before > 5 - or ( foo -> baz -> other > 10 and foo -> bar -> after > 15 ) + foo -> bar first -> before > 5 + or ( foo -> baz -> other > 10 and foo -> bar first -> after > 15 ) or foo -> baz -> bazValue > 20 func FeatureCallGreatherThan: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before > foo -> bar2 -> before + foo -> bar first -> before > foo -> bar2 first -> before ««« Aliases diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaCountOperationTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaCountOperationTest.xtend index 7da4d4ec4..eb4a8f8e1 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaCountOperationTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaCountOperationTest.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.generator.java.expression import com.google.common.collect.ImmutableList -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.RosettaModelObject import java.math.BigDecimal @@ -20,7 +20,7 @@ import com.regnosys.rosetta.generator.java.function.FunctionGeneratorHelper import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaCountOperationTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaExistsExpressionTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaExistsExpressionTest.xtend index bcbf4c98c..94825525a 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaExistsExpressionTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/RosettaExistsExpressionTest.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.generator.java.expression import com.google.common.collect.ImmutableList -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.RosettaModelObject import java.math.BigDecimal @@ -20,7 +20,7 @@ import com.regnosys.rosetta.generator.java.function.FunctionGeneratorHelper import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaExistsExpressionTest { @Inject extension CodeGeneratorTestHelper @@ -76,43 +76,43 @@ class RosettaExistsExpressionTest { inputs: foo Foo (1..1) output: result boolean (1..1) set result: - foo -> bar -> before only exists + foo -> bar first then before only exists func OnlyExistsMultiplePaths: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - ( foo -> bar -> before, foo -> bar -> after ) only exists + foo -> bar first then ( before, after ) only exists func OnlyExistsPathWithScheme: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - ( foo -> bar -> before, foo -> bar -> afterWithScheme ) only exists + foo -> bar first then ( before, afterWithScheme ) only exists func OnlyExistsBothPathsWithScheme: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - ( foo -> bar -> beforeWithScheme, foo -> bar -> afterWithScheme ) only exists + foo -> bar first then ( beforeWithScheme, afterWithScheme ) only exists func OnlyExistsListMultiplePaths: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - ( foo -> bar -> before, foo -> bar -> afterList ) only exists + foo -> bar first then ( before, afterList ) only exists func OnlyExistsListPathWithScheme: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - ( foo -> bar -> before, foo -> bar -> afterListWithScheme ) only exists + foo -> bar first then ( before, afterListWithScheme ) only exists func OnlyExistsListBothPathsWithScheme: inputs: foo Foo (1..1) output: result boolean (1..1) set result: - ( foo -> bar -> beforeListWithScheme, foo -> bar -> afterListWithScheme ) only exists + foo -> bar first then ( beforeListWithScheme, afterListWithScheme ) only exists ««« TODO tests compilation only, add unit test func MultipleSeparateOr_NoAliases_Exists: diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/TypeCoercionTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/TypeCoercionTest.xtend index 2a1d82c32..352ffc8f2 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/TypeCoercionTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/expression/TypeCoercionTest.xtend @@ -3,7 +3,7 @@ package com.regnosys.rosetta.generator.java.expression import org.junit.jupiter.api.^extension.ExtendWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.eclipse.xtext.testing.InjectWith -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import javax.inject.Inject import org.junit.jupiter.api.Test import com.regnosys.rosetta.generator.java.statement.builder.JavaExpression @@ -25,7 +25,7 @@ import com.regnosys.rosetta.generator.java.types.RJavaFieldWithMeta import com.regnosys.rosetta.generator.java.types.RJavaReferenceWithMeta @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class TypeCoercionTest { @Inject TypeCoercionService coercionService @Inject extension ImportManagerExtension diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/CalculationFunctionGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/CalculationFunctionGeneratorTest.xtend index 849eca60c..87c1ab698 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/CalculationFunctionGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/CalculationFunctionGeneratorTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.function -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension @@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class CalculationFunctionGeneratorTest { @Inject extension FunctionGeneratorHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/FunctionGeneratorHelper.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/FunctionGeneratorHelper.xtend index 87355752b..2be9dd93e 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/FunctionGeneratorHelper.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/FunctionGeneratorHelper.xtend @@ -21,6 +21,7 @@ import com.regnosys.rosetta.generator.java.RosettaJavaPackages.RootPackage import java.lang.reflect.InvocationTargetException import javax.inject.Inject import com.regnosys.rosetta.utils.ModelIdProvider +import com.rosetta.util.DottedPath class FunctionGeneratorHelper { @@ -42,11 +43,10 @@ class FunctionGeneratorHelper { } def createFunc(Map> classes, String funcName) { - createFunc(classes, rootPackage.functions.toString, funcName) + createFunc(classes, funcName, rootPackage.functions) } - - def createFunc(Map> classes, String namespace, String funcName) { - injector.getInstance(classes.get(namespace + '.' + funcName)) as RosettaFunction + def createFunc(Map> classes, String funcName, DottedPath packageName) { + injector.getInstance(classes.get(packageName + '.' + funcName)) as RosettaFunction } def invokeFunc(RosettaFunction func, Class resultClass, Object... inputs) { diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/FunctionGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/FunctionGeneratorTest.xtend index 2d3859cf0..975d32609 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/FunctionGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/function/FunctionGeneratorTest.xtend @@ -2,7 +2,7 @@ package com.regnosys.rosetta.generator.java.function import com.google.common.collect.ImmutableList import com.regnosys.rosetta.rosetta.simple.SimplePackage -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import com.regnosys.rosetta.validation.RosettaIssueCodes @@ -36,7 +36,7 @@ import com.rosetta.model.lib.meta.Reference import com.rosetta.model.metafields.MetaFields @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class FunctionGeneratorTest { @Inject extension FunctionGeneratorHelper @@ -44,6 +44,23 @@ class FunctionGeneratorTest { @Inject extension ModelHelper @Inject extension ValidationTestHelper + @Test + def void reportingRuleSupportsRecursion() { + val code = ''' + reporting rule Fac from int: + if item = 1 + then 1 + else item * Fac(item - 1) + '''.generateCode + + code.compileToClasses + val classes = code.compileToClasses + + val facRule = classes.createFunc("FacRule", rootPackage.reports) + + assertEquals(120, facRule.invokeFunc(Integer, #[5])) + } + @Test def void testCanPassMetaFromOutputOfFunctionCall() { val code = ''' @@ -1196,42 +1213,6 @@ class FunctionGeneratorTest { assertFalse(testOneOf.invokeFunc(Boolean, #[b2])) } - @Test - def void onlyExistsOnList() { - val code = ''' - type A: - a1 string (0..1) - a2 string (0..1) - a3 boolean (0..1) - - func TestOnlyExists: - inputs: - a A (0..*) - output: - result boolean (1..1) - - set result: - a -> a1 only exists - '''.generateCode - - val classes = code.compileToClasses - - val a1 = classes.createInstanceUsingBuilder("A", #{ - "a1" -> "some value" - }) - val a2 = classes.createInstanceUsingBuilder("A", #{ - "a1" -> "other value" - }) - val a3 = classes.createInstanceUsingBuilder("A", #{ - "a1" -> "some value", - "a2" -> "other value" - }) - - val testOnlyExists = classes.createFunc("TestOnlyExists") - assertTrue(testOnlyExists.invokeFunc(Boolean, #[List.of(a1, a2)])) - assertFalse(testOnlyExists.invokeFunc(Boolean, #[List.of(a1, a2, a3)])) - } - @Test def void testDeepPathOperatorWithMeta() { val code = ''' @@ -3119,7 +3100,7 @@ class FunctionGeneratorTest { result string (0..1) condition: - [ m1 -> currency , m2 -> currency ] = currency + [ m1 -> currency , m2 -> currency ] any = currency '''.generateCode code.compileToClasses } @@ -3479,7 +3460,7 @@ class FunctionGeneratorTest { top1-> foo disjoint top2 -> bar '''.parseRosetta - model.assertError(ROSETTA_DISJOINT_EXPRESSION, null, "Incompatible types: cannot use operator 'disjoint' with Foo and string.") + model.assertError(ROSETTA_DISJOINT_EXPRESSION, null, "Types `Foo` and `string` are not comparable") } @Test @@ -3499,14 +3480,14 @@ class FunctionGeneratorTest { top1 Top (1..1) top2 Top (1..1) - output: result int (1..1) + output: result boolean (1..1) set result: top1 -> foo and top2 -> foo '''.parseRosetta - model.assertError(SimplePackage.Literals.OPERATION, RosettaIssueCodes.TYPE_ERROR, - "Left hand side of 'and' expression must be boolean") + model.assertError(LOGICAL_OPERATION, null, + "Expected type `boolean`, but got `Foo` instead. Cannot use `Foo` with operator `and`") } @Test @@ -3948,7 +3929,7 @@ class FunctionGeneratorTest { type Bar: num number (0..1) - zap Zap (1..1) + zap Zap (1..2) enum Zap: A B C @@ -3974,8 +3955,8 @@ class FunctionGeneratorTest { set res: t1->num = t2->nums '''.parseRosetta - model.assertWarning(ROSETTA_BINARY_OPERATION, null, - "Comparison operator = should specify 'all' or 'any' when comparing a list to a single value") + model.assertError(EQUALITY_OPERATION, null, + "Operator `=` should specify `all` or `any` when comparing a list to a single value") } @Test diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/EnumGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/EnumGeneratorTest.xtend index 8180f22ba..067878f6c 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/EnumGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/EnumGeneratorTest.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.generator.java.object import com.regnosys.rosetta.generator.java.enums.EnumHelper -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.testing.InjectWith @@ -15,7 +15,7 @@ import org.junit.jupiter.api.Disabled import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class EnumGeneratorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ExternalHashcodeGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ExternalHashcodeGeneratorTest.xtend index db05bd55a..42a48c797 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ExternalHashcodeGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ExternalHashcodeGeneratorTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.object -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.testing.InjectWith @@ -13,7 +13,7 @@ import static org.hamcrest.MatcherAssert.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ExternalHashcodeGeneratorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/GlobalKeyGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/GlobalKeyGeneratorTest.xtend index e6a4a9c82..62916d0b2 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/GlobalKeyGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/GlobalKeyGeneratorTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.object -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.testing.InjectWith @@ -14,7 +14,7 @@ import static org.hamcrest.MatcherAssert.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class GlobalKeyGeneratorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelMetaGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelMetaGeneratorTest.xtend index 71085ec5c..97f9c1641 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelMetaGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelMetaGeneratorTest.xtend @@ -2,7 +2,7 @@ package com.regnosys.rosetta.generator.java.object import com.google.inject.AbstractModule import com.google.inject.Guice -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import com.rosetta.model.lib.functions.ConditionValidator @@ -27,7 +27,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ModelMetaGeneratorTest { @Inject extension ModelHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectBoilerPlateTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectBoilerPlateTest.xtend index 3c5539249..36d93a2cc 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectBoilerPlateTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectBoilerPlateTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.object -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.testing.InjectWith @@ -13,7 +13,7 @@ import static org.hamcrest.MatcherAssert.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ModelObjectBoilerPlateTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectBuilderGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectBuilderGeneratorTest.xtend index 10ed592eb..b65768e7b 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectBuilderGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectBuilderGeneratorTest.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.generator.java.object import com.google.common.collect.Lists -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import java.util.List @@ -16,7 +16,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ModelObjectBuilderGeneratorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectGeneratorTest.xtend index e55b62206..5aa1f889f 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/ModelObjectGeneratorTest.xtend @@ -3,7 +3,7 @@ package com.regnosys.rosetta.generator.java.object import com.google.common.collect.ImmutableList import com.google.common.collect.Lists import com.regnosys.rosetta.generator.java.RosettaJavaPackages.RootPackage -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import com.rosetta.model.lib.RosettaModelObject @@ -28,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ModelObjectGeneratorTest { @Inject extension ReflectExtensions diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/PojoInheritanceRegressionTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/PojoInheritanceRegressionTest.xtend index d7b547bcb..3cee755c4 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/PojoInheritanceRegressionTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/PojoInheritanceRegressionTest.xtend @@ -3,7 +3,7 @@ package com.regnosys.rosetta.generator.java.object import org.eclipse.xtext.testing.extensions.InjectionExtension import org.junit.jupiter.api.^extension.ExtendWith import org.eclipse.xtext.testing.InjectWith -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import javax.inject.Inject import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import org.junit.jupiter.api.Test @@ -20,7 +20,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle * gets through, please add to this test so it does not happen again. */ @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) @TestInstance(Lifecycle.PER_CLASS) class PojoInheritanceRegressionTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/PojoRegressionTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/PojoRegressionTest.xtend index 2462d2c84..35f59e2a8 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/PojoRegressionTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/PojoRegressionTest.xtend @@ -3,7 +3,7 @@ package com.regnosys.rosetta.generator.java.object import org.eclipse.xtext.testing.extensions.InjectionExtension import org.junit.jupiter.api.^extension.ExtendWith import org.eclipse.xtext.testing.InjectWith -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import javax.inject.Inject import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import org.junit.jupiter.api.Test @@ -20,7 +20,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle * through, please add to this test so it does not happen again. */ @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) @TestInstance(Lifecycle.PER_CLASS) class PojoRegressionTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaExtensionsTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaExtensionsTest.xtend index 40f0ebefe..4246dc590 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaExtensionsTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaExtensionsTest.xtend @@ -3,7 +3,7 @@ package com.regnosys.rosetta.generator.java.object import com.regnosys.rosetta.rosetta.RosettaEnumeration import com.regnosys.rosetta.rosetta.RosettaModel import com.regnosys.rosetta.rosetta.simple.Data -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.eclipse.xtext.testing.util.ParseHelper @@ -15,7 +15,7 @@ import javax.inject.Inject import com.regnosys.rosetta.types.RObjectFactory @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaExtensionsTest { @Inject extension ParseHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaModelTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaModelTest.xtend index de5357010..40f4ebfe6 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaModelTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaModelTest.xtend @@ -4,7 +4,7 @@ package com.regnosys.rosetta.generator.java.object import com.regnosys.rosetta.rosetta.RosettaEnumeration -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension @@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaModelTest{ @Inject extension ModelHelper modelHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaObjectInheritanceGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaObjectInheritanceGeneratorTest.xtend index 8425d664d..84ca205a5 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaObjectInheritanceGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaObjectInheritanceGeneratorTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.object -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.testing.InjectWith @@ -13,7 +13,7 @@ import org.junit.jupiter.api.Disabled import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaObjectInheritanceGeneratorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaProcessorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaProcessorTest.xtend index 2a633fe2b..8c422e566 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaProcessorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/object/RosettaProcessorTest.xtend @@ -8,12 +8,12 @@ import com.rosetta.model.lib.RosettaModelObject import org.junit.jupiter.api.^extension.ExtendWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.eclipse.xtext.testing.InjectWith -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import static org.junit.jupiter.api.Assertions.* @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaProcessorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/qualify/RosettaQualifyEventTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/qualify/RosettaQualifyEventTest.xtend index 2807a16a7..743ad4637 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/qualify/RosettaQualifyEventTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/qualify/RosettaQualifyEventTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.qualify -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.RosettaModelObject import java.math.BigDecimal @@ -18,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaQualifyEventTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/qualify/RosettaQualifyProductTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/qualify/RosettaQualifyProductTest.xtend index 78007c689..26b65e213 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/qualify/RosettaQualifyProductTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/qualify/RosettaQualifyProductTest.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.generator.java.qualify import com.google.common.collect.ImmutableList -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.RosettaModelObject import com.rosetta.model.lib.qualify.QualifyResult @@ -20,7 +20,7 @@ import static org.hamcrest.core.Is.is import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaQualifyProductTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/reports/ReportingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/reports/ReportingTest.xtend index 882800502..0345e1f32 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/reports/ReportingTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/reports/ReportingTest.xtend @@ -1,7 +1,7 @@ package com.regnosys.rosetta.generator.java.reports import org.eclipse.xtext.testing.InjectWith -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.junit.jupiter.api.^extension.ExtendWith import org.eclipse.xtext.testing.extensions.InjectionExtension import javax.inject.Inject @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test import com.rosetta.model.lib.ModelReportId import com.rosetta.util.types.generated.GeneratedJavaClassService -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) @ExtendWith(InjectionExtension) class ReportingTest { diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/reports/TabulatorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/reports/TabulatorTest.xtend index 9b75c7d4c..337fca843 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/reports/TabulatorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/reports/TabulatorTest.xtend @@ -2,7 +2,7 @@ package com.regnosys.rosetta.generator.java.reports import com.regnosys.rosetta.config.file.RosettaConfigurationFileProvider import com.regnosys.rosetta.generator.java.RosettaJavaPackages.RootPackage -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.rosetta.model.lib.ModelReportId import com.rosetta.model.lib.RosettaModelObject @@ -27,7 +27,7 @@ import static org.junit.jupiter.api.Assertions.* import static extension com.regnosys.rosetta.tests.util.CustomConfigTestHelper.* import com.rosetta.model.metafields.MetaFields -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) @ExtendWith(InjectionExtension) class TabulatorTest { diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/rule/RosettaRuleGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/rule/RosettaRuleGeneratorTest.xtend index 1963e676e..be53d19f9 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/rule/RosettaRuleGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/rule/RosettaRuleGeneratorTest.xtend @@ -2,11 +2,10 @@ package com.regnosys.rosetta.generator.java.rule import com.google.inject.Guice import com.google.inject.Injector -import com.regnosys.rosetta.generator.java.function.FunctionGeneratorHelper -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import com.regnosys.rosetta.tests.util.ModelHelper -import com.regnosys.rosetta.validation.RosettaIssueCodes +import com.regnosys.rosetta.generator.java.function.FunctionGeneratorHelper import com.rosetta.model.lib.RosettaModelObject import java.util.Map import javax.inject.Inject @@ -20,8 +19,9 @@ import org.junit.jupiter.api.^extension.ExtendWith import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* import static org.hamcrest.MatcherAssert.* import static org.junit.jupiter.api.Assertions.* +import com.rosetta.util.DottedPath -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) @ExtendWith(InjectionExtension) class RosettaRuleGeneratorTest { @@ -763,7 +763,7 @@ class RosettaRuleGeneratorTest { } val classes = code.compileToClasses - val test = classes.createFunc("com.rosetta.test.model.reports", "TEST_REGMiFIRReportFunction") + val test = classes.createFunc("TEST_REGMiFIRReportFunction", DottedPath.splitOnDots("com.rosetta.test.model.reports")) val input = classes.createInstanceUsingBuilder("Bar", #{"bar1" -> "bar1Value"}) @@ -1277,8 +1277,8 @@ class RosettaRuleGeneratorTest { '''.toString .replace('\r', "") .parseRosetta - .assertError(ROSETTA_SYMBOL_REFERENCE, RosettaIssueCodes.TYPE_ERROR, - "Expected type 'Foo' but was 'Bar'") + .assertError(ROSETTA_SYMBOL_REFERENCE, null, + "Expected type `Foo`, but got `Bar` instead. Rule `Rule2` cannot be called with type `Bar`") } diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/util/ModelGeneratorUtilTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/util/ModelGeneratorUtilTest.xtend index d5f7404ce..05f6858d9 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/util/ModelGeneratorUtilTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/util/ModelGeneratorUtilTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.util -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.junit.jupiter.api.Test @@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ModelGeneratorUtilTest { @Inject extension ModelHelper modelHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/validator/ValidatorGeneratorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/validator/ValidatorGeneratorTest.xtend index 411ca8190..f50904a3b 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/validator/ValidatorGeneratorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/validator/ValidatorGeneratorTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.generator.java.validator -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import javax.inject.Inject import org.eclipse.xtext.testing.InjectWith @@ -12,7 +12,7 @@ import org.junit.jupiter.api.^extension.ExtendWith import static org.junit.jupiter.api.Assertions.* @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ValidatorGeneratorTest { @Inject extension CodeGeneratorTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/interpreter/RosettaInterpreterTest.java b/rosetta-testing/src/test/java/com/regnosys/rosetta/interpreter/RosettaInterpreterTest.java index 9f61a1b2d..cf4ff2ff4 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/interpreter/RosettaInterpreterTest.java +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/interpreter/RosettaInterpreterTest.java @@ -23,7 +23,7 @@ import javax.inject.Inject; import com.regnosys.rosetta.rosetta.expression.RosettaExpression; -import com.regnosys.rosetta.tests.RosettaInjectorProvider; +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.eclipse.xtext.testing.InjectWith; @@ -37,7 +37,7 @@ import com.rosetta.model.lib.RosettaNumber; @ExtendWith(InjectionExtension.class) -@InjectWith(RosettaInjectorProvider.class) +@InjectWith(RosettaTestInjectorProvider.class) public class RosettaInterpreterTest { @Inject private ExpressionParser parser; diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/issues/Issue844.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/issues/Issue844.xtend index 73a1e9b2b..792f01bfd 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/issues/Issue844.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/issues/Issue844.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.issues -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension @@ -17,7 +17,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle // Regression test for https://github.com/finos/rune-dsl/issues/844 @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) @TestInstance(Lifecycle.PER_CLASS) class Issue844 { diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/resource/RosettaFragmentProviderTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/resource/RosettaFragmentProviderTest.xtend index 4041cfb95..036855b7a 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/resource/RosettaFragmentProviderTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/resource/RosettaFragmentProviderTest.xtend @@ -2,7 +2,7 @@ package com.regnosys.rosetta.resource import com.regnosys.rosetta.rosetta.RosettaModel import com.regnosys.rosetta.rosetta.simple.Data -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.eclipse.emf.ecore.util.EcoreUtil import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension @@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.* import javax.inject.Inject @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaFragmentProviderTest { @Inject extension ParseHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaExpressionsTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaExpressionsTest.xtend index b044a7329..1734cc9da 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaExpressionsTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaExpressionsTest.xtend @@ -26,7 +26,7 @@ import javax.inject.Inject * A set of tests for all instances of RosettaExpression i.e. RosettaAdditiveExpression */ @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaExpressionsTest { @Inject extension CodeGeneratorTestHelper @@ -99,7 +99,7 @@ class RosettaExpressionsTest { output: result boolean (1..1) set result: test -> one + test -> two = 42 - '''.parseRosetta.assertError(ARITHMETIC_OPERATION, null, "Incompatible types: cannot use operator '+' with date and date.") + '''.parseRosetta.assertError(ARITHMETIC_OPERATION, null, "Expected type `time`, but got `date` instead. Cannot add `date` to a `date`") } /** diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaParsingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaParsingTest.xtend index 50ff23108..1185ab9fa 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaParsingTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaParsingTest.xtend @@ -28,7 +28,7 @@ import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals import static org.junit.jupiter.api.Assertions.* @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaParsingTest { @Inject extension ModelHelper modelHelper @@ -220,7 +220,7 @@ class RosettaParsingTest { def void testDefaultIncompatibleTypesReturnsError() { "a default 2" .parseExpression(#["a string (1..1)"]) - .assertError(DEFAULT_OPERATION, null, "Incompatible types: cannot use operator 'default' with string and int.") + .assertError(DEFAULT_OPERATION, null, "Types `string` and `int` do not have a common supertype") } @Test @@ -230,13 +230,6 @@ class RosettaParsingTest { .assertNoIssues } - @Test - def void testDefaultIncompatibleCardinalityReturnsError() { - "a default b" - .parseExpression(#["a string (1..1)", "b string (1..*)"]) - .assertError(DEFAULT_OPERATION, null, "Cardinality mismatch - default operator requires both sides to have matching cardinality") - } - @Test def void testOnlyExistsInsideFunctionalOperation() { ''' @@ -763,7 +756,7 @@ class RosettaParsingTest { condition Foo_Bar: if foo then - if bar = BarEnum -> abc + if bar any = BarEnum -> abc then foobar exists else foobar is absent enum BarEnum: diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/util/ExpressionParserTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/util/ExpressionParserTest.xtend index 133d58442..f2009b486 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/util/ExpressionParserTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/util/ExpressionParserTest.xtend @@ -7,10 +7,10 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.^extension.ExtendWith import javax.inject.Inject -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ExpressionParserTest { @Inject extension ExpressionParser diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/types/RosettaTypeProviderTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/types/RosettaTypeProviderTest.xtend index 4df92fee6..434eacb49 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/types/RosettaTypeProviderTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/types/RosettaTypeProviderTest.xtend @@ -2,8 +2,9 @@ package com.regnosys.rosetta.types import com.regnosys.rosetta.rosetta.expression.RosettaBinaryOperation import com.regnosys.rosetta.rosetta.expression.RosettaContainsExpression +import com.regnosys.rosetta.rosetta.simple.Data import com.regnosys.rosetta.rosetta.simple.Function -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension @@ -13,14 +14,642 @@ import org.junit.jupiter.api.^extension.ExtendWith import static org.junit.jupiter.api.Assertions.* import org.eclipse.xtext.testing.validation.ValidationTestHelper import javax.inject.Inject +import com.regnosys.rosetta.tests.util.ExpressionParser +import com.regnosys.rosetta.types.builtin.RBuiltinTypeService +import com.regnosys.rosetta.rosetta.RosettaModel +import com.regnosys.rosetta.rosetta.expression.RosettaExpression +import java.util.List +import com.regnosys.rosetta.rosetta.expression.MapOperation +import com.regnosys.rosetta.rosetta.expression.ArithmeticOperation +import static extension com.regnosys.rosetta.types.RMetaAnnotatedType.* +import java.util.Optional +import java.math.BigInteger +import java.math.BigDecimal +import com.regnosys.rosetta.rosetta.RosettaEnumeration +import com.regnosys.rosetta.rosetta.expression.LogicalOperation +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle + +import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* +import org.eclipse.xtext.nodemodel.util.NodeModelUtils @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) +@TestInstance(Lifecycle.PER_CLASS) class RosettaTypeProviderTest { @Inject extension RosettaTypeProvider + @Inject extension CardinalityProvider @Inject extension ModelHelper modelHelper @Inject extension ValidationTestHelper + @Inject extension ExpressionParser + @Inject extension TypeFactory + @Inject extension RObjectFactory + @Inject extension TypeSystem + @Inject extension RBuiltinTypeService builtins + + RMetaAttribute SCHEME + @BeforeAll + def void setup() { + SCHEME = new RMetaAttribute("scheme", UNCONSTRAINED_STRING, null) + } + + private def void assertIsValidWithType(CharSequence expr, RMetaAnnotatedType expectedType, boolean expectedIsMulti, List context, String... attributes) { + assertIsValidWithType(expr.parseExpression(context, attributes), expr, expectedType, expectedIsMulti) + } + private def void assertIsValidWithType(CharSequence expr, RMetaAnnotatedType expectedType, boolean expectedIsMulti, List context) { + assertIsValidWithType(expr.parseExpression(context), expr, expectedType, expectedIsMulti) + } + private def void assertIsValidWithType(CharSequence expr, RMetaAnnotatedType expectedType, boolean expectedIsMulti, String... attributes) { + assertIsValidWithType(expr.parseExpression(attributes), expr, expectedType, expectedIsMulti) + } + private def void assertIsValidWithType(CharSequence expr, RMetaAnnotatedType expectedType, boolean expectedIsMulti) { + assertIsValidWithType(expr.parseExpression, expr, expectedType, expectedIsMulti) + } + private def void assertIsValidWithType(RosettaExpression expr, RMetaAnnotatedType expectedType, boolean expectedIsMulti) { + assertIsValidWithType(expr, NodeModelUtils.findActualNodeFor(expr).text, expectedType, expectedIsMulti) + } + private def void assertIsValidWithType(RosettaExpression expr, CharSequence originalExpression, RMetaAnnotatedType expectedType, boolean expectedIsMulti) { + expr.assertNoIssues + val actual = expr.RMetaAnnotatedType + + assertEquals(expectedType, actual, "Expression: " + originalExpression) + if (expectedIsMulti) { + assertTrue(expr.isMulti, "Expected multi cardinality. Expression: " + originalExpression) + } else { + assertFalse(expr.isMulti, "Expected single cardinality. Expression: " + originalExpression) + } + } + + @Test + def void testLiteralTypeInference() { + 'False'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + '"Some string"'.assertIsValidWithType(stringWithNoMeta(11, 11), false) + '3.14'.assertIsValidWithType(numberWithNoMeta(3, 2, "3.14", "3.14"), false) + '1'.assertIsValidWithType(intWithNoMeta(1, "1", "1"), false) + 'empty'.assertIsValidWithType(NOTHING_WITH_NO_META, false) + } + + @Test + def void testVariableTypeInference() { + val context = ''' + func TestVar: + output: result number (1..4) + alias c: if True then 42 else -1/12 + add result: + c + + func TestImplicitVar: + output: result int (3..3) + add result: + [1, 2, 3] extract item + 1 + '''.parseRosettaWithNoIssues + 'a'.assertIsValidWithType(UNCONSTRAINED_INT.withMeta(#[SCHEME]), true, #["a int (2..4) [metadata scheme]"]) + 'b'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false, #["b boolean (1..1)"]) + context.elements.get(0) as Function => [operations.head.expression.assertIsValidWithType(UNCONSTRAINED_NUMBER_WITH_NO_META, false)] + + context.elements.get(1) as Function => [operations.head.expression as MapOperation => [ + function.body as ArithmeticOperation => [ + left.assertIsValidWithType(intWithNoMeta(1, "1", "3"), false) + ] + ]]; + } + + @Test + def void testLogicalOperationTypeInference() { + 'True or False'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + 'True and False'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + } + + @Test + def void testLogicalOperationTypeChecking() { + '1 or False' + .parseExpression + .assertError(LOGICAL_OPERATION, null, "Expected type `boolean`, but got `int` instead. Cannot use `int` with operator `or`") + 'True or 3.14' + .parseExpression + .assertError(LOGICAL_OPERATION, null, "Expected type `boolean`, but got `number` instead. Cannot use `number` with operator `or`") + 'a or False' + .parseExpression(#['a boolean (1..2)']) + .assertWarning(LOGICAL_OPERATION, null, "Expecting single cardinality. The `or` operator requires a single cardinality input") + } + + @Test + def void testEqualityOperationTypeInference() { + '[2, 3] = [6.0, 7, 8]'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + '[2, 3] <> [6.0, 7, 8]'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + '[1, 3] all = 5.0'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + // TODO? +// 'empty all <> 5.0'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + '[1, 3] any = 5.0'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + + 'a = 1'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false, #['a int (0..1)']) + } + + @Test + def void testEqualityOperationTypeChecking() { + '1 = True' + .parseExpression + .assertError(EQUALITY_OPERATION, null, "Types `int` and `boolean` are not comparable") + // TODO? +// 'empty = True' +// .parseExpression +// .assertError(EQUALITY_OPERATION, null, "Cannot compare an empty value to a single value, as they cannot be of the same length. Perhaps you forgot to write `all` or `any` in front of the operator?") +// '[1, 2] = [3, 4, 5]' +// .parseExpression +// .assertError(EQUALITY_OPERATION, null, "Cannot compare a list with 2 items to a list with 3 items, as they cannot be of the same length.") + '[1, 2] <> [True, False, False]' + .parseExpression + .assertError(EQUALITY_OPERATION, null, "Types `int` and `boolean` are not comparable") + + '1 = True' + .parseExpression + .assertError(EQUALITY_OPERATION, null, "Types `int` and `boolean` are not comparable") + '[1, 3] any <> a' + .parseExpression(#['a int (1..2)']) + .assertWarning(EQUALITY_OPERATION, null, "Expecting single cardinality") +// '[1, 2] all = empty' +// .parseExpression +// .assertError(EQUALITY_OPERATION, null, "Expected a single value, but got an empty value instead") +// 'empty any = empty' +// .parseExpression +// .assertError(EQUALITY_OPERATION, null, "Expected a single value, but got an empty value instead") + '[1, 2] all = [1, 2]' + .parseExpression + .assertWarning(EQUALITY_OPERATION, null, "Expecting single cardinality") + '5 any <> [1, 2]' + .parseExpression + .assertWarning(EQUALITY_OPERATION, null, "Expecting multi cardinality. Did you mean to flip around the operands of the `<>` operator?") +// '[3.0] any <> 5' +// .parseExpression +// .assertError(EQUALITY_OPERATION, null, "The cardinality operator `any` is redundant when comparing two single values") + } + + // TODO: test arithmetic and comparisons with dates/times/etc + @Test + def void testArithmeticOperationTypeInference() { + '3 + 4'.assertIsValidWithType(intWithNoMeta(Optional.empty, Optional.of(BigInteger.valueOf(7)), Optional.of(BigInteger.valueOf(7))), false) + '3.0 + 4'.assertIsValidWithType(numberWithNoMeta(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("7")), Optional.of(new BigDecimal("7")), Optional.empty), false) + '3 + 4.0'.assertIsValidWithType(numberWithNoMeta(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("7")), Optional.of(new BigDecimal("7")), Optional.empty), false) + '3.0 + 4.0'.assertIsValidWithType(numberWithNoMeta(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("7")), Optional.of(new BigDecimal("7")), Optional.empty), false) + '"ab" + "cd"'.assertIsValidWithType(stringWithNoMeta(4, 4), false) + + '3 - 4'.assertIsValidWithType(intWithNoMeta(Optional.empty, Optional.of(BigInteger.valueOf(-1)), Optional.of(BigInteger.valueOf(-1))), false) + '3 - 4.0'.assertIsValidWithType(numberWithNoMeta(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("-1")), Optional.of(new BigDecimal("-1")), Optional.empty), false) + + '3 * 4'.assertIsValidWithType(intWithNoMeta(Optional.empty, Optional.of(BigInteger.valueOf(12)), Optional.of(BigInteger.valueOf(12))), false) + '3.0 * 4'.assertIsValidWithType(numberWithNoMeta(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("12")), Optional.of(new BigDecimal("12")), Optional.empty), false) + + '3 / 4'.assertIsValidWithType(UNCONSTRAINED_NUMBER_WITH_NO_META, false) + } + + @Test + def void testArithmeticOperationTypeChecking() { + '[1, 2] + 3' + .parseExpression + .assertWarning(ARITHMETIC_OPERATION, null, "Expecting single cardinality. The `+` operator requires a single cardinality input") + // TODO +// 'empty - 3' +// .parseExpression +// .assertError(ARITHMETIC_OPERATION, null, "Expected a single value, but got an empty value instead.") + '1.5 * False' + .parseExpression + .assertError(ARITHMETIC_OPERATION, null, "Expected type `number`, but got `boolean` instead. Cannot use `boolean` with operator `*`") + '"ab" + 3' + .parseExpression + .assertError(ARITHMETIC_OPERATION, null, "Expected type `string`, but got `int` instead. Cannot add `int` to a `string`") + 'a + 5' + .parseExpression(#['a int (1..2)']) + .assertWarning(ARITHMETIC_OPERATION, null, "Expecting single cardinality. The `+` operator requires a single cardinality input") + } + + @Test + def void testComparisonOperationTypeInference() { + '1 < 2'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + '3 > 3.14'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + '-5.1 <= 42'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + '-3.14 >= 3.14'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + + '[1, 2] any < 5'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + // TODO? +// 'empty all > 5'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + } + + @Test + def void testComparisonOperationTypeChecking() { + // TODO: support date, zonedDateTime and `time`? + '[1, 2] < 3' + .parseExpression + .assertWarning(COMPARISON_OPERATION, null, "Expecting single cardinality. Did you mean to use `all` or `any` in front of the `<` operator?") + // TODO +// 'empty > 3' +// .parseExpression +// .assertError(COMPARISON_OPERATION, null, "Expected a single value, but got an empty value instead.") + '1.5 <= False' + .parseExpression + .assertError(COMPARISON_OPERATION, null, "Expected type `number`, but got `boolean` instead. Cannot compare a `boolean` to a `number`") + + 'a < 5' + .parseExpression(#['a int (1..2)']) + .assertWarning(COMPARISON_OPERATION, null, "Expecting single cardinality. Did you mean to use `all` or `any` in front of the `<` operator?") + '[1, 2] any < a' + .parseExpression(#['a int (1..2)']) + .assertWarning(COMPARISON_OPERATION, null, "Expecting single cardinality") +// '[1, 2] all >= empty' +// .parseExpression +// .assertError(COMPARISON_OPERATION, null, "Expected a single value, but got an empty value instead") + 'empty any < empty' + .parseExpression + .assertWarning(COMPARISON_OPERATION, null, "Expecting multi cardinality. Did you mean to remove the `any` modifier on the `<` operator?") + '[1, 2] all > [1, 2]' + .parseExpression + .assertWarning(COMPARISON_OPERATION, null, "Expecting single cardinality") + '5 any <= [1, 2]' + .parseExpression + .assertWarning(COMPARISON_OPERATION, null, "Expecting single cardinality. Did you mean to flip around the operands of the `<=` operator?") + '5 all >= 1' + .parseExpression + .assertWarning(COMPARISON_OPERATION, null, "Expecting multi cardinality. Did you mean to remove the `all` modifier on the `>=` operator?") + } + + @Test + def void testConditionalExpressionTypeInference() { + 'if True then [1, 2] else [3.0, 4.0, 5.0, 6.0]'.assertIsValidWithType(numberWithNoMeta(2, 1, "1", "6"), true); + } + + @Test + def void testConditionalExpressionTypeChecking() { + 'if [True, False] then 1 else 2' + .parseExpression + .assertWarning(ROSETTA_CONDITIONAL_EXPRESSION, null, "Expecting single cardinality. The condition of an if-then-else expression should be single cardinality") + // TODO +// 'if empty then 1 else 2' +// .parseExpression +// .assertError(ROSETTA_CONDITIONAL_EXPRESSION, null, "Expected a single value, but got an empty value instead.") + 'if True then 1 else False' + .parseExpression + .assertError(ROSETTA_CONDITIONAL_EXPRESSION, null, "Types `int` and `boolean` do not have a common supertype") + 'if True then [1, 2, 3] else [False, True]' + .parseExpression + .assertError(ROSETTA_CONDITIONAL_EXPRESSION, null, "Types `int` and `boolean` do not have a common supertype") + } + + @Test + def void testListLiteralTypeInference() { + '[]'.assertIsValidWithType(NOTHING_WITH_NO_META, false); + '[2, 4.5, 7, -3.14]'.assertIsValidWithType(numberWithNoMeta(3, 2, "-3.14", "7"), true); + '[2, [1, 2], -3.14]'.assertIsValidWithType(numberWithNoMeta(3, 2, "-3.14", "2"), true); + } + + @Test + def void testListLiteralTypeChecking() { + '[1, True]' + .parseExpression + .assertError(LIST_LITERAL, null, "Types `int` and `boolean` do not have a common supertype") + } + + @Test + def void testFunctionCallTypeInference() { + val context = ''' + func SomeFunc: + inputs: + a int (1..1) + b boolean (2..4) + output: result number (3..5) + add result: + [1.0, 2.0, 3.0] + '''.parseRosettaWithNoIssues + 'SomeFunc(42, [True, False, True])'.assertIsValidWithType(UNCONSTRAINED_NUMBER_WITH_NO_META, true, #[context]) + } + + @Test + def void testFunctionCallTypeChecking() { + val context = ''' + namespace test + + func SomeFunc: + inputs: + a int (1..1) + b boolean (2..4) + output: result int (1..1) + set result: + 42 + '''.parseRosettaWithNoIssues + + 'SomeFunc(1, [False, True], True)' + .parseExpression(#[context]) + .assertError(ROSETTA_SYMBOL_REFERENCE, null, "Expected 2 arguments, but got 3 instead"); + 'SomeFunc(1, [2, 3])' + .parseExpression(#[context]) + .assertError(ROSETTA_SYMBOL_REFERENCE, null, "Expected type `boolean`, but got `int` instead. Cannot assign `int` to input `b`"); + // TODO +// 'SomeFunc(1, [False, True, False, False, True])' +// .parseExpression(#[context]) +// .assertError(ROSETTA_SYMBOL_REFERENCE, null, "Expected a list with 2 to 4 items, but got a list with 5 items instead"); + } + + @Test + def void testProjectionTypeInference() { + val context = ''' + namespace test + + type A: + x int (1..1) + y number (0..*) + z boolean (3..7) + '''.parseRosettaWithNoIssues + 'a -> x'.assertIsValidWithType(UNCONSTRAINED_INT_WITH_NO_META, false, #[context], #['a A (1..1)']) + 'a -> y'.assertIsValidWithType(UNCONSTRAINED_NUMBER_WITH_NO_META, true, #[context], #['a A (1..1)']) + 'a -> z'.assertIsValidWithType(BOOLEAN_WITH_NO_META, true, #[context], #['a A (1..1)']) + 'a -> x'.assertIsValidWithType(UNCONSTRAINED_INT_WITH_NO_META, true, #[context], #['a A (2..5)']) + 'a -> y'.assertIsValidWithType(UNCONSTRAINED_NUMBER_WITH_NO_META, true, #[context], #['a A (1..1)']) + 'a -> z'.assertIsValidWithType(BOOLEAN_WITH_NO_META, true, #[context], #['a A (1..1)']) + } + + @Test + def void testEnumTypeInference() { + val context = ''' + namespace test + + enum A: + V1 + V2 + + func Test: + output: result A (1..1) + set result: + A -> V1 + '''.parseRosettaWithNoIssues + val A = (context.elements.get(0) as RosettaEnumeration).buildREnumType; + 'A -> V1'.assertIsValidWithType(A.withNoMeta, false, #[context]) + } + + @Test + def void testExistsTypeInference() { + 'a exists'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false, #['a int (0..1)']); + 'a exists'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false, #['a int (0..3)']); + } + + @Test + def void testExistsTypeChecking() { + // TODO +// 'empty exists' +// .parseExpression +// .assertError(ROSETTA_EXISTS_EXPRESSION, null, "Expected an optional value, but got an empty value instead.") +// '42 exists' +// .parseExpression +// .assertError(ROSETTA_EXISTS_EXPRESSION, null, "Expected an optional value, but got a single value instead.") +// '(if True then 42 else [1, 2, 3, 4, 5]) exists' +// .parseExpression +// .assertError(ROSETTA_EXISTS_EXPRESSION, null, "Expected an optional value, but got a list with 1 to 5 items instead.") + } + + @Test + def void testAbsentTypeInference() { + 'a is absent'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false, #['a int (0..1)']); + 'a is absent'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false, #['a int (0..3)']); + } + + @Test + def void testAbsentTypeChecking() { + // TODO +// 'empty is absent' +// .parseExpression +// .assertError(ROSETTA_ABSENT_EXPRESSION, null, "Expected an optional value, but got an empty value instead.") +// '42 is absent' +// .parseExpression +// .assertError(ROSETTA_ABSENT_EXPRESSION, null, "Expected an optional value, but got a single value instead.") +// '(if True then 42 else [1, 2, 3, 4, 5]) is absent' +// .parseExpression +// .assertError(ROSETTA_ABSENT_EXPRESSION, null, "Expected an optional value, but got a list with 1 to 5 items instead.") + } + + @Test + def void testCountTypeInference() { + val positiveInt = intWithNoMeta(Optional.empty, Optional.of(BigInteger.ZERO), Optional.empty) + 'empty count'.assertIsValidWithType(positiveInt, false); + '42 count'.assertIsValidWithType(positiveInt, false); + '[1, 2, 3] count'.assertIsValidWithType(positiveInt, false); + '(if True then empty else [1, 2, 3]) count'.assertIsValidWithType(positiveInt, false); + } + + @Test + def void testOnlyExistsTypeInference() { + val context = ''' + namespace test + + type A: + x int (0..1) + y number (0..3) + z boolean (0..*) + + condition C: + x only exists and (x, y) only exists + '''.parseRosettaWithNoIssues; + + (context.elements.get(0) as Data).conditions.head.expression as LogicalOperation => [ + left.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + right.assertIsValidWithType(BOOLEAN_WITH_NO_META, false) + ] + 'a -> x only exists'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false, #[context], #['a A (1..1)']) + '(a -> x, a -> y) only exists'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false, #[context], #['a A (1..1)']) + } + + @Test + def void testOnlyExistsTypeChecking() { + val model = ''' + namespace test + + type Foo: + bar int (1..1) + baz boolean (0..1) + + condition X: + baz only exists + + type A: + x Foo (0..1) + y number (0..3) + z boolean (0..*) + + condition C1: + (x -> baz, y) only exists and (y, x -> baz) only exists + condition C2: + (x, x) only exists and (x -> baz, x -> baz) only exists + + func Test: + inputs: + a A (1..1) + foo Foo (1..1) + b A (2..3) + c A (0..1) + output: result boolean (0..*) + add result: + b -> x only exists + add result: + c only exists + add result: + (a -> x -> baz, a -> x) only exists + add result: + foo -> baz only exists + '''.parseRosetta; + + (model.elements.get(0) as Data).conditions.head.expression + .assertError(ROSETTA_SYMBOL_REFERENCE, null, "Operator `only exists` is not supported for type Foo. All attributes of input type should be optional"); + + (model.elements.get(1) as Data).conditions => [ + get(0).expression as LogicalOperation => [ + left.assertError(ROSETTA_SYMBOL_REFERENCE, null, "All parent paths must be equal") + right.assertError(ROSETTA_SYMBOL_REFERENCE, null, "All parent paths must be equal") + ] + get(1).expression as LogicalOperation => [ + left.assertError(ROSETTA_ONLY_EXISTS_EXPRESSION, null, "Duplicate attribute") + right.assertError(ROSETTA_ONLY_EXISTS_EXPRESSION, null, "Duplicate attribute") + ] + ] + + (model.elements.get(2) as Function).operations => [ + get(0).expression.assertWarning(ROSETTA_SYMBOL_REFERENCE, null, "Expecting single cardinality. The `only exists` operator requires a single cardinality input") + get(1).expression.assertError(ROSETTA_ONLY_EXISTS_EXPRESSION, null, "Object must have a parent object") + get(2).expression.assertError(ROSETTA_SYMBOL_REFERENCE, null, "All parent paths must be equal") + get(3).expression.assertError(ROSETTA_SYMBOL_REFERENCE, null, "Operator `only exists` is not supported for type Foo. All attributes of input type should be optional") + ] + } + + @Test + def void testOnlyElementTypeInference() { + '(if True then 0 else [1, 2]) only-element'.assertIsValidWithType(intWithNoMeta(1, "0", "2"), false); + '(if True then empty else [True, False]) only-element'.assertIsValidWithType(BOOLEAN_WITH_NO_META, false); + '(if True then 0 else [1, 2, 3, 42.0]) only-element'.assertIsValidWithType(numberWithNoMeta(3, 1, "0", "42.0"), false); + } + + @Test + def void testOnlyElementTypeChecking() { + // TODO +// 'empty only-element' +// .parseExpression +// .assertWarning(ROSETTA_ONLY_ELEMENT, null, "Expected a list with 1 to 2 items, but got an empty value instead.") +// '42 only-element' +// .parseExpression +// .assertWarning(ROSETTA_ONLY_ELEMENT, null, "Expected a list with 1 to 2 items, but got a single value instead.") +// '[1, 2] only-element' +// .parseExpression +// .assertWarning(ROSETTA_ONLY_ELEMENT, null, "Expected a list with 1 to 2 items, but got a list with 2 items instead.") +// '(if True then empty else 42) only-element' +// .parseExpression +// .assertWarning(ROSETTA_ONLY_ELEMENT, null, "Expected a list with 1 to 2 items, but got an optional value instead.") + } + + @Test + def void testTypeAliasJoin() { + val model = ''' + namespace test + + typeAlias maxNString(n int): string(minLength: 1, maxLength: n) + typeAlias max3String: maxNString(n: 3) + typeAlias max4String: maxNString(n: 4) + + func Test: + inputs: + s1 max3String (1..1) + s2 max4String (1..1) + s3 maxNString(n: 4) (1..1) + output: result string (0..*) + add result: if True then s1 else s2 + add result: if True then s2 else s2 + add result: if True then s2 else s3 + '''.parseRosettaWithNoIssues + model.elements.last as Function => [ + val max4String = inputs.get(1).typeCall.typeCallToRType.withNoMeta + val maxNString = inputs.get(2).typeCall.typeCallToRType.withNoMeta + + operations => [ + get(0).expression.assertIsValidWithType(maxNString, false) + get(1).expression.assertIsValidWithType(max4String, false) + get(2).expression.assertIsValidWithType(maxNString, false) + ] + ] + } + + @Test + def void shouldCoerceStringToParameterizedString() { + ''' + namespace test + + func Test: + inputs: str string (1..1) + output: max3String string(minLength: 1, maxLength: 3) (1..1) + set max3String: str + '''.parseRosettaWithNoIssues + } + + @Test + def void shouldCoerceParameterizedStringToString() { + ''' + namespace test + + func Test: + inputs: max3String string(minLength: 1, maxLength: 3) (1..1) + output: str string (1..1) + set str: max3String + '''.parseRosettaWithNoIssues + } + + @Test + def void shouldCoerceStringToStringTypeAlias() { + ''' + namespace test + + typeAlias Max3String: string(minLength: 1, maxLength: 3) + + func Test: + inputs: str string (1..1) + output: max3String Max3String (1..1) + set max3String: str + '''.parseRosettaWithNoIssues + } + + @Test + def void shouldCoerceStringTypeAliasToString() { + ''' + namespace test + + typeAlias Max3String: string(minLength: 1, maxLength: 3) + + func Test: + inputs: max3String Max3String (1..1) + output: str string (1..1) + set str: max3String + '''.parseRosettaWithNoIssues + } + + @Test + def void shouldCoerceDifferentParameterizedStrings() { + ''' + namespace test + + func Test: + inputs: max10String string(minLength: 1, maxLength: 10) (1..1) + output: max3String string(minLength: 1, maxLength: 3) (1..1) + set max3String: max10String + '''.parseRosettaWithNoIssues + } + + @Test + def void shouldCoerceDifferentTypeAliases() { + ''' + namespace test + + typeAlias Max10String: string(minLength: 1, maxLength: 10) + typeAlias Max3String: string(minLength: 1, maxLength: 3) + + func Test: + inputs: max10String Max10String (1..1) + output: max3String Max3String (1..1) + set max3String: max10String + '''.parseRosettaWithNoIssues + } @Test def void testAttributeSameNameAsAnnotationTest() { @@ -66,7 +695,7 @@ class RosettaTypeProviderTest { inputs: foo Foo (1..1) output: is_event boolean (1..1) set is_event: - foo -> iBar = 4.0 + foo -> iBar any = 4.0 '''.parseRosettaWithNoErrors.elements.filter(Function) val allNumber = funcs.filter[name == "Qualify_AllNumber"].head diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/types/RosettaTypingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/types/RosettaTypingTest.xtend deleted file mode 100644 index 0c2b221a3..000000000 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/types/RosettaTypingTest.xtend +++ /dev/null @@ -1,728 +0,0 @@ -package com.regnosys.rosetta.types - -import com.regnosys.rosetta.tests.RosettaInjectorProvider -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith - -import com.regnosys.rosetta.tests.util.ModelHelper -import com.regnosys.rosetta.rosetta.simple.Function -import com.regnosys.rosetta.rosetta.expression.RosettaConditionalExpression -import com.regnosys.rosetta.rosetta.simple.Data -import com.regnosys.rosetta.rosetta.expression.ArithmeticOperation -import com.regnosys.rosetta.rosetta.RosettaEnumeration -import com.regnosys.rosetta.rosetta.expression.LogicalOperation -import com.regnosys.rosetta.rosetta.expression.MapOperation -import com.regnosys.rosetta.types.builtin.RBuiltinTypeService -import java.util.Optional -import java.math.BigDecimal -import com.regnosys.rosetta.tests.util.ExpressionValidationHelper -import com.regnosys.rosetta.tests.util.ExpressionParser -import javax.inject.Inject -import java.math.BigInteger - -@ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) -class RosettaTypingTest { - @Inject - extension TypeFactory - - @Inject - extension TypeSystem - - @Inject - extension TypeTestUtil - - @Inject - extension ExpressionParser - - @Inject - extension ExpressionValidationHelper - - @Inject - extension ModelHelper - - @Inject - extension RBuiltinTypeService - - @Inject - extension RObjectFactory - - @Test - def void testLiteralTypeInference() { - 'False'.assertIsValidWithType(singleBoolean) - '"Some string"'.assertIsValidWithType(singleString(11, 11)) - '3.14'.assertIsValidWithType(singleNumber(3, 2, "3.14", "3.14")) - '1'.assertIsValidWithType(singleInt(1, "1", "1")) - 'empty'.assertIsValidWithType(emptyNothing) - } - - @Test - def void testVariableTypeInference() { - val model = ''' - namespace test - - func TestVar: - inputs: - a int (2..4) - b boolean (1..1) - output: result number (1..4) - alias c: if b then 42 else -1/12 - add result: - if b then a else c - - func TestImplicitVar: - output: result int (3..3) - add result: - [1, 2, 3] extract item + 1 - '''.parseRosettaWithNoIssues - model.elements.get(0) as Function => [operations.head.expression as RosettaConditionalExpression => [ - ^if.assertHasType(singleBoolean) - ifthen.assertHasType(createListType(UNCONSTRAINED_INT, 2, 4)) - elsethen.assertHasType(singleUnconstrainedNumber) - ]]; - - model.elements.get(1) as Function => [operations.head.expression as MapOperation => [ - function.body as ArithmeticOperation => [ - left.assertHasType(singleInt(1, "1", "3")) - ] - ]]; - } - - // TODO: test auxiliary functions - @Test - def void testSubtyping() { - val t1 = createListType(UNCONSTRAINED_INT, 1, 3); - val t2 = createListType(UNCONSTRAINED_NUMBER, 1, 5); - t1.assertListSubtype(t2) - - val t3 = createListType(BOOLEAN, 1, 3); - t1.assertNotListSubtype(t3); - - val t4 = createListType(UNCONSTRAINED_INT, 1, 2); - t1.assertNotListSubtype(t4); - } - - @Test - def void testLogicalOperationTypeInference() { - 'True or False'.assertIsValidWithType(singleBoolean) - 'True and False'.assertIsValidWithType(singleBoolean) - - // Test loosened version - '(if False then True else [True, False]) and False'.assertIsValidWithType(singleBoolean) - } - - @Test - def void testLogicalOperationTypeChecking() { - '1 or False' - .parseExpression - .assertError(null, "Expected type `boolean`, but got `int` instead.") - 'True or 3.14' - .parseExpression - .assertError(null, "Expected type `boolean`, but got `number` instead.") - } - - @Test - def void testEqualityOperationTypeInference() { - '(if True then [1] else [2, 3]) = (if False then [4.0, 5] else [6.0, 7, 8])' - .assertIsValidWithType(singleBoolean) - '(if True then [1] else [2, 3]) <> (if False then [4.0, 5] else [6.0, 7, 8])' - .assertIsValidWithType(singleBoolean) - '[1, 3] all = 5.0'.assertIsValidWithType(singleBoolean) - 'empty all <> 5.0'.assertIsValidWithType(singleBoolean) - '[1, 3] any = 5.0'.assertIsValidWithType(singleBoolean) - - 'a = 1' - .parseExpression(#['a int (0..1)']) - .assertNoIssues - - val model = ''' - namespace test - - type Foo: - packageTransactionPriceNotation int (0..1) - - condition C: - packageTransactionPriceNotation = 1 - '''.parseRosettaWithNoIssues - val expression = (model.elements.last as Data).conditions.head.expression; - expression.assertHasType(singleBoolean); - - // Test loosened version - '[1, 3] any = (if False then 1 else [2, 3])'.assertIsValidWithType(singleBoolean) - } - - @Test - def void testEqualityOperationTypeChecking() { - '1 = True' - .parseExpression - .assertError(null, "Types `int` and `boolean` are not comparable.") - 'empty = True' - .parseExpression - .assertError(null, "Cannot compare an empty value to a single value, as they cannot be of the same length. Perhaps you forgot to write `all` or `any` in front of the operator?") - '[1, 2] = [3, 4, 5]' - .parseExpression - .assertError(null, "Cannot compare a list with 2 items to a list with 3 items, as they cannot be of the same length.") - '[1, 2] <> [True, False, False]' - .parseExpression - .assertError(null, "Types `int` and `boolean` are not comparable.") - - '[1, 2] all = empty' - .parseExpression - .assertError(null, "Expected a single value, but got an empty value instead.") - 'empty any = empty' - .parseExpression - .assertError(null, "Expected a single value, but got an empty value instead.") - '[1, 2] all = [1, 2]' - .parseExpression - .assertError(null, "Expected a single value, but got a list with 2 items instead.") - '5 any <> [1, 2]' - .parseExpression - .assertError(null, "Expected a single value, but got a list with 2 items instead. Perhaps you meant to swap the left and right operands?") - '[3.0] any <> 5' - .parseExpression - .assertError(null, "The cardinality operator `any` is redundant when comparing two single values.") - } - - // TODO: test arithmetic and comparisons with dates/times/etc - @Test - def void testArithmeticOperationTypeInference() { - '3 + 4'.assertIsValidWithType(singleInt(Optional.empty, Optional.of(BigInteger.valueOf(7)), Optional.of(BigInteger.valueOf(7)))) - '3.0 + 4'.assertIsValidWithType(singleNumber(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("7")), Optional.of(new BigDecimal("7")), Optional.empty)) - '3 + 4.0'.assertIsValidWithType(singleNumber(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("7")), Optional.of(new BigDecimal("7")), Optional.empty)) - '3.0 + 4.0'.assertIsValidWithType(singleNumber(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("7")), Optional.of(new BigDecimal("7")), Optional.empty)) - '"ab" + "cd"'.assertIsValidWithType(singleString(4, 4)) - - '3 - 4'.assertIsValidWithType(singleInt(Optional.empty, Optional.of(BigInteger.valueOf(-1)), Optional.of(BigInteger.valueOf(-1)))) - '3 - 4.0'.assertIsValidWithType(singleNumber(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("-1")), Optional.of(new BigDecimal("-1")), Optional.empty)) - - '3 * 4'.assertIsValidWithType(singleInt(Optional.empty, Optional.of(BigInteger.valueOf(12)), Optional.of(BigInteger.valueOf(12)))) - '3.0 * 4'.assertIsValidWithType(singleNumber(Optional.empty, Optional.of(1), Optional.of(new BigDecimal("12")), Optional.of(new BigDecimal("12")), Optional.empty)) - - '3 / 4'.assertIsValidWithType(singleUnconstrainedNumber) - - // Test loosened version - '(if False then 2 else [3, 4]) + 5'.assertIsValidWithType(singleInt(Optional.empty, Optional.of(BigInteger.valueOf(7)), Optional.of(BigInteger.valueOf(9)))) - } - - @Test - def void testArithemticOperationTypeChecking() { - '[1, 2] + 3' - .parseExpression - .assertError(null, "Expected a single value, but got a list with 2 items instead.") - 'empty - 3' - .parseExpression - .assertError(null, "Expected a single value, but got an empty value instead.") - '1.5 * False' - .parseExpression - .assertError(null, "Expected type `number`, but got `boolean` instead.") - '"ab" + 3' - .parseExpression - .assertError(null, "Expected arguments to be either both a `string` or both a `number`, but got `string` and `int` instead.") - } - - @Test - def void testComparisonOperationTypeInference() { - '1 < 2'.assertIsValidWithType(singleBoolean) - '3 > 3.14'.assertIsValidWithType(singleBoolean) - '-5.1 <= 42'.assertIsValidWithType(singleBoolean) - '-3.14 >= 3.14'.assertIsValidWithType(singleBoolean) - - '[1, 2] any < 5'.assertIsValidWithType(singleBoolean) - 'empty all > 5'.assertIsValidWithType(singleBoolean) - - // Test loosened version - '(if False then 2 else [3, 4]) < 5'.assertIsValidWithType(singleBoolean) - '[1, 2] any < (if False then 5 else [3, 4])'.assertIsValidWithType(singleBoolean) - } - - @Test - def void testComparisonOperationTypeChecking() { - // TODO: support date, zonedDateTime and `time`? - '[1, 2] < 3' - .parseExpression - .assertError(null, "Expected a single value, but got a list with 2 items instead.") - 'empty > 3' - .parseExpression - .assertError(null, "Expected a single value, but got an empty value instead.") - '1.5 <= False' - .parseExpression - .assertError(null, "Expected type `number`, but got `boolean` instead.") - - '[1, 2] all >= empty' - .parseExpression - .assertError(null, "Expected a single value, but got an empty value instead.") - 'empty any < empty' - .parseExpression - .assertError(null, "Expected a single value, but got an empty value instead.") - '[1, 2] all > [1, 2]' - .parseExpression - .assertError(null, "Expected a single value, but got a list with 2 items instead.") - '5 any <= [1, 2]' - .parseExpression - .assertError(null, "Expected a single value, but got a list with 2 items instead. Perhaps you meant to swap the left and right operands?") - '5 all >= 1' - .parseExpression - .assertError(null, "The cardinality operator `all` is redundant when comparing two single values.") - } - - @Test - def void testConditionalExpressionTypeInference() { - 'if True then [1, 2] else [3.0, 4.0, 5.0, 6.0]'.assertIsValidWithType(createListType(constrainedNumber(2, 1, "1", "6"), 2, 4)); - } - - @Test - def void testConditionalExpressionTypeChecking() { - 'if [True, False] then 1 else 2' - .parseExpression - .assertError(null, "Expected a single value, but got a list with 2 items instead.") - 'if empty then 1 else 2' - .parseExpression - .assertError(null, "Expected a single value, but got an empty value instead.") - 'if True then 1 else False' - .parseExpression - .assertError(null, "Types `int` and `boolean` do not have a common supertype.") - 'if True then [1, 2, 3] else [False, True]' - .parseExpression - .assertError(null, "Types `int` and `boolean` do not have a common supertype.") - } - - @Test - def void testListLiteralTypeInference() { - '[]'.assertIsValidWithType(emptyNothing); - '[2, 4.5, 7, -3.14]'.assertIsValidWithType(createListType(constrainedNumber(3, 2, "-3.14", "7"), 4, 4)); - '[2, [1, 2], -3.14]'.assertIsValidWithType(createListType(constrainedNumber(3, 2, "-3.14", "2"), 4, 4)); - } - - @Test - def void testListLiteralTypeChecking() { - '[1, True]' - .parseExpression - .assertError(null, "Elements do not have a common supertype: `int`, `boolean`.") - } - - @Test - def void testFunctionCallTypeInference() { - val model = ''' - namespace test - - func SomeFunc: - inputs: - a int (1..1) - b boolean (2..4) - output: result number (3..5) - add result: - [1.0, 2.0, 3.0] - - func Test: - output: result number (3..5) - add result: - SomeFunc(42, [True, False, True]) - '''.parseRosettaWithNoIssues - val expression = (model.elements.last as Function).operations.head.expression; - expression.assertHasType(createListType(UNCONSTRAINED_NUMBER, 3, 5)); - } - - @Test - def void testFunctionCallTypeChecking() { - val model = ''' - namespace test - - func SomeFunc: - inputs: - a int (1..1) - b boolean (2..4) - output: result int (1..1) - set result: - 42 - - func TestParamNumber: - output: result int (1..1) - set result: - SomeFunc(1, [False, True], True) - - func TestParamType: - output: result int (1..1) - set result: - SomeFunc(1, [2, 3]) - - func TestParamCardinality: - output: result int (1..1) - set result: - SomeFunc(1, [False, True, False, False, True]) - '''.parseRosetta - - val expr1 = (model.elements.get(1) as Function).operations.head.expression; - expr1.assertError(null, "Expected 2 arguments, but got 3 instead."); - - val expr2 = (model.elements.get(2) as Function).operations.head.expression; - expr2.assertError(null, "Expected type `boolean`, but got `int` instead."); - - val expr3 = (model.elements.get(3) as Function).operations.head.expression; - expr3.assertError(null, "Expected a list with 2 to 4 items, but got a list with 5 items instead."); - } - - @Test - def void testProjectionTypeInference() { - val model = ''' - namespace test - - type A: - x int (1..1) - y number (0..*) - z boolean (3..7) - - func Test1: - inputs: - a A (1..1) - output: result int (1..1) - set result: - a -> x - - func Test2: - inputs: - a A (1..1) - output: result number (0..*) - add result: - a -> y - - func Test3: - inputs: - a A (1..1) - output: result boolean (3..7) - add result: - a -> z - - func Test4: - inputs: - a A (2..5) - output: result int (2..5) - add result: - a -> x - - func Test5: - inputs: - a A (2..5) - output: result number (0..*) - add result: - a -> y - - func Test6: - inputs: - a A (2..5) - output: result boolean (6..35) - add result: - a -> z - '''.parseRosettaWithNoIssues - val expr1 = (model.elements.get(1) as Function).operations.head.expression; - expr1.assertHasType(createListType(UNCONSTRAINED_INT, 1, 1)); - - val expr2 = (model.elements.get(2) as Function).operations.head.expression; - expr2.assertHasType(createListType(UNCONSTRAINED_NUMBER, 0)); - - val expr3 = (model.elements.get(3) as Function).operations.head.expression; - expr3.assertHasType(createListType(BOOLEAN, 3, 7)); - - val expr4 = (model.elements.get(4) as Function).operations.head.expression; - expr4.assertHasType(createListType(UNCONSTRAINED_INT, 2, 5)); - - val expr5 = (model.elements.get(5) as Function).operations.head.expression; - expr5.assertHasType(createListType(UNCONSTRAINED_NUMBER, 0)); - - val expr6 = (model.elements.get(6) as Function).operations.head.expression; - expr6.assertHasType(createListType(BOOLEAN, 6, 35)); - } - - @Test - def void testEnumTypeInference() { - val model = ''' - namespace test - - enum A: - V1 - V2 - - func Test: - output: result A (1..1) - set result: - A -> V1 - '''.parseRosettaWithNoIssues - val A = (model.elements.get(0) as RosettaEnumeration).buildREnumType; - val expr1 = (model.elements.get(1) as Function).operations.head.expression; - expr1.assertHasType(createListType(A, 1, 1)); - } - - @Test - def void testExistsTypeInference() { - '(if True then [] else 5) exists'.assertIsValidWithType(singleBoolean); - '(if True then [] else [1, 2, 3]) exists'.assertIsValidWithType(singleBoolean); - } - - @Test - def void testExistsTypeChecking() { - 'empty exists' - .parseExpression - .assertError(null, "Expected an optional value, but got an empty value instead.") - '42 exists' - .parseExpression - .assertError(null, "Expected an optional value, but got a single value instead.") - '(if True then 42 else [1, 2, 3, 4, 5]) exists' - .parseExpression - .assertError(null, "Expected an optional value, but got a list with 1 to 5 items instead.") - } - - @Test - def void testAbsentTypeInference() { - '(if True then [] else 5) is absent'.assertIsValidWithType(singleBoolean); - '(if True then [] else [1, 2, 3]) is absent'.assertIsValidWithType(singleBoolean); - } - - @Test - def void testAbsentTypeChecking() { - 'empty is absent' - .parseExpression - .assertError(null, "Expected an optional value, but got an empty value instead.") - '42 is absent' - .parseExpression - .assertError(null, "Expected an optional value, but got a single value instead.") - '(if True then 42 else [1, 2, 3, 4, 5]) is absent' - .parseExpression - .assertError(null, "Expected an optional value, but got a list with 1 to 5 items instead.") - } - - @Test - def void testCountTypeInference() { - val singlePositiveInt = singleInt(Optional.empty, Optional.of(BigInteger.ZERO), Optional.empty) - 'empty count'.assertIsValidWithType(singlePositiveInt); - '42 count'.assertIsValidWithType(singlePositiveInt); - '[1, 2, 3] count'.assertIsValidWithType(singlePositiveInt); - '(if True then empty else [1, 2, 3]) count'.assertIsValidWithType(singlePositiveInt); - } - - @Test - def void testOnlyExistsTypeInference() { - val model = ''' - namespace test - - type A: - x int (0..1) - y number (0..3) - z boolean (0..*) - - condition C: - x only exists and (x, y) only exists - - func Test: - inputs: - a A (1..1) - output: result boolean (1..1) - set result: - a -> x only exists and (a -> x, a -> y) only exists - '''.parseRosettaWithNoIssues; - - (model.elements.get(0) as Data).conditions.head.expression as LogicalOperation => [ - left.assertHasType(singleBoolean) - right.assertHasType(singleBoolean) - ] - (model.elements.get(1) as Function).operations.head.expression as LogicalOperation => [ - left.assertHasType(singleBoolean) - right.assertHasType(singleBoolean) - ] - } - - @Test - def void testOnlyExistsTypeChecking() { - val model = ''' - namespace test - - type Foo: - bar int (1..1) - baz boolean (0..1) - - condition X: - baz only exists - - type A: - x Foo (0..1) - y number (0..3) - z boolean (0..*) - - condition C1: - (x -> baz, y) only exists and (y, x -> baz) only exists - condition C2: - (x, x) only exists and (x -> baz, x -> baz) only exists - - func Test: - inputs: - a A (1..1) - foo Foo (1..1) - b A (2..3) - c A (0..1) - output: result boolean (0..*) - add result: - b -> x only exists - add result: - c only exists - add result: - (a -> x -> baz, a -> x) only exists - add result: - foo -> baz only exists - '''.parseRosetta; - - (model.elements.get(0) as Data).conditions.head.expression - .assertError(null, "The `only exists` operator is not applicable to instances of `Foo`."); - - (model.elements.get(1) as Data).conditions => [ - get(0).expression as LogicalOperation => [ - left.assertError(null, "All parent paths must be equal.") - right.assertError(null, "All parent paths must be equal.") - ] - get(1).expression as LogicalOperation => [ - left.assertError(null, "Duplicate attribute.") - right.assertError(null, "Duplicate attribute.") - ] - ] - - (model.elements.get(2) as Function).operations => [ - get(0).expression.assertError(null, "Expected a single value, but got a list with 2 to 3 items instead.") - get(1).expression.assertError(null, "Object must have a parent object.") - get(2).expression.assertError(null, "All parent paths must be equal.") - get(3).expression.assertError(null, "The `only exists` operator is not applicable to instances of `Foo`.") - ] - } - - @Test - def void testOnlyElementTypeInference() { - '(if True then 0 else [1, 2]) only-element'.assertIsValidWithType(createListType(constrainedInt(1, "0", "2"), 0, 1)); - '(if True then empty else [True, False]) only-element'.assertIsValidWithType(createListType(BOOLEAN, 0, 1)); - '(if True then 0 else [1, 2, 3, 42.0]) only-element'.assertIsValidWithType(createListType(constrainedNumber(3, 1, "0", "42.0"), 0, 1)); - } - - @Test - def void testOnlyElementTypeChecking() { - 'empty only-element' - .parseExpression - .assertWarning(null, "Expected a list with 1 to 2 items, but got an empty value instead.") - '42 only-element' - .parseExpression - .assertWarning(null, "Expected a list with 1 to 2 items, but got a single value instead.") - '[1, 2] only-element' - .parseExpression - .assertWarning(null, "Expected a list with 1 to 2 items, but got a list with 2 items instead.") - '(if True then empty else 42) only-element' - .parseExpression - .assertWarning(null, "Expected a list with 1 to 2 items, but got an optional value instead.") - } - - @Test - def void testTypeAliasJoin() { - val model = ''' - namespace test - - typeAlias maxNString(n int): string(minLength: 1, maxLength: n) - typeAlias max3String: maxNString(n: 3) - typeAlias max4String: maxNString(n: 4) - - func Test: - inputs: - s1 max3String (1..1) - s2 max4String (1..1) - s3 maxNString(n: 4) (1..1) - output: result string (0..*) - add result: if True then s1 else s2 - add result: if True then s2 else s2 - add result: if True then s2 else s3 - '''.parseRosettaWithNoIssues - model.elements.last as Function => [ - val max4String = createListType(inputs.get(1).typeCall.typeCallToRType, single) - val maxNString = createListType(inputs.get(2).typeCall.typeCallToRType, single) - - operations => [ - get(0).expression.assertHasType(maxNString) - get(1).expression.assertHasType(max4String) - get(2).expression.assertHasType(maxNString) - ] - ] - } - - @Test - def void shouldCoerceStringToParameterizedString() { - ''' - namespace test - - func Test: - inputs: str string (1..1) - output: max3String string(minLength: 1, maxLength: 3) (1..1) - set max3String: str - '''.parseRosettaWithNoIssues - } - - @Test - def void shouldCoerceParameterizedStringToString() { - ''' - namespace test - - func Test: - inputs: max3String string(minLength: 1, maxLength: 3) (1..1) - output: str string (1..1) - set str: max3String - '''.parseRosettaWithNoIssues - } - - @Test - def void shouldCoerceStringToStringTypeAlias() { - ''' - namespace test - - typeAlias Max3String: string(minLength: 1, maxLength: 3) - - func Test: - inputs: str string (1..1) - output: max3String Max3String (1..1) - set max3String: str - '''.parseRosettaWithNoIssues - } - - @Test - def void shouldCoerceStringTypeAliasToString() { - ''' - namespace test - - typeAlias Max3String: string(minLength: 1, maxLength: 3) - - func Test: - inputs: max3String Max3String (1..1) - output: str string (1..1) - set str: max3String - '''.parseRosettaWithNoIssues - } - - @Test - def void shouldCoerceDifferentParameterizedStrings() { - ''' - namespace test - - func Test: - inputs: max10String string(minLength: 1, maxLength: 10) (1..1) - output: max3String string(minLength: 1, maxLength: 3) (1..1) - set max3String: max10String - '''.parseRosettaWithNoIssues - } - - @Test - def void shouldCoerceDifferentTypeAliases() { - ''' - namespace test - - typeAlias Max10String: string(minLength: 1, maxLength: 10) - typeAlias Max3String: string(minLength: 1, maxLength: 3) - - func Test: - inputs: max10String Max10String (1..1) - output: max3String Max3String (1..1) - set max3String: max10String - '''.parseRosettaWithNoIssues - } - -} diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/types/SubtypeRelationTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/types/SubtypeRelationTest.xtend index fdc5b2bbf..bfeb811c5 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/types/SubtypeRelationTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/types/SubtypeRelationTest.xtend @@ -3,7 +3,7 @@ package com.regnosys.rosetta.types import org.eclipse.xtext.testing.extensions.InjectionExtension import org.junit.jupiter.api.^extension.ExtendWith import com.regnosys.rosetta.rosetta.simple.Data -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.eclipse.xtext.testing.InjectWith import javax.inject.Inject import com.regnosys.rosetta.tests.util.ModelHelper @@ -16,10 +16,10 @@ import com.regnosys.rosetta.rosetta.RosettaEnumeration import com.regnosys.rosetta.tests.util.ExpressionParser import com.regnosys.rosetta.types.builtin.RBuiltinTypeService import com.regnosys.rosetta.types.builtin.RStringType -import static extension com.regnosys.rosetta.types.RMetaAnnotatedType.withEmptyMeta +import static extension com.regnosys.rosetta.types.RMetaAnnotatedType.withNoMeta @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class SubtypeRelationTest { @Inject extension SubtypeRelation @Inject extension ModelHelper @@ -62,7 +62,7 @@ class SubtypeRelationTest { val joined = fieldBType.join(fieldCType) - assertEquals(fieldA.withEmptyMeta, joined) + assertEquals(fieldA.withNoMeta, joined) } @Test diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/types/TypeTestUtil.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/types/TypeTestUtil.xtend deleted file mode 100644 index 0929166e4..000000000 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/types/TypeTestUtil.xtend +++ /dev/null @@ -1,46 +0,0 @@ -package com.regnosys.rosetta.types - -import com.regnosys.rosetta.rosetta.expression.RosettaExpression - -import static org.junit.jupiter.api.Assertions.* -import com.regnosys.rosetta.tests.util.ExpressionValidationHelper -import com.regnosys.rosetta.tests.util.ExpressionParser -import javax.inject.Inject - -class TypeTestUtil { - @Inject - extension TypeSystem - - @Inject - extension ExpressionValidationHelper - - @Inject - extension ExpressionParser - - def RListType getType(CharSequence expr) { - return getType(expr.parseExpression); - } - def RListType getType(RosettaExpression expr) { - val res = expr.inferType - assertNotNull(res); - return res; - } - - def void assertIsValidWithType(CharSequence expr, RListType expected) { - val e = expr.parseExpression - e.assertNoIssues - e.assertHasType(expected) - } - - def void assertHasType(RosettaExpression e, RListType expected) { - val t = e.type - assertEquals(expected, t) - } - - def void assertListSubtype(RListType a, RListType b) { - assertTrue(isListSubtypeOf(a, b)) - } - def void assertNotListSubtype(RListType a, RListType b) { - assertFalse(isListSubtypeOf(a, b)) - } -} \ No newline at end of file diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/utils/RosettaSimpleSystemSolverTest.java b/rosetta-testing/src/test/java/com/regnosys/rosetta/utils/RosettaSimpleSystemSolverTest.java index 61e79f902..f1d875078 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/utils/RosettaSimpleSystemSolverTest.java +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/utils/RosettaSimpleSystemSolverTest.java @@ -37,14 +37,14 @@ import com.regnosys.rosetta.interpreter.RosettaInterpreterContext; import com.regnosys.rosetta.interpreter.RosettaValue; import com.regnosys.rosetta.rosetta.expression.RosettaExpression; -import com.regnosys.rosetta.tests.RosettaInjectorProvider; +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider; import com.regnosys.rosetta.tests.util.ExpressionParser; import com.regnosys.rosetta.tests.util.RosettaValueHelper; import com.regnosys.rosetta.rosetta.simple.Attribute; import com.regnosys.rosetta.rosetta.RosettaSymbol; @ExtendWith(InjectionExtension.class) -@InjectWith(RosettaInjectorProvider.class) +@InjectWith(RosettaTestInjectorProvider.class) public class RosettaSimpleSystemSolverTest { @Inject private RosettaSimpleSystemSolver solver; diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/AttributeValidatorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/AttributeValidatorTest.xtend index afde772f6..70eebef87 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/AttributeValidatorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/AttributeValidatorTest.xtend @@ -11,9 +11,10 @@ import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.* import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* import javax.inject.Inject +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider @ExtendWith(InjectionExtension) -@InjectWith(MyRosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class AttributeValidatorTest implements RosettaIssueCodes { @Inject extension ValidationTestHelper @@ -85,7 +86,7 @@ class AttributeValidatorTest implements RosettaIssueCodes { type Bar extends Foo: override otherAttr number (0..1) '''.parseRosetta - .assertError(ATTRIBUTE, null, "Attribute otherAttr does not exist") + .assertError(ATTRIBUTE, null, "Attribute otherAttr does not exist in supertype") } @Test diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/ChoiceValidatorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/ChoiceValidatorTest.xtend index 2679f9ed1..2d7113127 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/ChoiceValidatorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/ChoiceValidatorTest.xtend @@ -10,9 +10,10 @@ import org.junit.jupiter.api.^extension.ExtendWith import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.* import javax.inject.Inject +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider @ExtendWith(InjectionExtension) -@InjectWith(MyRosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class ChoiceValidatorTest implements RosettaIssueCodes { @Inject extension ValidationTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/EnumValidatorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/EnumValidatorTest.xtend index 61952fcb3..ffb6bb386 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/EnumValidatorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/EnumValidatorTest.xtend @@ -10,9 +10,10 @@ import org.junit.jupiter.api.^extension.ExtendWith import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* import javax.inject.Inject +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider @ExtendWith(InjectionExtension) -@InjectWith(MyRosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class EnumValidatorTest implements RosettaIssueCodes { @Inject extension ValidationTestHelper diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaValidatorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaValidatorTest.xtend index 29c197b12..dcd28147f 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaValidatorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaValidatorTest.xtend @@ -3,16 +3,12 @@ */ package com.regnosys.rosetta.validation -import com.regnosys.rosetta.RosettaRuntimeModule -import com.regnosys.rosetta.rosetta.simple.Data -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import com.regnosys.rosetta.tests.util.ModelHelper import org.eclipse.xtext.diagnostics.Diagnostic -import org.eclipse.xtext.service.SingletonBinding import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.eclipse.xtext.validation.Check import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.^extension.ExtendWith @@ -24,13 +20,24 @@ import javax.inject.Inject import com.regnosys.rosetta.tests.util.ExpressionParser @ExtendWith(InjectionExtension) -@InjectWith(MyRosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class RosettaValidatorTest implements RosettaIssueCodes { @Inject extension ValidationTestHelper @Inject extension ModelHelper @Inject extension ExpressionParser + @Test + def void testConditionShouldBeSingleCardinality() { + val model = ''' + type Foo: + condition C: + [True, False] + '''.parseRosetta + + model.assertWarning(CONDITION, null, "Expecting single cardinality. A condition should be single cardinality") + } + @Test def void testOnlyExistsOnMetaIsNotValidOnSymbolReferences() { val model = ''' @@ -240,7 +247,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { '''.parseRosetta model.assertError(IMPORT, null, - '"as" statement can only be used with wildcard import' + '"as" statement can only be used with wildcard imports' ) } @@ -260,7 +267,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { def void testSwitchInputRecordTypesAreNotValid() { "someDate switch default \"someResult\"" .parseExpression(#["someDate date (1..1)"]) - .assertError(SWITCH_OPERATION, null, "Type `date` is not a valid switch argument type. Supported argument types are basic types, enumerations, and choice types.") + .assertError(SWITCH_OPERATION, null, "Operator `switch` is not supported for type date. Supported argument types are basic types, enumerations, and choice types") } @Test @@ -281,7 +288,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { default "someOtherValue" ''' .parseExpression(#[model], #["inEnum SomeEnum (1..*)"]) - .assertError(ROSETTA_EXPRESSION, null, "Input to switch must be single cardinality") + .assertWarning(ROSETTA_EXPRESSION, null, "Expecting single cardinality. The `switch` operator requires a single cardinality input") } @@ -356,7 +363,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { "inFoo switch default 42" .parseExpression(#[model], #["inFoo Foo (1..1)"]) - .assertError(SWITCH_OPERATION, null, "Type `Foo` is not a valid switch argument type. Supported argument types are basic types, enumerations, and choice types.") + .assertError(SWITCH_OPERATION, null, "Operator `switch` is not supported for type Foo. Supported argument types are basic types, enumerations, and choice types") } @Test @@ -533,7 +540,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { '''.parseRosetta model.assertError(ROSETTA_SYMBOL_REFERENCE, null, - "Invalid number of arguments. Expecting 1 but passed 0." + "Expected 1 argument, but got 0 instead" ) } @@ -727,8 +734,8 @@ class RosettaValidatorTest implements RosettaIssueCodes { ins then val '''.parseRosetta - model.assertError(OPERATION, null, - "Cardinality mismatch - cannot assign list to a single value.") + model.assertWarning(OPERATION, null, + "Expecting single cardinality. Cannot assign a list to a single value") } @Test @@ -1529,10 +1536,10 @@ class RosettaValidatorTest implements RosettaIssueCodes { inputs: a int (0..*) output: b int (0..*) add b: - a extract [Add] + a extract Add '''.parseRosetta model.assertError(ROSETTA_SYMBOL_REFERENCE, null, - "Expected 2 arguments, but got 0 instead.") + "Expected 2 arguments, but got 0 instead") } @Test @@ -1546,7 +1553,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { a() '''.parseRosetta model.assertError(ROSETTA_SYMBOL_REFERENCE, null, - "A variable may not be called.") + "A variable may not be called") } @Test @@ -1586,8 +1593,8 @@ class RosettaValidatorTest implements RosettaIssueCodes { condition A: *42 '''.parseRosetta - model.assertError(ROSETTA_IMPLICIT_VARIABLE, null, - "Expected type `number`, but got `Foo` instead.") + model.assertError(ARITHMETIC_OPERATION, null, + "Expected type `number`, but got `Foo` instead. Cannot use `Foo` with operator `*`") } @Test @@ -1601,8 +1608,8 @@ class RosettaValidatorTest implements RosettaIssueCodes { if id = True then id < 1 '''.parseRosetta - model.assertError(ROSETTA_CONDITIONAL_EXPRESSION, TYPE_ERROR, - "Incompatible types: cannot use operator '=' with int and boolean.") + model.assertError(EQUALITY_OPERATION, null, + "Types `int` and `boolean` are not comparable") } @Test @@ -1629,7 +1636,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { if id = True then id < 1 '''.parseRosetta - model.assertError(COMPARISON_OPERATION, null, "Incompatible types: cannot use operator '<' with boolean and int.") + model.assertError(COMPARISON_OPERATION, null, "Operator `<` is not supported for type boolean. Supported types are number, date and zonedDateTime") } @Test @@ -1648,7 +1655,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { set out: "not a Foo" '''.parseRosetta - model.assertError(OPERATION, TYPE_ERROR, "Expected type 'Foo' but was 'string'") + model.assertError(OPERATION, null, "Expected type `Foo`, but got `string` instead. Cannot assign `string` to output `out`") } @@ -1665,7 +1672,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { set out -> id: "not a boolean" '''.parseRosetta - model.assertError(OPERATION, TYPE_ERROR, "Expected type 'boolean' but was 'string'") + model.assertError(OPERATION, null, "Expected type `boolean`, but got `string` instead. Cannot assign `string` to output `id`") } @Test @@ -1686,7 +1693,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { set result -> attr: in1 as-key '''.parseRosetta - model.assertError(OPERATION, TYPE_ERROR, "Expected type 'WithKey' but was 'TypeToUse'") + model.assertError(OPERATION, null, "Expected type `WithKey`, but got `TypeToUse` instead. Cannot assign `TypeToUse` to output `attr`") } @Test @@ -1718,7 +1725,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { output: out string (0..1) set out: in0->other '''.parseRosetta - model.assertError(OPERATION, TYPE_ERROR, "Expected type 'string' but was 'int'") + model.assertError(OPERATION, null, "Expected type `string`, but got `int` instead. Cannot assign `int` to output `out`") } @Test @@ -1828,7 +1835,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { enum Foo: BAR '''.parseRosetta - model.assertError(ROSETTA_TYPE, DUPLICATE_ELEMENT_NAME, 'Duplicate element name') + model.assertError(ROSETTA_TYPE, DUPLICATE_ELEMENT_NAME, 'Duplicate element named \'Foo\'') } @Test @@ -2038,8 +2045,8 @@ class RosettaValidatorTest implements RosettaIssueCodes { Foo(timestamp) = timestamp '''.parseRosetta - model.assertError(ROSETTA_SYMBOL_REFERENCE, TYPE_ERROR, - "Expected type 'zonedDateTime' but was 'date'") + model.assertError(ROSETTA_SYMBOL_REFERENCE, null, + "Expected type `zonedDateTime`, but got `date` instead. Cannot assign `date` to input `timestamp`") } @Test @@ -2141,7 +2148,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { attribute1 '''.parseRosetta model.assertError(CHOICE_OPERATION, null, - "At least two attributes must be passed to a choice rule.") + "At least two attributes must be passed to a choice rule") } @@ -2224,7 +2231,9 @@ class RosettaValidatorTest implements RosettaIssueCodes { '''.parseRosetta model.assertError(ROSETTA_SYNONYM_BODY, null, - "Pattern to match must be a valid regular expression") +"Pattern to match must be a valid regular expression - Unclosed character class near index 5 +([A-Z) + ^") } @Disabled @@ -2258,7 +2267,9 @@ class RosettaValidatorTest implements RosettaIssueCodes { '''.parseRosetta model.assertError(ROSETTA_ENUM_SYNONYM, null, - "Pattern to match must be a valid regular expression") +"Pattern to match must be a valid regular expression - Unclosed character class near index 5 +([A-Z) + ^") } @Test @@ -2386,7 +2397,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def shouldNotGenerateCountCardinalityErrorForMap() { + def shouldNotGenerateCountCardinalityErrorForExtract() { val model = ''' type Bar: foos Foo (0..*) @@ -2410,7 +2421,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def shouldNotGenerateCountCardinalityErrorDefaultParameterForMap() { + def shouldNotGenerateCountCardinalityErrorDefaultParameterForExtract() { val model = ''' type Bar: foos Foo (0..*) @@ -2434,7 +2445,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def shouldNotGenerateCountCardinalityErrorForNestedMap() { + def shouldNotGenerateCountCardinalityErrorForNestedExtract() { val model = ''' type Bar: foos Foo (0..*) @@ -2460,7 +2471,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def shouldNotGenerateCountCardinalityErrorDefaultParameterForNestedMap() { + def shouldNotGenerateCountCardinalityErrorDefaultParameterForNestedExtract() { val model = ''' type Bar: foos Foo (0..*) @@ -2486,7 +2497,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def shouldNotGenerateErrorForMapListOperation() { + def shouldNotGenerateErrorForExtractListOperation() { val model = ''' type Bar: foo Foo (1..1) @@ -2553,7 +2564,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { set result: if bars exists - then bars map [ item -> foo ] distinct only-element -> amount + then bars extract [ item -> foo ] distinct only-element -> amount '''.parseRosetta // then clause should generate syntax error (see test above shouldGenerateErrorForFeatureCallAfterListOperation) model.assertError(ROSETTA_MODEL, Diagnostic.SYNTAX_DIAGNOSTIC, "missing EOF at '->'") @@ -2663,7 +2674,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def void shouldGenerateListMapNoExpressionError() { + def void shouldGenerateListExtractNoExpressionError() { val model = ''' func FuncFoo: inputs: @@ -2673,7 +2684,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { add strings: foos - map + extract type Foo: x string (1..1) @@ -2682,7 +2693,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def void shouldGenerateListMapParametersError() { + def void shouldGenerateListExtractParametersError() { val model = ''' func FuncFoo: inputs: @@ -2692,7 +2703,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { add strings: foos - map a, b [ a -> x ] + extract a, b [ a -> x ] type Foo: x string (1..1) @@ -2701,7 +2712,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def void mapWithNamedFunctionReferenceShouldGenerateNoError() { + def void extractWithNamedFunctionReferenceShouldGenerateNoError() { val model = ''' func DoSomething: inputs: @@ -2729,7 +2740,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def void shouldGenerateListMapParametersErrorNamedFunctionReference() { + def void shouldGenerateListExtractParametersErrorNamedFunctionReference() { val model = ''' func DoSomething: inputs: @@ -2749,16 +2760,16 @@ class RosettaValidatorTest implements RosettaIssueCodes { add strings: foos - map DoSomething + extract DoSomething type Foo: x string (1..1) '''.parseRosetta - model.assertError(ROSETTA_SYMBOL_REFERENCE, null, "Expected 2 arguments, but got 0 instead.") + model.assertError(ROSETTA_SYMBOL_REFERENCE, null, "Expected 2 arguments, but got 0 instead") } @Test - def void shouldNotGenerateListMapExpressionCardinalityError() { + def void shouldNotGenerateListExtractExpressionCardinalityError() { val model = ''' func FuncFoo: inputs: @@ -2779,7 +2790,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def void shouldNotGenerateListMapExpressionCardinalityError2() { + def void shouldNotGenerateListExtractExpressionCardinalityError2() { val model = ''' func FuncFoo: inputs: @@ -2803,7 +2814,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { } @Test - def void shouldNotGenerateListMapExpressionCardinalityError3() { + def void shouldNotGenerateListExtractExpressionCardinalityError3() { val model = ''' func FuncFoo: inputs: @@ -2841,7 +2852,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { add strings: foos - map a [ a -> x ] // not a list of lists + extract a [ a -> x ] // not a list of lists flatten type Foo: @@ -2957,12 +2968,12 @@ class RosettaValidatorTest implements RosettaIssueCodes { add strings: foos - map a [ a -> xs ] // list of lists + extract a [ a -> xs ] // list of lists type Foo: xs string (0..*) '''.parseRosetta - model.assertError(OPERATION, null, "Assign expression contains a list of lists, use flatten to create a list.") + model.assertError(OPERATION, null, "Assign expression contains a list of lists, use flatten to create a list") } @Test @@ -2976,12 +2987,12 @@ class RosettaValidatorTest implements RosettaIssueCodes { add strings: foos - map a [ a -> xs ] // list of lists + extract a [ a -> xs ] // list of lists type Foo: xs string (0..*) '''.parseRosetta - model.assertError(OPERATION, null, "Assign expression contains a list of lists, use flatten to create a list.") + model.assertError(OPERATION, null, "Assign expression contains a list of lists, use flatten to create a list") } @Test @@ -2995,7 +3006,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { alias stringsAlias: foos - map a [ a -> xs ] // list of lists + extract a [ a -> xs ] // list of lists add strings: stringsAlias @@ -3017,7 +3028,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { set res: foos - map a [ a -> xs ] // list of lists + extract a [ a -> xs ] // list of lists only-element type Foo: @@ -3037,7 +3048,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { add res: foos - map a [ a -> xs ] // list of lists + extract a [ a -> xs ] // list of lists distinct type Foo: @@ -3094,7 +3105,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { x5 int (1..1) x6 string (0..1) '''.parseRosetta - model.assertError(ROSETTA_BINARY_OPERATION, null, "Left hand side of 'and' expression must be boolean") + model.assertError(ROSETTA_BINARY_OPERATION, null, "Expected type `boolean`, but got `number` instead. Cannot use `number` with operator `and`") } @Test @@ -3113,7 +3124,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { x3 number (1..1) x4 number (1..1) '''.parseRosetta - model.assertError(LOGICAL_OPERATION, null, "Left hand side of 'and' expression must be boolean") + model.assertError(LOGICAL_OPERATION, null, "Expected type `boolean`, but got `number` instead. Cannot use `number` with operator `and`") } @Test @@ -3151,7 +3162,7 @@ class RosettaValidatorTest implements RosettaIssueCodes { type Foo: x string (0..1) '''.parseRosetta - model.assertError(REDUCE_OPERATION, null, "List reduce expression must evaluate to the same type as the input. Found types Foo and String.") + model.assertError(REDUCE_OPERATION, null, "List reduce expression must evaluate to the same type as the input. Found types Foo and string.") } @Test @@ -3451,28 +3462,4 @@ class RosettaValidatorTest implements RosettaIssueCodes { models.forEach[assertNoIssues] } -} - -class MyRosettaInjectorProvider extends RosettaInjectorProvider { - override createRuntimeModule() { - return new RosettaRuntimeModule(){ - override bindClassLoaderToInstance() { - return MyRosettaInjectorProvider - .getClassLoader(); - } - - @SingletonBinding(eager=true) - override Class bindRosettaValidator() { - return ExceptionValidator - } - } - } -} - -class ExceptionValidator extends RosettaValidator{ - @Check - def checkForSharks(Data ele) { - if (ele.name.contains("Fish")) throw new Exception("SHARK!") - - } -} +} \ No newline at end of file diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/TypeValidatorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/TypeValidatorTest.xtend index 1b312c1b5..cce0dfeea 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/TypeValidatorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/TypeValidatorTest.xtend @@ -10,9 +10,10 @@ import org.junit.jupiter.api.^extension.ExtendWith import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.* import javax.inject.Inject +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider @ExtendWith(InjectionExtension) -@InjectWith(MyRosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class TypeValidatorTest implements RosettaIssueCodes { @Inject extension ValidationTestHelper diff --git a/rosetta-tools/src/main/java/com/regnosys/rosetta/tools/modelimport/XsdTypeImport.java b/rosetta-tools/src/main/java/com/regnosys/rosetta/tools/modelimport/XsdTypeImport.java index 068710fc0..6e7613641 100644 --- a/rosetta-tools/src/main/java/com/regnosys/rosetta/tools/modelimport/XsdTypeImport.java +++ b/rosetta-tools/src/main/java/com/regnosys/rosetta/tools/modelimport/XsdTypeImport.java @@ -28,7 +28,9 @@ import org.xmlet.xsdparser.xsdelements.enums.UsageEnum; import org.xmlet.xsdparser.xsdelements.visitors.AttributesVisitor; +import com.google.common.collect.Iterables; import com.google.common.collect.Streams; +import com.regnosys.rosetta.RosettaEcoreUtil; import com.regnosys.rosetta.rosetta.RosettaCardinality; import com.regnosys.rosetta.rosetta.RosettaFactory; import com.regnosys.rosetta.rosetta.expression.ExpressionFactory; @@ -50,11 +52,13 @@ private record ChoiceGroup(List attributes, boolean required) {} public final String SIMPLE_EXTENSION_ATTRIBUTE_NAME = "value"; private final XsdUtil util; + private final RosettaEcoreUtil ecoreUtil; @Inject - public XsdTypeImport(XsdUtil util) { + public XsdTypeImport(XsdUtil util, RosettaEcoreUtil ecoreUtil) { super(XsdNamedElements.class); this.util = util; + this.ecoreUtil = ecoreUtil; } @Override @@ -126,7 +130,7 @@ public List registerType(XsdNamedElements xsdType, RosettaXsdMapping xsdMa if (xsdType instanceof XsdGroup group) { xsdMapping.registerGroup(group, data); - completeData(data, Stream.of(group.getChildElement()), null, xsdMapping, result, targetConfig); + initialCompleteData(data, Stream.of(group.getChildElement()), null, xsdMapping, result, targetConfig); } else { XsdComplexType ct = (XsdComplexType)xsdType; xsdMapping.registerComplexType(ct, data); @@ -139,14 +143,13 @@ public List registerType(XsdNamedElements xsdType, RosettaXsdMapping xsdMa xsdMapping.registerAttribute(ct, valueAttr); } - completeData(data, Streams.concat(getChildElement(ct).stream(), getAttributes(ct)), null, xsdMapping, result, targetConfig); + initialCompleteData(data, Streams.concat(getChildElement(ct).stream(), getAttributes(ct)), null, xsdMapping, result, targetConfig); } // Post process: make sure all names are unique: util.makeNamesUnique(result); result.forEach(d -> { util.makeNamesUnique(d.getAttributes()); - util.makeNamesUnique(d.getConditions()); }); elementType.ifPresent(d -> util.makeNamesUnique(d.getAttributes())); @@ -212,47 +215,24 @@ private Data createData(String name, Stream abstractElements data.setName(name); result.add(data); - completeData(data, abstractElements, initialChoiceGroup, xsdMapping, result, config); + initialCompleteData(data, abstractElements, initialChoiceGroup, xsdMapping, result, config); return data; } - private void completeData(Data data, Stream abstractElements, ChoiceGroup initialChoiceGroup, RosettaXsdMapping xsdMapping, List result, ImportTargetConfig config) { + private void initialCompleteData(Data data, Stream abstractElements, ChoiceGroup initialChoiceGroup, RosettaXsdMapping xsdMapping, List result, ImportTargetConfig config) { // Add attributes List choiceGroups = new ArrayList<>(); if (initialChoiceGroup != null) { choiceGroups.add(initialChoiceGroup); } abstractElements.forEach(elem -> registerXsdElementsRecursively(data, elem, initialChoiceGroup, choiceGroups, xsdMapping, result, config)); - - // Add conditions - choiceGroups.forEach(choiceGroup -> { - if (choiceGroup.attributes.size() > 1) { - Condition choice = SimpleFactory.eINSTANCE.createCondition(); - choice.setName("Choice"); - // TODO: shouldn't the count of parent attributes also be taken into account? - if (choiceGroup.attributes.size() == data.getAttributes().size() && choiceGroup.required) { - OneOfOperation oneOf = ExpressionFactory.eINSTANCE.createOneOfOperation(); - oneOf.setOperator("one-of"); - choice.setExpression(oneOf); - } else { - ChoiceOperation op = ExpressionFactory.eINSTANCE.createChoiceOperation(); - op.setOperator("choice"); - op.setNecessity(choiceGroup.required ? Necessity.REQUIRED : Necessity.OPTIONAL); - op.getAttributes().addAll(choiceGroup.attributes); - choice.setExpression(op); - } - data.getConditions().add(choice); - } else if (choiceGroup.attributes.size() == 1 && choiceGroup.required) { - Attribute attr = choiceGroup.attributes.get(0); - attr.getCard().setInf(1); - } - }); } @Override public void completeType(XsdNamedElements xsdType, RosettaXsdMapping xsdMapping) { if (xsdType instanceof XsdGroup group) { - completeXsdElementsRecursively(group.getChildElement(), false, xsdMapping); + Data data = xsdMapping.getRosettaTypeFromGroup(group); + completeData(data, Stream.of(group.getChildElement()), null, xsdMapping); } else { XsdComplexType ct = (XsdComplexType)xsdType; Data data = xsdMapping.getRosettaTypeFromComplex(ct); @@ -272,11 +252,10 @@ public void completeType(XsdNamedElements xsdType, RosettaXsdMapping xsdMapping) attr.setTypeCall(xsdMapping.getRosettaTypeCall(base)); }); - Stream.concat(getChildElement(ct).stream(), getAttributes(ct)) - .forEach(content -> completeXsdElementsRecursively(content, false, xsdMapping)); + completeData(data, Stream.concat(getChildElement(ct).stream(), getAttributes(ct)), null, xsdMapping); } } - private void completeXsdElementsRecursively(XsdAbstractElement abstractElement, boolean isChoiceGroup, RosettaXsdMapping xsdMapping) { + private void completeXsdElementsRecursively(Data currentData, XsdAbstractElement abstractElement, ChoiceGroup currentChoiceGroup, List currentChoiceGroups, RosettaXsdMapping xsdMapping) { if (abstractElement instanceof XsdElement elem) { Attribute attr = xsdMapping.getAttribute(elem); if (elem.getTypeAsXsd() != null) { @@ -285,9 +264,15 @@ private void completeXsdElementsRecursively(XsdAbstractElement abstractElement, // TODO attr.setTypeCall(xsdMapping.getRosettaTypeCallFromBuiltin("string")); } + if (currentChoiceGroup != null) { + currentChoiceGroup.attributes.add(attr); + } } else if (abstractElement instanceof XsdGroup group) { Attribute attr = xsdMapping.getAttribute(group); attr.setTypeCall(xsdMapping.getRosettaTypeCall(group)); + if (currentChoiceGroup != null) { + currentChoiceGroup.attributes.add(attr); + } } else if (abstractElement instanceof XsdAttribute xsdAttr) { Attribute attr = xsdMapping.getAttribute(xsdAttr); if (xsdAttr.getXsdSimpleType() != null) { @@ -298,34 +283,88 @@ private void completeXsdElementsRecursively(XsdAbstractElement abstractElement, // TODO attr.setTypeCall(xsdMapping.getRosettaTypeCallFromBuiltin("string")); } + if (currentChoiceGroup != null) { + currentChoiceGroup.attributes.add(attr); + } } else if (abstractElement instanceof XsdSequence seq) { - if (isChoiceGroup || isMulti(seq.getMaxOccurs()) || seq.getMinOccurs() == 0) { - seq.getXsdElements().forEach(child -> completeXsdElementsRecursively(child, false, xsdMapping)); + if (currentChoiceGroup != null || isMulti(seq.getMaxOccurs()) || seq.getMinOccurs() == 0) { + Data newData = xsdMapping.getRosettaTypeFromComplex(seq); + completeData(newData, seq.getXsdElements(), null, xsdMapping); + Attribute attr = xsdMapping.getAttribute(seq); attr.setTypeCall(xsdMapping.getRosettaTypeCall(seq)); + if (currentChoiceGroup != null) { + currentChoiceGroup.attributes.add(attr); + } } else { - seq.getXsdElements().forEach(child -> completeXsdElementsRecursively(child, isChoiceGroup, xsdMapping)); + seq.getXsdElements().forEach(elem -> completeXsdElementsRecursively(currentData, elem, null, currentChoiceGroups, xsdMapping)); } } else if (abstractElement instanceof XsdAll all) { - if (isChoiceGroup || all.getMinOccurs() == 0) { - all.getXsdElements().forEach(child -> completeXsdElementsRecursively(child, false, xsdMapping)); + if (currentChoiceGroup != null || all.getMinOccurs() == 0) { + Data newData = xsdMapping.getRosettaTypeFromComplex(all); + completeData(newData, all.getXsdElements(), null, xsdMapping); + Attribute attr = xsdMapping.getAttribute(all); attr.setTypeCall(xsdMapping.getRosettaTypeCall(all)); + if (currentChoiceGroup != null) { + currentChoiceGroup.attributes.add(attr); + } } else { - all.getXsdElements().forEach(child -> completeXsdElementsRecursively(child, isChoiceGroup, xsdMapping)); + all.getXsdElements().forEach(elem -> completeXsdElementsRecursively(currentData, elem, null, currentChoiceGroups, xsdMapping)); } } else if (abstractElement instanceof XsdChoice choice) { - if (isChoiceGroup || isMulti(choice.getMaxOccurs())) { - choice.getXsdElements().forEach(child -> completeXsdElementsRecursively(child, true, xsdMapping)); - Attribute attr = xsdMapping.getAttribute(choice); + if (currentChoiceGroup != null || isMulti(choice.getMaxOccurs())) { + boolean required = choice.getMinOccurs() > 0 && choice.getXsdElements().allMatch(elem -> Integer.parseInt(elem.getAttributesMap().getOrDefault(MIN_OCCURS_TAG, "1")) > 0); + ChoiceGroup initialChoiceGroup = new ChoiceGroup(new ArrayList<>(), required); + Data newData = xsdMapping.getRosettaTypeFromComplex(choice); + completeData(newData, choice.getXsdElements(), initialChoiceGroup, xsdMapping); + + Attribute attr = xsdMapping.getAttribute(choice); attr.setTypeCall(xsdMapping.getRosettaTypeCall(choice)); + if (currentChoiceGroup != null) { + currentChoiceGroup.attributes.add(attr); + } } else { - choice.getXsdElements().forEach(child -> completeXsdElementsRecursively(child, true, xsdMapping)); + ChoiceGroup newChoiceGroup = new ChoiceGroup(new ArrayList<>(), choice.getMinOccurs() > 0); + currentChoiceGroups.add(newChoiceGroup); + choice.getXsdElements().forEach(child -> completeXsdElementsRecursively(currentData, child, newChoiceGroup, currentChoiceGroups, xsdMapping)); } - } else { - return; } } + private void completeData(Data data, Stream abstractElements, ChoiceGroup initialChoiceGroup, RosettaXsdMapping xsdMapping) { + List choiceGroups = new ArrayList<>(); + if (initialChoiceGroup != null) { + choiceGroups.add(initialChoiceGroup); + } + + // Complete attributes + abstractElements.forEach(elem -> completeXsdElementsRecursively(data, elem, initialChoiceGroup, choiceGroups, xsdMapping)); + + // Add conditions + choiceGroups.forEach(choiceGroup -> { + if (choiceGroup.attributes.size() > 1) { + Condition choice = SimpleFactory.eINSTANCE.createCondition(); + choice.setName("Choice"); + if (choiceGroup.attributes.size() == Iterables.size(ecoreUtil.getAllAttributes(data)) && choiceGroup.required) { + OneOfOperation oneOf = ExpressionFactory.eINSTANCE.createOneOfOperation(); + oneOf.setOperator("one-of"); + choice.setExpression(oneOf); + } else { + ChoiceOperation op = ExpressionFactory.eINSTANCE.createChoiceOperation(); + op.setOperator("choice"); + op.setNecessity(choiceGroup.required ? Necessity.REQUIRED : Necessity.OPTIONAL); + op.getAttributes().addAll(choiceGroup.attributes); + choice.setExpression(op); + } + data.getConditions().add(choice); + } else if (choiceGroup.attributes.size() == 1 && choiceGroup.required) { + Attribute attr = choiceGroup.attributes.get(0); + attr.getCard().setInf(1); + } + }); + + util.makeNamesUnique(data.getConditions()); + } public Map getXMLConfiguration(XsdNamedElements xsdType, RosettaXsdMapping xsdMapping, String schemaTargetNamespace) { Data data; diff --git a/rosetta-tools/src/test/java/com/regnosys/rosetta/tools/minimalexampleproducer/UnnecessaryElementsRemoverTest.xtend b/rosetta-tools/src/test/java/com/regnosys/rosetta/tools/minimalexampleproducer/UnnecessaryElementsRemoverTest.xtend index d33cc3705..533314331 100644 --- a/rosetta-tools/src/test/java/com/regnosys/rosetta/tools/minimalexampleproducer/UnnecessaryElementsRemoverTest.xtend +++ b/rosetta-tools/src/test/java/com/regnosys/rosetta/tools/minimalexampleproducer/UnnecessaryElementsRemoverTest.xtend @@ -1,6 +1,6 @@ package com.regnosys.rosetta.tools.minimalexampleproducer -import com.regnosys.rosetta.tests.RosettaInjectorProvider +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider import org.eclipse.xtext.testing.InjectWith import org.eclipse.xtext.testing.extensions.InjectionExtension import org.junit.jupiter.api.^extension.ExtendWith @@ -18,7 +18,7 @@ import org.eclipse.xtext.serializer.impl.Serializer import com.regnosys.rosetta.rosetta.RosettaRule @ExtendWith(InjectionExtension) -@InjectWith(RosettaInjectorProvider) +@InjectWith(RosettaTestInjectorProvider) class UnnecessaryElementsRemoverTest { @Inject UnnecessaryElementsRemover service @Inject Serializer serializer diff --git a/rosetta-tools/src/test/java/com/regnosys/rosetta/tools/modelimport/XsdImportTest.java b/rosetta-tools/src/test/java/com/regnosys/rosetta/tools/modelimport/XsdImportTest.java index 571edc44d..31e84329f 100644 --- a/rosetta-tools/src/test/java/com/regnosys/rosetta/tools/modelimport/XsdImportTest.java +++ b/rosetta-tools/src/test/java/com/regnosys/rosetta/tools/modelimport/XsdImportTest.java @@ -48,12 +48,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.regnosys.rosetta.builtin.RosettaBuiltinsService; -import com.regnosys.rosetta.tests.RosettaInjectorProvider; +import com.regnosys.rosetta.tests.RosettaTestInjectorProvider; import com.regnosys.rosetta.types.builtin.RBuiltinTypeService; import com.rosetta.util.serialisation.RosettaXMLConfiguration; @ExtendWith(InjectionExtension.class) -@InjectWith(RosettaInjectorProvider.class) +@InjectWith(RosettaTestInjectorProvider.class) public class XsdImportTest { private static final String NAMESPACE = "test.ns"; diff --git a/rosetta-tools/src/test/resources/model-import/multi/expected/confirmation-type.rosetta b/rosetta-tools/src/test/resources/model-import/multi/expected/confirmation-type.rosetta index 0dfaaaca7..9c7903aa2 100644 --- a/rosetta-tools/src/test/resources/model-import/multi/expected/confirmation-type.rosetta +++ b/rosetta-tools/src/test/resources/model-import/multi/expected/confirmation-type.rosetta @@ -486,10 +486,10 @@ type CommodityBasketOption extends Option: <"Defines a commodity basket option p premium CommodityPremium (1..*) <"The option premium payable by the buyer to the seller."> commodityContentModel CommodityContentModel (0..1) - condition Choice: + condition Choice0: required choice strikePriceUnderlyingReference, strikePriceBasketReference - condition Choice: + condition Choice1: required choice commodityStrikePriceModel, commodityFloatingStrikePriceModel type CommodityDigitalOption extends Option: <"Defines a commodity digital option product. Defines the digital commodity option product type. Digital options exercise when a barrier is breached and are financially settled. The \'commodityDigitalOption\' type is an extension of the \'commodityOption\' product."> @@ -589,10 +589,10 @@ type CommodityVarianceLeg extends CommodityPerformanceSwapLeg: <"Specifies the v volatilityStrikePrice number (0..1) <"Specifies the volatility strike price when this strike is expressed in standard deviation units. Payments on the variance leg are equal to the national amount multiplied by the realized volatility squared minus the volatility strike price squared. Notional amount * (realized volatility^2 - volatility strike^2). Squaring the volatility strike price converts the volatility strike price into a variance strike price. Squaring the realized volatility converts realized volatility to realized variance."> varianceCalculation CommodityVarianceCalculation (1..1) <"Specifies, in relation to each Payment Date, the variance percentage which, when multiplied times the notional amount is the amount to which the Payment Date relates. For purposes of this representation the realized variance is: (annualizationFactor / N) * signma from i = 1 to N (ln (S sub (i+1)) / (S sub i)), where: ln is the natural logarithm, N is the number of pricing dates, S sub i is the relevant price on the observation date i. If nAdjustment is \'true\' then the denominator of the annualization factor is (N - 1) rather than N. If realized volatility is the performance metric in a variance swap rather than realized variance then the square root of the formula above will appear in the confirmation."> - condition Choice: + condition Choice0: required choice notionalAmount, notionalAmountReference - condition Choice: + condition Choice1: required choice varianceStrikePrice, volatilityStrikePrice type ElectricityPhysicalLeg extends PhysicalSwapLeg: <"Physically settled electricity leg. Physically settled leg of a physically settled electricity transaction."> @@ -655,13 +655,13 @@ type Repo extends Product: <"Global element representing a Repo. A Repo, modeled bondEquityModel BondEquityModel (0..*) triParty TriParty (0..1) - condition Choice: + condition Choice0: required choice fixedRateSchedule, floatingRateCalculation - condition Choice: + condition Choice1: required choice duration, repoSequence - condition Choice: + condition Choice2: required choice bondEquityModel, triParty type RequestClearingEligibility extends CorrectableRequestMessage: @@ -5097,7 +5097,7 @@ type GasPhysicalQuantity extends CommodityPhysicalQuantityBase: <"The quantity o gasPhysicalQuantitySequence GasPhysicalQuantitySequence (0..1) condition Choice: - one-of + required choice commodityFixedPhysicalQuantityModel, gasPhysicalQuantitySequence type GasPhysicalQuantitySequence: @@ -8562,7 +8562,7 @@ type UnderlyerInterestLeg extends DirectionalLeg: <"A type describing interest p spreadSchedule SpreadSchedule (0..*) <"The ISDA Spread or a Spread schedule expressed as explicit spreads and dates. In the case of a schedule, the step dates may be subject to adjustment in accordance with any adjustments specified in calculationPeriodDatesAdjustments. The spread is a per annum rate, expressed as a decimal. For purposes of determining a calculation period amount, if positive the spread will be added to the floating rate and if negative the spread will be subtracted from the floating rate. A positive 10 basis point (0.1%) spread would be represented as 0.001."> condition Choice: - one-of + required choice fixedRate, spreadSchedule type Unit: <"A type used to record information about a unit, subdivision, desk, or other similar business entity."> @@ -10450,7 +10450,7 @@ type TouchRateObservation extends TriggerRateObservation: isExercisable boolean (0..1) <"If the touch or no touch event hasn\'t generated an exercise, then we specify whether the option is exercisable or not."> condition Choice: - one-of + required choice touchRateObservationSequence, isExercisable type TouchRateObservationSequence: diff --git a/rosetta-xcore-plugin-dependencies/pom.xml b/rosetta-xcore-plugin-dependencies/pom.xml index 2cb1b3be0..53ab6cd00 100644 --- a/rosetta-xcore-plugin-dependencies/pom.xml +++ b/rosetta-xcore-plugin-dependencies/pom.xml @@ -63,10 +63,6 @@ org.eclipse.xtext org.eclipse.xtext.generator - - org.eclipse.xsemantics - org.eclipse.xsemantics.dsl - com.google.inject guice