Home » Modeling » TMF (Xtext) » Checking if JvmFormalParameter isAssignableFrom XExpression
| |
Re: Checking if JvmFormalParameter isAssignableFrom XExpression [message #1724020 is a reply to message #1723960] |
Fri, 19 February 2016 21:38 |
Larry LeBron Messages: 124 Registered: October 2015 |
Senior Member |
|
|
Ok, I've now done debugging on the isAssignableFrom call. Thanks for the advice. I should note that the test I've been doing is on references to java.lang.String.
In the end, the difference is that this test passes in JUnit and not in the Eclipse runtime:
In RawTypeConformanceComputer:
protected int doIsConformant(ParameterizedTypeReference left, ParameterizedTypeReference right, int flags) {
if (left.getType() == right.getType()) {
In JUnit, these are at equal points in memory (same object id in the debugger), but in the runtime, they're not. In the runtime, they are two different JvmGenericTypeImplCustom objects.
They have all the same values internally, so I'd imagine if this test were instead:
if (left.getType().equals(right.getType()))
it would pass.
In the runtime, the method continues to this point:
JvmType leftType = left.getType();
JvmType rightType = right.getType();
EClass leftEClass = leftType.eClass();
if (leftEClass == TypesPackage.Literals.JVM_GENERIC_TYPE) {
JvmGenericType castedLeftType = (JvmGenericType) leftType;
EClass rightEClass = rightType.eClass();
if (castedLeftType.isFinal()) {
if (rightEClass == TypesPackage.Literals.JVM_TYPE_PARAMETER && getSuperType(right, castedLeftType) != null) {
Since this is java.lang.String, the castedLeftType is final, but this test fails:
rightEClass == TypesPackage.Literals.JVM_TYPE_PARAMETER
which causes the method to return.
Any advice?
Thanks!
|
|
|
Re: Checking if JvmFormalParameter isAssignableFrom XExpression [message #1724028 is a reply to message #1724020] |
Fri, 19 February 2016 22:36 |
Larry LeBron Messages: 124 Registered: October 2015 |
Senior Member |
|
|
If it's helpful, I just did another test with:
param: java.util.AbsractList
arg: java.util.ArrayList
In this case, the system goes deeper into the doIsConformant() method, eventually reaching ParameterizedTypeReference.internalGetSuperType(JvmType rawType).
This ends with a call to getSuperType(rawType, interfaceType, type, new RecursionGuard<JvmType>()).
In the runtime, this then returned a null supertype resulting from this test returning true:
if (!guard.tryNext(thisType)) {
return null;
}
In my JUnit test, it follows the same exact path but passes this point, as the recursion guard allows it to continue and it finds the superType.
|
|
| |
Re: Checking if JvmFormalParameter isAssignableFrom XExpression [message #1724970 is a reply to message #1724040] |
Sun, 28 February 2016 09:38 |
Larry LeBron Messages: 124 Registered: October 2015 |
Senior Member |
|
|
Ok, I finally got around to creating a project which cleanly reproduces this issue. In experimenting with it, I've learned a bit more about the underlying cause, and you may say that this is expected behavior.
I've attached the language project here. The file extension is ".refs"
Be aware that this is a VERY brittle project, created only to demonstrate this issue.
The grammar is very simple, allowing only one instance of each:
- An entity type with one param
- An entityRef type with one argument
During the first entityRef validation (see TyperefsValidator), the arg type is stored as a LightweightTypeReference in the TypeConformanceChecking class. Whatever the type of this argument, it will be stored permanently for the entire execution. Once this is initialized, it is never updated.
Subsequent calls to infer the model will query TypeConformanceChecking.checkMatchingParamAgainstArg, which compares the current entity param with that previously stored arg LightweightTypeReference, using LightweightTypeReference.isAssignagbleFrom()
Here is example code to use:
The test file I used in the runtime:
entity foo(String s) {
ref foo(s);
}
In this example, the first validation will store the type of the arg s, which is java.lang.String.
In this case, the behavior I'm seeing is that, before I save, calls to infer() for an entity with the matching type (in this case, java.lang.String), will reflect that the types are conformant, meaning LightweightTypeReference.isAssignableFrom will return true. This is all logged for the test.
Example output before save:
resolving type for arg s
type is String
converting param JvmParameterizedTypeReference: (type uri: platform:/resource/TypeRefExample/src/test/test.refs#|0) to lightweight type ref
lightweighttyperef is String
ParamType: String is assignable from String
However, when I save, then the test will not detect the matching types. Instead, I see output like:
converting param JvmParameterizedTypeReference: java.lang.String to lightweight type ref
lightweighttyperef is String
ParamType: String is not assignable from String
So, not sure if this is a bug, or if saving a LightweightTypeRef in this way is just an improper thing to do.
To reproduce, run the DSL in runtime eclipse. Create a test .refs file as above. Once the arg type has been stored, trigger infer() by cleaning the project or altering the entity name and saving.
I hope this helps! Let me know if you have any issues reproducing it.
|
|
| | | |
Re: Checking if JvmFormalParameter isAssignableFrom XExpression [message #1724996 is a reply to message #1724976] |
Mon, 29 February 2016 01:30 |
Larry LeBron Messages: 124 Registered: October 2015 |
Senior Member |
|
|
As to the main question of why I'm doing this. My language has a one-to-many cross-reference system, where one entity reference can reference many conformant entities. For tracking this, I've built an index that tracks the names of "entities" and "entity references" as well as their parameters and arguments.
This way, when a new entity or entity reference is added, I can quickly check for conformance, without having to look through all resources.
So, I was hoping that storing the LightweightTypeReferences for an entityRef argument and the JvmFormalParameters for an entity would allow for this.
As to this issue itself, I'm confused by what you mean when you say "You call the method at places there String is not sure to be a Java.lang.String."
I've updated the example project, and am now calling the test at many different places:
- At the beginning of the EntityDefinition infer(), both for pre-linking true and false
- In the acceptor of the EntityDefinition infer(), both for pre-linking true and false
- In an @Check validator method for EntityDefinition
I modified the logging to output the qualified name, for the LightweightTypeReferences. In all cases, the isAssignable test always knows that both the param type and arg type have qualified name java.lang.String.
However, as mentioned earlier, when the test runs after a "clean" or a "save", I get this result where isAssignableFrom returns false.
e.g.
converting param JvmParameterizedTypeReference: java.lang.String to lightweight type ref
lightweighttyperef is String
ParamType: java.lang.String is not assignable from java.lang.String
Is there a different point in the system where you would expect this check to work in all cases?
|
|
| | | |
Re: Checking if JvmFormalParameter isAssignableFrom XExpression [message #1725008 is a reply to message #1725004] |
Mon, 29 February 2016 06:46 |
Larry LeBron Messages: 124 Registered: October 2015 |
Senior Member |
|
|
The reason it needs to be handled this way is because of the one-to-many cross-reference, which needs to be fully tracked.
For example, something like this is valid:
// in resource A
ent foo (String s) {
...
}
// in resource B
ent foo (Object o) {
...
}
// in resource C
ent other() {
entRef foo("hi");
}
In this case, that entRef in C.other is considered to be referencing both A.foo(String) and B.foo(Object). This needs to be tracked, both for eclipse UI tools and for producing an index file which is used by the executing language code.
If, after this, a programmer adds:
[code]
// in resource D
ent foo (String s) {
...
}
[code]
I need to efficiently be able to efficiently add that to the list of entity's referenced by entRef foo("hi"); This is where the indexing system using names and argument LightWeightTypeReferences comes into play. It makes it efficient to look up any possible matching references and index them, without having to poll all possible resources.
Therefore, in the end, the validation checker looks like:
@Check
def checkReferencesAtLeastOneEntity(EntityReference ref) {
if (!entRefIndex.indexesAtLeastOneEntity(ref)) {
error("bad", ref, null)
}
}
Either way, I still don't understand with the LightweightTypeReference.isAssignableFrom() is returning false when the qualified name is equal for both references.
|
|
| | |
Re: Checking if JvmFormalParameter isAssignableFrom XExpression [message #1725015 is a reply to message #1725012] |
Mon, 29 February 2016 07:40 |
Larry LeBron Messages: 124 Registered: October 2015 |
Senior Member |
|
|
Unfortunately, I don't think the entity ref maps well to a constructor call, because the object to construct (which entity to select) is actually dynamically decided at program execution time, based on the one-to-many mapping indexed as I've described here.
For now, I have this logic for checking whether an arg conforms to a param, which is working so far. I'm just not sure if its reliance on reflection is ever going to become a performance issue. This is Xtend code:
def private paramConformsToArg(JvmParameterizedTypeReference paramType, LightweightTypeReference argType) {
/* a null arg always conforms to a param */
if (argType.type == null) {
return true
}
/* Confirm that the generic type arguments, if they exist, are strictly equal */
if (!paramTypeStrictlyEqualsArgType(paramType, argType, true)) {
log(VALIDATION, "Type args are not conformant:" + paramType + " arg: " + argType)
return false
}
val paramQualifiedName = paramType.type.qualifiedName
val paramClass = paramQualifiedName.classForQualifiedName
val argQualifiedName = argType.type.qualifiedName
var argClass = argQualifiedName.classForQualifiedName
return paramClass.isAssignableFrom(argClass)
}
def private boolean paramTypeStrictlyEqualsArgType(JvmParameterizedTypeReference paramType, LightweightTypeReference argType, boolean onlyCheckTypeArgs) {
val paramTypeGenericArgs = paramType.arguments
val argTypeGenericArgs = argType.typeArguments
/*Only check the generic type arguments if the parameter has declared some. Otherwise, it should be considered equal based on its "base" alone
e.g. arg of type List<String> equals param of type List */
if (!paramTypeGenericArgs.empty) {
if (paramTypeGenericArgs.size != argTypeGenericArgs.size) {
return false
}
/* Recursively examine the generic type arguments, ensuring that they're all strictly equal */
for (var i = 0; i < paramTypeGenericArgs.size; i++) {
var paramGenericArg = paramTypeGenericArgs.get(i) as JvmParameterizedTypeReference
var argGenericArg = argTypeGenericArgs.get(i)
if (!paramTypeStrictlyEqualsArgType(paramGenericArg, argGenericArg, false)) {
return false
}
}
}
if (onlyCheckTypeArgs) {
return true
}
return paramType.type.qualifiedName == argType.type.qualifiedName
}
def getClassForQualifiedName(String qualifiedName) {
if (qualifiedName.isPrimitiveTypeName) {
qualifiedName.classForPrimitive
} else {
Class.forName(qualifiedName)
}
}
|
|
|
Goto Forum:
Current Time: Wed Sep 25 01:26:25 GMT 2024
Powered by FUDForum. Page generated in 0.05481 seconds
|