Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 312554

Summary: [serializer] Serializer chooses first matching parser rule for local serialization
Product: [Modeling] TMF Reporter: Jan Koehnlein <jan>
Component: XtextAssignee: Project Inbox <tmf.xtext-inbox>
Status: CLOSED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: henrik.lindberg, moritz.eysholdt, sebastian.zarnekow
Version: 1.0.0Flags: jan: indigo+
Target Milestone: SR1   
Hardware: PC   
OS: Mac OS X - Carbon (unsup.)   
Whiteboard:
Bug Depends on: 333976, 352088    
Bug Blocks:    

Description Jan Koehnlein CLA 2010-05-12 05:12:24 EDT
If a grammar contains to rules with the same return types, that both assign the features that are set in the object to be serialized (and maybe more optional ones) the serializer takes the first match, even though it might not be valid in the context of the element.

MWE2 for example defines two rules with almost identical feature assignment.

RootComponent returns Component:
  {Component} (type=[types::JvmType|FQN]| '@' module=[Module|FQN]) (':' name=FQN)? (autoInject?='auto-inject')? 
  '{'
    assignment+=Assignment* 
  '}';
  
Component:
  {Component} (type=[types::JvmType|FQN]| '@' module=[Module|FQN])? (':' name=FQN)? (autoInject?='auto-inject')? 
  '{'
    assignment+=Assignment* 
  '}';

While the RootComponent rule only matches the first occurrence, Components can be nested using the Component rule. Serializing a non root component will usually result in applying the RootComponent rule as it preceeds the Component rule. 

One consequence of this bug is that comments are changing positions when applying a quickfix (#302128).
Comment 1 Moritz Eysholdt CLA 2010-09-21 12:15:33 EDT
what's your intended way of implementing this?
Comment 2 Sebastian Zarnekow CLA 2010-09-21 12:18:55 EDT
I'll check the containment tree of the semantic model against the grammar if the to-be-serialized-token is a root token.
Comment 3 Moritz Eysholdt CLA 2010-09-21 12:21:27 EDT
by "root token" do you mean an EObject for which there is only one valid parser rule? that would work...
Comment 4 Sebastian Zarnekow CLA 2010-09-21 12:23:24 EDT
By RootToken I mean org.eclipse.xtext.parsetree.reconstr.impl.AbstractParseTreeConstructor.RootToken
Comment 5 Moritz Eysholdt CLA 2010-09-21 12:37:40 EDT
(In reply to comment #4)
> By RootToken I mean
> org.eclipse.xtext.parsetree.reconstr.impl.AbstractParseTreeConstructor.RootToken

hm... then I don't see what you're up to.

(In reply to comment #2)
> I'll check the containment tree of the semantic model against the grammar if
> the to-be-serialized-token is a root token.

because, with the current implementation, every to-be-serialized-EObject is a valid RootToken. On the one hand this is convenient to allow easy serialization for every sub-tree of a model. But on the other hand this is exactly the cause for this bug. The algorithm I was thinking of is:

boolean exactlyOneParserRuleForObject(EObject) {
 There is exactly one parser rule in the grammar that could have instantiated this EObject. I.e., object.eClass() == rule.returnType || actionExists(object.eClass(), rule) || unassigendRuleCallExists(object.eClass(), rule)
}

extended_to_be_serialized = to_be_serialized;
while(!exactlyOneParserRuleForObject(extended_to_be_serialized)))
    extended_to_be_serialized = extended_to_be_serialized.eContainer();

text = serialize(extended_to_be_serialized);
text = trimToRegion(text, to_be_serialized);


The main problem is that if there are multiple parser rules for an EObject the only algorithm that can select the right one is the serializer's backtracking algorithm.
Comment 6 Sebastian Zarnekow CLA 2010-09-21 13:03:36 EDT
I'm on the following track:

InstanceDescription tryConsume() {
  if (next == lastRuleCallOrigin && lastRuleCallOrigin instanceof RootToken) {
    if (!isValidFor(getEObject()) {
      return null;
    }
  }
  if(getEObject().eClass() != grammarAccess....getClassifier())
    return null;
  return eObjectConsumer;
}
Comment 7 Sebastian Zarnekow CLA 2010-09-21 13:04:52 EDT
(In reply to comment #6)
> I'm on the following track:
> 
> InstanceDescription tryConsume() {
>   if (next == lastRuleCallOrigin && lastRuleCallOrigin instanceof RootToken) {
>     if (!isValidFor(getEObject()) {
>       return null;
>     }
>   }
>   if(getEObject().eClass() != grammarAccess....getClassifier())
>     return null;
>   return eObjectConsumer;
> }

It has to read:

IEObjectConsumer tryConsume() {
  ..
}
Comment 8 Moritz Eysholdt CLA 2010-09-21 13:14:15 EDT
(In reply to comment #6)
> I'm on the following track:
> 
> InstanceDescription tryConsume() {
>   if (next == lastRuleCallOrigin && lastRuleCallOrigin instanceof RootToken) {
>     if (!isValidFor(getEObject()) {
>       return null;
>     }
>   }
>   if(getEObject().eClass() != grammarAccess....getClassifier())
>     return null;
>   return eObjectConsumer;
> }

ok, I see. How do you want to implement isValidFor(getEObject()?
Comment 9 Sebastian Zarnekow CLA 2010-09-24 10:51:42 EDT
Postponed.
Comment 10 Henrik Lindberg CLA 2010-10-11 16:01:36 EDT
I think I may have been bitten by this bug.

I experienced an insertion of the string "@precondition" before an Expression, and a ';' after the expression as result of a SemanticModification of a SwitchExpression (derived from Expression).

I had the following...

Function :  ... // other things omitted
    (preCondition = PreCondition) ?
   ;

PreCondition returns Expression : "@precondition" Expression ';' ;

Changing this to:

Function :  ... //
   ("@precondition preCondition = Expression ';' )

(and removing the PreCondition rule) worked around the issue.
Comment 11 Moritz Eysholdt CLA 2011-01-11 09:59:38 EST
bug 333976 proposes to solve this in the way that a "context" can be given to the serializer. The context can be a ParserRule or an AssignedAction.

Furthermore, a component to find all valid contexts for a semantic object would come handy. 

---
interface GrammarContextFinder {
   Iterable<EObject> findValidContexts(EObject semanitcObject, boolean checkContainer, Iterable<EObject> contextsToConsider);
}

- if contextsToConsider is null, all contexts are considered.
- this returns a list of all contexts that would be valid to serializes semanitcObject.
- if checkContainer is true, this should also check if there is a context for semanticObject's container that has an assigned rule call/action to the returned context.
Comment 12 Sebastian Zarnekow CLA 2011-07-14 17:31:45 EDT
The described MWE2 scenario works like a charm now. Resolved fixed.
Comment 13 Karsten Thoms CLA 2017-09-19 17:31:21 EDT
Closing all bugs that were set to RESOLVED before Neon.0
Comment 14 Karsten Thoms CLA 2017-09-19 17:42:32 EDT
Closing all bugs that were set to RESOLVED before Neon.0