Skip to content

Commit

Permalink
Implicit enum types (finos#839)
Browse files Browse the repository at this point in the history
* Copied changes from enum-extensions branch

* Fixed static compilation errors

* Cleaned

* Cleaned

* Added tests

* Fixed

* Added hover for enum type
  • Loading branch information
SimonCockx authored Sep 16, 2024
1 parent f4d4790 commit bfc2739
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import com.regnosys.rosetta.ide.contentassist.cancellable.CancellableRosettaPars
import com.regnosys.rosetta.ide.contentassist.cancellable.CancellableContentAssistService
import com.regnosys.rosetta.ide.contentassist.cancellable.RosettaOperationCanceledManager
import com.regnosys.rosetta.ide.semantictokens.RosettaSemanticTokenModifiersProvider
import org.eclipse.xtext.ide.server.hover.IHoverService
import com.regnosys.rosetta.ide.hover.RosettaHoverService

/**
* Use this class to register ide components.
Expand Down Expand Up @@ -95,4 +97,8 @@ class RosettaIdeModule extends AbstractRosettaIdeModule {
def Class<? extends OperationCanceledManager> bindOperationCanceledManager() {
RosettaOperationCanceledManager
}

def Class<? extends IHoverService> bindIHoverService() {
RosettaHoverService
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,31 @@
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;

import com.google.common.collect.Streams;
import com.regnosys.rosetta.rosetta.RosettaDefinable;
import com.regnosys.rosetta.rosetta.RosettaEnumValue;
import com.regnosys.rosetta.rosetta.RosettaSymbol;
import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference;
import com.regnosys.rosetta.types.CardinalityProvider;
import com.regnosys.rosetta.types.ExpectedTypeProvider;
import com.regnosys.rosetta.types.RType;

public class RosettaDocumentationProvider implements IEObjectDocumentationProvider {

@Inject
private ExpectedTypeProvider expectedTypeProvider;
@Inject
private CardinalityProvider cardinalityProvider;

@Override
public String getDocumentation(EObject o) {
return Streams.concat(
getDocumentationFromReference(o).stream(),
getDocumentationFromOwner(o).stream()
).collect(Collectors.joining("\n\n"));
}

public List<String> getDocumentationFromReference(EObject o) {
List<String> docs = new ArrayList<>();
if (o instanceof RosettaSymbol) {
RosettaSymbol symbol = (RosettaSymbol)o;
Expand All @@ -35,10 +49,18 @@ public String getDocumentation(EObject o) {
docs.add(objectWithDocs.getDefinition());
}
}
if (docs.isEmpty()) {
return null;
}
return docs.stream().collect(Collectors.joining("\n\n"));
return docs;
}

public List<String> getDocumentationFromOwner(EObject o) {
List<String> docs = new ArrayList<>();
if (o instanceof RosettaSymbolReference) {
RosettaSymbol symbol = ((RosettaSymbolReference)o).getSymbol();
if (symbol instanceof RosettaEnumValue) {
RType t = expectedTypeProvider.getExpectedTypeFromContainer(o);
docs.add(t.toString());
}
}
return docs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.regnosys.rosetta.ide.hover;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.ide.server.Document;
import org.eclipse.xtext.ide.server.hover.HoverContext;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.ITextRegion;

public class RosettaHoverContext extends HoverContext {

private final EObject owner;

public RosettaHoverContext(Document document, XtextResource resource, int offset, ITextRegion region, EObject element, EObject owner) {
super(document, resource, offset, region, element);
this.owner = owner;
}

public EObject getOwner() {
return owner;
}

@Override
public String toString() {
return "RosettaHoverContext [document=" + getDocument() + ", resource=" + (getResource() == null ? "null" : getResource().getURI())
+ ", offset=" + getOffset() + ", region=" + getRegion() + ", element="
+ (getElement() == null ? "null" : EcoreUtil.getURI(getElement())) + ", owner="
+ (getOwner() == null ? "null" : EcoreUtil.getURI(getOwner())) + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.regnosys.rosetta.ide.hover;

import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.inject.Inject;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.Range;
import org.eclipse.xtext.ide.server.Document;
import org.eclipse.xtext.ide.server.hover.HoverService;
import org.eclipse.xtext.ide.server.hover.IHoverService;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.resource.EObjectAtOffsetHelper;
import org.eclipse.xtext.resource.ILocationInFileProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.ITextRegion;

import com.google.common.collect.Streams;

public class RosettaHoverService extends HoverService {
@Inject
private EObjectAtOffsetHelper eObjectAtOffsetHelper;

@Inject
private ILocationInFileProvider locationInFileProvider;

@Inject
private RosettaDocumentationProvider documentationProvider;

@Override
public Hover hover(Document document, XtextResource resource, HoverParams params, CancelIndicator cancelIndicator) {
int offset = document.getOffSet(params.getPosition());
RosettaHoverContext context = createContext(document, resource, offset);
return hover(context);
}

protected RosettaHoverContext createContext(Document document, XtextResource resource, int offset) {
EObject crossLinkedEObject = eObjectAtOffsetHelper.resolveCrossReferencedElementAt(resource, offset);
if (crossLinkedEObject != null) {
if (crossLinkedEObject.eIsProxy()) {
return null;
}
IParseResult parseResult = resource.getParseResult();
if (parseResult == null) {
return null;
}
ILeafNode leafNode = NodeModelUtils.findLeafNodeAtOffset(parseResult.getRootNode(), offset);
if (leafNode != null && leafNode.isHidden() && leafNode.getOffset() == offset) {
leafNode = NodeModelUtils.findLeafNodeAtOffset(parseResult.getRootNode(), offset - 1);
}
if (leafNode == null) {
return null;
}
ITextRegion leafRegion = leafNode.getTextRegion();
EObject owner = NodeModelUtils.findActualSemanticObjectFor(leafNode);
return new RosettaHoverContext(document, resource, offset, leafRegion, crossLinkedEObject, owner);
}
EObject element = eObjectAtOffsetHelper.resolveElementAt(resource, offset);
if (element == null) {
return null;
}
ITextRegion region = locationInFileProvider.getSignificantTextRegion(element);
return new RosettaHoverContext(document, resource, offset, region, element, element);
}

protected Hover hover(RosettaHoverContext context) {
if (context == null) {
return IHoverService.EMPTY_HOVER;
}
MarkupContent contents = getMarkupContent(context);
if (contents == null) {
return IHoverService.EMPTY_HOVER;
}
Range range = getRange(context);
if (range == null) {
return IHoverService.EMPTY_HOVER;
}
return new Hover(contents, range);
}

protected MarkupContent getMarkupContent(RosettaHoverContext ctx) {
return toMarkupContent(getKind(ctx), getContents(ctx.getElement(), ctx.getOwner()));
}

public String getContents(EObject element, EObject owner) {
Stream<String> allDocs = Stream.empty();
if (element != null) {
allDocs = Streams.concat(allDocs, documentationProvider.getDocumentationFromReference(element).stream());
}
if (owner != null) {
allDocs = Streams.concat(allDocs, documentationProvider.getDocumentationFromOwner(owner).stream());
}
return allDocs.collect(Collectors.joining("\n\n"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.regnosys.rosetta.ide.hover

import com.regnosys.rosetta.ide.tests.AbstractRosettaLanguageServerTest
import org.junit.jupiter.api.Test

class RosettaDocumentationProviderTest extends AbstractRosettaLanguageServerTest {
@Test
def testMultiCardinalityDocs() {
testHover[
val model = '''
namespace foo.bar
type Foo:
attr int (2..3)
'''
it.model = model
it.line = 3
it.column = 1
it.assertHover = [
val expected = '''
[[3, 1] .. [3, 5]]
kind: markdown
value: **Multi cardinality.**
'''
assertEquals(expected, toExpectation)
]
]
}

@Test
def testImplicitEnumDocs() {
testHover[
val model = '''
namespace foo.bar
enum MyEnum:
VALUE
func Foo:
output:
result MyEnum (1..1)
set result:
VALUE
'''
it.model = model
it.line = 10
it.column = 2
it.assertHover = [
val expected = '''
[[10, 2] .. [10, 7]]
kind: markdown
value: MyEnum
'''
assertEquals(expected, toExpectation)
]
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class RosettaEcoreUtil {
RDataType:
t.allNonOverridenAttributes.map[EObject]
REnumType:
t.EObject.allEnumValues
t.allEnumValues
RRecordType: {
if (resourceSet !== null) {
builtins.toRosettaType(t, RosettaRecordType, resourceSet).features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,9 @@ class ExpressionGenerator extends RosettaExpressionSwitch<JavaStatementBuilder,
}

}
RosettaEnumValue: {
enumCall(s, context.expectedType)
}
ClosureParameter: {
new JavaVariable(context.scope.getIdentifierOrThrow(s), expr.isMulti ? MAPPER_C.wrap(expr) as JavaType : MAPPER_S.wrap(expr))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,32 +62,9 @@ import org.slf4j.LoggerFactory
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 com.regnosys.rosetta.rosetta.expression.InlineFunction
import com.regnosys.rosetta.rosetta.RosettaAttributeReference
import java.util.List
import org.eclipse.xtext.scoping.impl.SimpleScope
import org.eclipse.xtext.resource.EObjectDescription
import org.eclipse.xtext.naming.QualifiedName
import com.regnosys.rosetta.utils.RosettaConfigExtension
import org.eclipse.xtext.resource.impl.AliasedEObjectDescription
import com.regnosys.rosetta.rosetta.simple.Attribute
import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference
import com.regnosys.rosetta.rosetta.expression.ChoiceOperation
import com.regnosys.rosetta.types.RType
import com.regnosys.rosetta.rosetta.RosettaTypeAlias
import com.regnosys.rosetta.rosetta.TypeCall
import com.regnosys.rosetta.rosetta.ParametrizedRosettaType
import javax.inject.Inject
import com.regnosys.rosetta.rosetta.expression.RosettaConstructorExpression
import com.regnosys.rosetta.rosetta.expression.ConstructorKeyValuePair
import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall
import com.regnosys.rosetta.types.RDataType
import com.regnosys.rosetta.utils.DeepFeatureCallUtil
import org.eclipse.xtext.scoping.impl.ImportNormalizer
import org.eclipse.xtext.util.Strings
import com.regnosys.rosetta.rosetta.simple.Annotated
import com.regnosys.rosetta.types.RObjectFactory
import com.regnosys.rosetta.RosettaEcoreUtil
import com.regnosys.rosetta.types.ExpectedTypeProvider

/**
* This class contains custom scoping description.
Expand All @@ -102,6 +79,7 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider {
static Logger LOGGER = LoggerFactory.getLogger(RosettaScopeProvider)

@Inject RosettaTypeProvider typeProvider
@Inject ExpectedTypeProvider expectedTypeProvider
@Inject extension RosettaEcoreUtil
@Inject extension RosettaConfigExtension configs
@Inject extension RosettaFunctionExtensions
Expand Down Expand Up @@ -196,7 +174,12 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider {
inputsAndOutputs.add(function.output)
return Scopes.scopeFor(inputsAndOutputs)
} else {
val implicitFeatures = typeProvider.findFeaturesOfImplicitVariable(context)
var implicitFeatures = typeProvider.findFeaturesOfImplicitVariable(context)

val expectedType = expectedTypeProvider.getExpectedTypeFromContainer(context)
if (expectedType instanceof REnumType) {
implicitFeatures = implicitFeatures + expectedType.allEnumValues
}

val inline = EcoreUtil2.getContainerOfType(context, InlineFunction)
if(inline !== null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,44 @@ class RosettaParsingTest {
@Inject extension ValidationTestHelper
@Inject extension ExpressionParser

@Test
def void testPropagationForScopingForImplicitEnumType() {
val model = '''
enum FooEnum:
FOO1
FOO2
'''.parseRosettaWithNoIssues

'''
myEnumValue
= (["bar", "baz"]
filter = "baz"
then extract FOO1
then only-element)
'''
.parseExpression(#[model], #["myEnumValue FooEnum (1..1)"])
.assertNoIssues
}

@Test
def void testScopingForImplicitEnumType() {
val model = '''
enum FooEnum:
FOO1
FOO2
func OutputOfFunction:
output:
result FooEnum (1..1)
set result:
FOO1
'''.parseRosettaWithNoIssues

"myEnumValue = FOO2"
.parseExpression(#[model], #["myEnumValue FooEnum (1..1)"])
.assertNoIssues
}

@Test
def void testCannotAccessEnumValueThroughAnotherEnumValue() {
val model = '''
Expand Down

0 comments on commit bfc2739

Please sign in to comment.