Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implicit enum types #839

Merged
merged 12 commits into from
Sep 16, 2024
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