Incorrect "Type mismatch" error due to duplicate type hierarchy in editor [message #1780100] |
Wed, 17 January 2018 11:56 |
Balz Guenat Messages: 23 Registered: January 2018 |
Junior Member |
|
|
Incorrect "type mismatch" error due to duplicate type hierarchy in editor
Hi there,
We are using a DSL based on xbase and the associated features to use it in Eclipse.
Our issue is that we get incorrect "type mismatch" errors in the editor.
We do not get these errors in the Package Explorer.
These incorrect errors only appear after the opened file has been saved at least once.
After saving, any change, even just inserting (valid) whitespace, will trigger the errors.
Saving again will not remove the errors and the package explorer (correctly) continues to show no errors.
The only way we found to get rid of the errors (or annotations thereof) is to close the file and reopen it (saving and editing will again cause the errors).
This is consistently reproducible using the following steps:
1. Open the (valid) file.
2. Make any change to it, like inserting and removing again a newline at the end.
3. Save the file.
4. Make any change.
I have yet to produce a minimal example that exhibits this behavior but here is the current context:
package com.siemens.mo.idp.idc.example
// this is a regular Java class
import com.siemens.mo.idp.base.model.TableDomNodeList
// this is a namespace containing simple_table
import com.siemens.mo.idp.idc.example.def.simple_module.*
checker SimpleChecker {
release *
category ExampleCategory
use table simple_table
check checkAfTaskVerzeichnis {
// signature of the following constructor is public TableDomNodeList(final TableDocument)
for (haupt : new TableDomNodeList(simple_table)) { // here simple_table throws the type mismatch error
protocol haupt.toString
}
}
}
simple_table has type simple_module_simple_table which directly extends TableDocument.
Therefore it should be usable as an argument for the TableDomNodeList constructor.
Debugging reveals that the reason these types are deemed incompatible is that at the time of the check, there seem to exist two duplicate (or at least similar) type hierarchies.
The expected type, coming from the declaration of the constructor, lies in one type hierarchy whereas the actual type, coming from the declaration of simple_table, lies in the other type hierarchy.
When the type checker walks up the hierarchy starting from the actual type simple_module_simple_table, it will indeed find a type called TableDocument, but that type (or rather the object it is represented by) is not the same TableDocument that is required by the constructor.
That is, we have two identical objects representing the TableDocument type. Since these objects are compared using a normal "==" in ParameterizedTypeReference::getSuperType(...), they are not deemed equal.
The type checker continues its walk up the hierarchy until the end and deems the types incompatible, causing the type mismatch error.
This raises the following, possibly related questions:
Why are there two type hierarchies?
Why are the errors only present in the editor version of the file but not the disk files?
Why do the errors only appear after the file has been saved?
And finally and most importantly, how can we get rid of the incorrect errors?
|
|
|
|
|
Re: Incorrect "Type mismatch" error due to duplicate type hierarchy in editor [message #1780198 is a reply to message #1780150] |
Thu, 18 January 2018 14:43 |
Balz Guenat Messages: 23 Registered: January 2018 |
Junior Member |
|
|
Alright, so here is a pretty compact environment where I could replicate the error.
Grammar:
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.xbase.Xbase
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
DomainModel:
packageDeclaration=PackageDeclaration;
Module:
'module' name=ValidID '{'
tables+=Table+
'}';
Table:
'table' name=ValidID;
UseTable:
'use' 'table' table=[Table];
PackageDeclaration:
'package' name=QualifiedName
importSection=XImportSection?
(module=Module |
checker=Checker);
Checker:
'checker' name=ValidID '{'
useTables+=UseTable+
checks+=Check+
'}';
Check:
'check' name=ValidID
body=XBlockExpression;
Inferrer:
/*
* generated by Xtext 2.13.0
*/
package org.xtext.example.mydsl.jvmmodel
import com.google.inject.Inject
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.xtext.example.mydsl.myDsl.DomainModel
import org.eclipse.xtext.naming.IQualifiedNameProvider
import org.eclipse.xtext.naming.QualifiedName
import org.eclipse.xtext.common.types.JvmGenericType
import java.util.Map
import org.xtext.example.mydsl.myDsl.*
import org.eclipse.xtext.common.types.JvmField
import sharedclasses.BaseTable
import org.eclipse.xtext.common.types.JvmConstructor
/**
* <p>Infers a JVM model from the source model.</p>
*
* <p>The JVM model should contain all elements that would appear in the Java code
* which is generated from the source model. Other models link against the JVM model rather than the source model.</p>
*/
class MyDslJvmModelInferrer extends AbstractModelInferrer {
/**
* convenience API to build and initialize JVM types and their members.
*/
@Inject extension JvmTypesBuilder
@Inject extension IQualifiedNameProvider
/** Global repository for Table classes. */
static Map<QualifiedName, JvmGenericType> tableClasses = <QualifiedName, JvmGenericType>newLinkedHashMap()
def dispatch void infer(Module module, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
System.out.println(String.format("Inferring Module (%d)", 0));
// infer Table classes
for (table : module.tables) {
infer(table, acceptor, isPreIndexingPhase)
}
acceptor.accept(module.toClass(module.fullyQualifiedName)) [
for (table : module.tables) {
members += inferTableField(table)
}
]
}
/**
* Calculates the fully qualified name of the Table's class.<br>
* If the Table is contained (ie. by a IDC Checker or Module),
* then the class's simple name is constructed by the container's name and the Table's name.
*/
def QualifiedName getTableClassQN(Table table) {
var QualifiedName tableQN
if (table.eContainer !== null) {
tableQN = table.eContainer.fullyQualifiedName
}
if (tableQN === null) {
tableQN = table.fullyQualifiedName
}
return tableQN.skipLast(1).append(tableQN.lastSegment + '_' + table.name)
}
/**
* Infers the Table's class and registers the class in the global repository.
*/
def dispatch void infer(Table table, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
val tableClassQN = getTableClassQN(table)
val JvmGenericType tableClass = table.toClass(tableClassQN) [
superTypes += BaseTable.typeRef
val JvmConstructor constructor = table.toConstructor[]
constructor.body = []
members += constructor
]
acceptor.accept(tableClass)
tableClasses.put(tableClassQN, tableClass)
}
/**
* Infers a field with the type of the Table's class taken from the global repository.
*/
def JvmField inferTableField(Table table) {
val tableClass = tableClasses.get(getTableClassQN(table))
return table.toField(table.name, tableClass.typeRef) [
initializer = [
append('''
new «tableClass.typeRef.qualifiedName»()
''')
]
]
}
def dispatch void infer(Checker checker, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(checker.toClass(checker.fullyQualifiedName)) [
// process Use Table Declarations
for (useTable : checker.useTables) {
members += inferTableField(useTable.table)
}
// process Check Declarations
for (check : checker.checks) {
members += check.toMethod(check.name, void.typeRef) [
body = check.body
]
}
]
}
}
There are two files written in the DSL. First, SimpleChecker.mydsl:
package mypackage
import mypackage.simple_module.*
import mypackage2.Foo
//new ClassA("")
//"things"
checker SimpleChecker {
use table simple_table
check simpleCheck {
new Foo(simple_table)
}
}
Second, simple_module.mydsl:
package mypackage
module simple_module {
table simple_table
}
Foo is a very simple class:
package mypackage2;
import sharedclasses.BaseTable;
public class Foo {
public Foo(BaseTable b) {}
}
The package sharedclasses, which is used by both the inferrer and the SimpleChecker has a single class BaseTable:
package sharedclasses;
public class BaseTable {
}
Please ask if anything is unclear.
|
|
|
|
|
|
|
|
|
|
|
|
Re: Incorrect "Type mismatch" error due to duplicate type hierarchy in editor [message #1780299 is a reply to message #1780296] |
Fri, 19 January 2018 17:03 |
Balz Guenat Messages: 23 Registered: January 2018 |
Junior Member |
|
|
Same project setup as before.
SimpleChecker.mydsl:
package mypackage
import mypackage2.Foo
checker SimpleChecker {
table simple_table
check simpleCheck {
new Foo(simple_table)
}
}
MyDsl.xtext:
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.xbase.Xbase
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
DomainModel:
packageDeclaration=PackageDeclaration;
Module:
'module' name=ValidID '{'
tables+=Table+
'}';
Table:
'table' name=ValidID;
UseTable:
'use' 'table' table=[Table];
PackageDeclaration:
'package' name=QualifiedName
importSection=XImportSection?
(module=Module |
checker=Checker);
Checker:
'checker' name=ValidID '{'
(useTables+=UseTable |
tables+=Table)+
checks+=Check+
'}';
Check:
'check' name=ValidID
body=XBlockExpression;
MyDslJvmModelInferrer.xtend:
/*
* generated by Xtext 2.13.0
*/
package org.xtext.example.mydsl.jvmmodel
import com.google.inject.Inject
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.xtext.example.mydsl.myDsl.DomainModel
import org.eclipse.xtext.naming.IQualifiedNameProvider
import org.eclipse.xtext.naming.QualifiedName
import org.eclipse.xtext.common.types.JvmGenericType
import java.util.Map
import org.xtext.example.mydsl.myDsl.*
import org.eclipse.xtext.common.types.JvmField
import sharedclasses.BaseTable
import org.eclipse.xtext.common.types.JvmConstructor
/**
* <p>Infers a JVM model from the source model.</p>
*
* <p>The JVM model should contain all elements that would appear in the Java code
* which is generated from the source model. Other models link against the JVM model rather than the source model.</p>
*/
class MyDslJvmModelInferrer extends AbstractModelInferrer {
/**
* convenience API to build and initialize JVM types and their members.
*/
@Inject extension JvmTypesBuilder
@Inject extension IQualifiedNameProvider
def dispatch void infer(Module module, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
// infer Table classes
for (table : module.tables) {
infer(table, acceptor, isPreIndexingPhase)
}
acceptor.accept(module.toClass(module.fullyQualifiedName)) [
for (table : module.tables) {
members += inferTableField(table)
}
]
}
/**
* Calculates the fully qualified name of the Table's class.<br>
* If the Table is contained (ie. by a IDC Checker or Module),
* then the class's simple name is constructed by the container's name and the Table's name.
*/
def QualifiedName getTableClassQN(Table table) {
var QualifiedName tableQN
if (table.eContainer !== null) {
tableQN = table.eContainer.fullyQualifiedName
}
if (tableQN === null) {
tableQN = table.fullyQualifiedName
}
return tableQN.skipLast(1).append(tableQN.lastSegment + '_' + table.name)
}
/**
* Infers the Table's class and registers the class in the global repository.
*/
def dispatch void infer(Table table, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
val tableClassQN = getTableClassQN(table)
val JvmGenericType tableClass = table.toClass(tableClassQN) [
superTypes += BaseTable.typeRef
val JvmConstructor constructor = table.toConstructor[]
constructor.body = []
members += constructor
]
acceptor.accept(tableClass)
}
/**
* Infers a field with the type of the Table's class taken from the global repository.
*/
def JvmField inferTableField(Table table) {
val tableClassQN = getTableClassQN(table)
return table.toField(table.name, tableClassQN.toString.typeRef) [
initializer = [
append('''
new «tableClassQN.toString.typeRef.qualifiedName»()
''')
]
]
}
def dispatch void infer(Checker checker, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
// infer Table classes
for (table : checker.tables) {
infer(table, acceptor, isPreIndexingPhase)
}
acceptor.accept(checker.toClass(checker.fullyQualifiedName)) [
// process Use Table Declarations
for (useTable : checker.useTables) {
members += inferTableField(useTable.table)
}
// process Table Declarations
for (table : checker.tables) {
members += inferTableField(table)
}
// process Check Declarations
for (check : checker.checks) {
members += check.toMethod(check.name, void.typeRef) [
body = check.body
]
}
]
}
}
To reproduce the issue without unit tests, in SimpleChecker.mydsl, on the line "table simple_table", delete the last character (i.e. the 'e' from simple_table). The usage in the constructor should be marked, as expected. Now add back the 'e'. The usage becomes marked with a different (incorrect) error "...refers to missing type". After another input, like adding a space, the error vanishes (as it should have before).
|
|
|
|
|
|
|
|
Re: Incorrect "Type mismatch" error due to duplicate type hierarchy in editor [message #1780412 is a reply to message #1780410] |
Mon, 22 January 2018 09:40 |
|
looks like parseHelper is not ment to work with xbase
you may try somehting like
class CheckerWithTable {
@Inject extension ParseHelper<DomainModel>
@Inject extension ValidationTestHelper
@Inject CompilationTestHelper compilationTestHelper
def void foo(BaseTable table) {
System.out.println(table.class.toString)
}
@Test
def void checkerWithTable() {
compilationTestHelper.compile('''
package mypackage
checker SimpleCheckerWithCatalog {
table simple_table
check SimplePointCheck {
foo(simple_table)
}
}
''') [result|
Assert.assertEquals('''
MULTIPLE FILES WERE GENERATED
File 1 : /myProject/./src-gen/mypackage/SimpleCheckerWithCatalog.java
package mypackage;
import mypackage.SimpleCheckerWithCatalog_simple_table;
@SuppressWarnings("all")
public class SimpleCheckerWithCatalog {
private SimpleCheckerWithCatalog_simple_table simple_table = new mypackage.SimpleCheckerWithCatalog_simple_table()
;
public void SimplePointCheck() {
/* name is null */;
}
}
File 2 : /myProject/./src-gen/mypackage/SimpleCheckerWithCatalog_simple_table.java
package mypackage;
import sharedclasses.BaseTable;
@SuppressWarnings("all")
public class SimpleCheckerWithCatalog_simple_table extends BaseTable {
public SimpleCheckerWithCatalog_simple_table() {
}
}
'''.toString(), result.getSingleGeneratedCode());
val errors = result.errorsAndWarnings
Assert.assertEquals(errors.join(","),errors,newArrayList)
]
}
}
(i dont understand your inferror logic to write a correct test)
Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
|
|
|
|
Powered by
FUDForum. Page generated in 0.05878 seconds