Skip to content

Commit 593d7a4

Browse files
gselzerctrueden
authored andcommitted
Create isSafeAssignable to help ops.run
We also add a boolean to control isSafeAssignable usage. We really only want to call isSafeAssignable when we are using MatchingUtils through the OpMatcher, so there is now a boolean allowing the user to control whether or not they want to go that extra distance and call isSafeAssignable or if they just want to check if there will be no compiler error.
1 parent bce0c1f commit 593d7a4

File tree

4 files changed

+203
-68
lines changed

4 files changed

+203
-68
lines changed

src/main/java/org/scijava/ops/matcher/MatchingUtils.java

Lines changed: 175 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
import com.google.common.base.Objects;
3636

37+
import java.lang.reflect.Method;
38+
import java.lang.reflect.Modifier;
3739
import java.lang.reflect.ParameterizedType;
3840
import java.lang.reflect.Type;
3941
import java.lang.reflect.TypeVariable;
@@ -56,9 +58,9 @@ private MatchingUtils() {
5658
}
5759

5860
/**
59-
* Checks for raw assignability. TODO This method is not yet fully
60-
* implemented. The correct behavior should be as follows. Suppose we have a
61-
* generic typed method like:
61+
* Checks for raw assignability. TODO This method is not yet fully implemented.
62+
* The correct behavior should be as follows. Suppose we have a generic typed
63+
* method like:
6264
*
6365
* <pre>
6466
*public static &lt;N&gt; List&lt;N&gt; foo(N in) {
@@ -88,13 +90,13 @@ private MatchingUtils() {
8890
* checkGenericOutputsAssignability(nilN.getType(), nilWildcardNumber.getType, ...)
8991
* </pre>
9092
*
91-
* Using a map where N was already bound to Integer (N -> Integer.class).
92-
* This method is useful for the following scenario: During ops matching, we
93-
* first check if the arguments (inputs) of the requested op are applicable
94-
* to the arguments of an op candidate. During this process, possible type
95-
* variables may be inferred. The can then be used with this method to find
96-
* out if the outputs of the op candidate would be assignable to the output
97-
* of the requested op.
93+
* Using a map where N was already bound to Integer (N -> Integer.class). This
94+
* method is useful for the following scenario: During ops matching, we first
95+
* check if the arguments (inputs) of the requested op are applicable to the
96+
* arguments of an op candidate. During this process, possible type variables
97+
* may be inferred. The can then be used with this method to find out if the
98+
* outputs of the op candidate would be assignable to the output of the
99+
* requested op.
98100
*
99101
* @param froms
100102
* @param tos
@@ -106,18 +108,23 @@ public static int checkGenericOutputsAssignability(Type[] froms, Type[] tos,
106108
for (int i = 0; i < froms.length; i++) {
107109
Type from = froms[i];
108110
Type to = tos[i];
109-
110-
//HACK: we CAN assign, for example, a Function<Iterable<N>, O> to a Function<Iterable<Integer>, Double>,
111-
//because in this situation O is not bounded to any other types. However isAssignable will fail,
112-
//since we cannot just cast Double to O without that required knowledge that O can be fixed to Double.
113-
//We get around this by recording in typeBounds that our previously unbounded TypeVariable (from) \
114-
//is now fixed to (to), then simply assigning (from) to (to), since from only has one bound, being to.
115-
if(from instanceof TypeVariable && typeBounds.get(from) == null) {
116-
TypeVariable<?> fromTypeVar = (TypeVariable<?>) from;
117-
TypeVarFromParameterizedTypeInfo fromInfo = new TypeVarFromParameterizedTypeInfo(fromTypeVar);
118-
fromInfo.fixBounds(to, true);
119-
typeBounds.put(fromTypeVar, fromInfo);
120-
from = to;
111+
112+
// HACK: we CAN assign, for example, a Function<Iterable<N>, O> to a
113+
// Function<Iterable<Integer>, Double>,
114+
// because in this situation O is not bounded to any other types. However
115+
// isAssignable will fail,
116+
// since we cannot just cast Double to O without that required knowledge that O
117+
// can be fixed to Double.
118+
// We get around this by recording in typeBounds that our previously unbounded
119+
// TypeVariable (from) \
120+
// is now fixed to (to), then simply assigning (from) to (to), since from only
121+
// has one bound, being to.
122+
if (from instanceof TypeVariable && typeBounds.get(from) == null) {
123+
TypeVariable<?> fromTypeVar = (TypeVariable<?>) from;
124+
TypeVarFromParameterizedTypeInfo fromInfo = new TypeVarFromParameterizedTypeInfo(fromTypeVar);
125+
fromInfo.fixBounds(to, true);
126+
typeBounds.put(fromTypeVar, fromInfo);
127+
from = to;
121128
}
122129

123130
if (!Types.isAssignable(Types.raw(from), Types.raw(to)))
@@ -128,8 +135,8 @@ public static int checkGenericOutputsAssignability(Type[] froms, Type[] tos,
128135

129136
/**
130137
* Checks whether it would be legal to assign the {@link Type} source to the
131-
* specified {@link ParameterizedType} destination (which could possibly be
132-
* a supertype of the source type). Thereby, possible {@link TypeVariable}s
138+
* specified {@link ParameterizedType} destination (which could possibly be a
139+
* supertype of the source type). Thereby, possible {@link TypeVariable}s
133140
* contained in the parameters of the source are tried to be inferred in the
134141
* sense of empty angle brackets when a new object is created:
135142
*
@@ -140,9 +147,9 @@ public static int checkGenericOutputsAssignability(Type[] froms, Type[] tos,
140147
* Hence, the types to put between the brackets are tried to be determined.
141148
* Inference will be done by simple matching of an encountered
142149
* {@link TypeVariable} in the source to the corresponding type in the
143-
* parameters of the destination. If an {@link TypeVariable} is encountered
144-
* more than once, the corresponding type in the destination needs to
145-
* perfectly match. Else, false will be returned.</br>
150+
* parameters of the destination. If an {@link TypeVariable} is encountered more
151+
* than once, the corresponding type in the destination needs to perfectly
152+
* match. Else, false will be returned.</br>
146153
* </br>
147154
* Examples:
148155
* <ul>
@@ -171,8 +178,8 @@ public static int checkGenericOutputsAssignability(Type[] froms, Type[] tos,
171178
* </ul>
172179
* </ul>
173180
* <ul>
174-
* Here, the parameter {@code <M extends Number>} can be inferred to be of
175-
* type {@code Double} from the type {@code Supplier<Double>}
181+
* Here, the parameter {@code <M extends Number>} can be inferred to be of type
182+
* {@code Double} from the type {@code Supplier<Double>}
176183
* </ul>
177184
* <ul>
178185
* Consequently the following will return false:
@@ -183,8 +190,8 @@ public static int checkGenericOutputsAssignability(Type[] froms, Type[] tos,
183190
* Nil&lt;Supplier&lt;String&gt;&gt;() {}.getType())</li>
184191
* </ul>
185192
* <ul>
186-
* {@code <M extends Number>} can't be inferred, as type {@code String} is
187-
* not within the bounds of {@code M}.
193+
* {@code <M extends Number>} can't be inferred, as type {@code String} is not
194+
* within the bounds of {@code M}.
188195
* </ul>
189196
* <ul>
190197
* Furthermore, the following will return false for:
@@ -203,12 +210,20 @@ public static int checkGenericOutputsAssignability(Type[] froms, Type[] tos,
203210
* @param src
204211
* the type for which assignment should be checked from
205212
* @param dest
206-
* the parameterized type for which assignment should be checked
207-
* to
208-
* @return whether and assignment of source to destination would be a legal
209-
* java statement
213+
* the parameterized type for which assignment should be checked to
214+
* @param typeVarAssigns
215+
* the map of TypeVariables to Types that would occur in this
216+
* scenario
217+
* @param safeAssignability
218+
* used to determine if we want to check if the src->dest
219+
* assignment would be safely assignable even though it would cause a
220+
* compiler error if we explicitly tried to do this (useful pretty
221+
* much only for Op matching)
222+
* @return whether and assignment of source to destination would be a legal java
223+
* statement
210224
*/
211-
public static boolean checkGenericAssignability(Type src, ParameterizedType dest, Map<TypeVariable<?>, Type> typeVarAssigns) {
225+
public static boolean checkGenericAssignability(Type src, ParameterizedType dest,
226+
Map<TypeVariable<?>, Type> typeVarAssigns, boolean safeAssignability) {
212227
// check raw assignability
213228
if (!Types.isAssignable(Types.raw(src), Types.raw(dest)))
214229
return false;
@@ -225,18 +240,68 @@ public static boolean checkGenericAssignability(Type src, ParameterizedType dest
225240
return Types.isAssignable(src, dest);
226241
}
227242

228-
return checkGenericAssignability(srcTypes, destTypes, dest, typeVarAssigns);
243+
return checkGenericAssignability(srcTypes, destTypes, src, dest, typeVarAssigns, safeAssignability);
229244
}
230245

231-
public static boolean checkGenericAssignability(Type src, ParameterizedType dest) {
232-
return checkGenericAssignability(src, dest, new HashMap<TypeVariable<?>, Type>());
246+
/**
247+
* @param src
248+
* the type for which assignment should be checked from
249+
* @param dest
250+
* the parameterized type for which assignment should be checked to
251+
* @param safeAssignability
252+
* used to determine if we want to check if the src->dest
253+
* assignment would be safely assignable even though it would cause a
254+
* compiler error if we explicitly tried to do this (useful pretty
255+
* much only for Op matching)
256+
* @return whether and assignment of source to destination would be a legal java
257+
* statement
258+
*/
259+
public static boolean checkGenericAssignability(Type src, ParameterizedType dest, boolean safeAssignability) {
260+
return checkGenericAssignability(src, dest, new HashMap<TypeVariable<?>, Type>(), safeAssignability);
233261
}
234262

235-
private static boolean checkGenericAssignability(Type[] srcTypes, Type[] destTypes, Type dest) {
236-
return checkGenericAssignability(srcTypes, destTypes, dest, new HashMap<TypeVariable<?>, Type>());
263+
/**
264+
* @param srcTypes the Type arguments for the source Type
265+
* @param destTypes the Type arguments for the destination Type
266+
* @param src
267+
* the type for which assignment should be checked from
268+
* @param dest
269+
* the parameterized type for which assignment should be checked to
270+
* @param safeAssignability
271+
* used to determine if we want to check if the src->dest
272+
* assignment would be safely assignable even though it would cause a
273+
* compiler error if we explicitly tried to do this (useful pretty
274+
* much only for Op matching)
275+
* @return whether and assignment of source to destination would be a legal java
276+
* statement
277+
*/
278+
private static boolean checkGenericAssignability(Type[] srcTypes, Type[] destTypes, Type src, Type dest,
279+
boolean safeAssignability) {
280+
return checkGenericAssignability(srcTypes, destTypes, src, dest, new HashMap<TypeVariable<?>, Type>(),
281+
safeAssignability);
237282
}
238283

239-
private static boolean checkGenericAssignability(Type[] srcTypes, Type[] destTypes, Type dest, Map<TypeVariable<?>, Type> typeVarAssigns) {
284+
/**
285+
*
286+
* @param srcTypes the Type arguments for the source Type
287+
* @param destTypes the Type arguments for the destination Type
288+
* @param src
289+
* the type for which assignment should be checked from
290+
* @param dest
291+
* the parameterized type for which assignment should be checked to
292+
* @param typeVarAssigns
293+
* the map of TypeVariables to Types that would occur in this
294+
* scenario
295+
* @param safeAssignability
296+
* used to determine if we want to check if the src->dest
297+
* assignment would be safely assignable even though it would cause a
298+
* compiler error if we explicitly tried to do this (useful pretty
299+
* much only for Op matching)
300+
* @return whether and assignment of source to destination would be a legal java
301+
* statement
302+
*/
303+
private static boolean checkGenericAssignability(Type[] srcTypes, Type[] destTypes, Type src, Type dest,
304+
Map<TypeVariable<?>, Type> typeVarAssigns, boolean safeAssignability) {
240305
// if the number of type arguments does not match, the types can't be
241306
// assignable
242307
if (srcTypes.length != destTypes.length) {
@@ -252,19 +317,62 @@ private static boolean checkGenericAssignability(Type[] srcTypes, Type[] destTyp
252317
mappedSrcTypes = mapVarToTypes(srcTypes, typeVarAssigns);
253318
} catch (TypeInferenceException e) {
254319
// types can't be inferred
255-
return false;
320+
return safeAssignability && isSafeAssignable(destTypes, typeVarAssigns, src, dest);
256321
}
257322

258323
// Build a new parameterized type from inferred types and check
259324
// assignability
260325
Class<?> matchingRawType = Types.raw(dest);
261326
Type inferredSrcType = Types.parameterize(matchingRawType, mappedSrcTypes);
262327
if (!Types.isAssignable(inferredSrcType, dest)) {
263-
return false;
328+
if (!safeAssignability || !isSafeAssignable(destTypes, typeVarAssigns, src, dest))
329+
return false;
264330
}
265331
return true;
266332
}
267333

334+
/**
335+
* We know that the special types for the Op candidate and what we asked for are
336+
* the same (i.e. that we are trying to determine if one Function can be
337+
* assigned to another Function). There are some situations (that are
338+
* particularly common when using ops.run()) where the Function SHOULD NOT
339+
* NORMALLY MATCH UP but WE KNOW IT WILL BE SAFE TO ASSIGN. This method attempts
340+
* to tease those situations out as a last resort.
341+
*
342+
* @param srcTypes
343+
* - the array of Parameterized types of the potential match
344+
* @param destTypes
345+
* - the array of Parameterized types of the OpInfo we called the
346+
* matcher on (in the case of ops.run(), it is a Type array of the
347+
* types of the args we passed through.)
348+
* @param typeVarAssigns
349+
* - a Map of all of the Type Variables already determined.
350+
* @param dest
351+
* - the speical type of the Op that we want to find a match for
352+
* (determined by the user / ops.run())
353+
* @return boolean - true if we can safely match this Op even though the types
354+
* do not directly match up. False otherwise.
355+
*/
356+
public static boolean isSafeAssignable(Type[] destTypes, Map<TypeVariable<?>, Type> typeVarAssigns, Type src,
357+
Type dest) {
358+
359+
Method[] destMethods = Arrays.stream(Types.raw(dest).getDeclaredMethods())
360+
.filter(method -> Modifier.isAbstract(method.getModifiers())).toArray(Method[]::new);
361+
Type[] params = Types.methodParamTypes(destMethods[0], Types.raw(src));
362+
Type returnType = Types.methodReturnType(destMethods[0], Types.raw(src));
363+
for (int i = 0; i < params.length; i++) {
364+
if (!Types.isAssignable(destTypes[i], params[i], typeVarAssigns))
365+
return false;
366+
}
367+
368+
// Computers will have void as their return type, meaning that there is no
369+
// output to check.
370+
if (returnType == void.class)
371+
return true;
372+
373+
return Types.isAssignable(returnType, destTypes[destTypes.length - 1], typeVarAssigns);
374+
}
375+
268376
/**
269377
* Exception indicating that type vars could not be inferred.
270378
*/
@@ -277,8 +385,8 @@ private static class TypeInferenceException extends Exception {
277385

278386
/**
279387
* Map type vars in specified type list to types using the specified map. In
280-
* doing so, type vars mapping to other type vars will not be followed but
281-
* just repalced.
388+
* doing so, type vars mapping to other type vars will not be followed but just
389+
* repalced.
282390
*
283391
* @param typesToMap
284392
* @param typeAssigns
@@ -355,13 +463,17 @@ private static void inferTypeVariables(Type[] types, Type[] inferFrom, Map<TypeV
355463
} else if (types[i] instanceof ParameterizedType) {
356464
// Recursively follow parameterized types
357465
if (!(inferFrom[i] instanceof ParameterizedType)) {
358-
throw new TypeInferenceException();
466+
Type[] fromType = { types[i] };
467+
fromType = Types.mapVarToTypes(fromType, typeAssigns);
468+
if (!Types.isAssignable(inferFrom[i], fromType[0])) {
469+
throw new TypeInferenceException();
470+
}
471+
} else {
472+
ParameterizedType paramType = (ParameterizedType) types[i];
473+
ParameterizedType paramInferFrom = (ParameterizedType) inferFrom[i];
474+
inferTypeVariables(paramType.getActualTypeArguments(), paramInferFrom.getActualTypeArguments(),
475+
typeAssigns);
359476
}
360-
ParameterizedType paramType = (ParameterizedType) types[i];
361-
ParameterizedType paramInferFrom = (ParameterizedType) inferFrom[i];
362-
inferTypeVariables(paramType.getActualTypeArguments(), paramInferFrom.getActualTypeArguments(),
363-
typeAssigns);
364-
365477
} else if (types[i] instanceof WildcardType) {
366478
// TODO Do we need to specifically handle Wildcards? Or are they
367479
// sufficiently handled by Types.satisfies below?
@@ -374,20 +486,19 @@ private static void inferTypeVariables(Type[] types, Type[] inferFrom, Map<TypeV
374486
}
375487

376488
/**
377-
* Finds the type parameters of the most specific super type of the
378-
* specified subType whose erasure is the specified superErasure. Hence,
379-
* will return the type parameters of superErasure possibly narrowed down by
380-
* subType. If superErasure is not raw or not a super type of subType, an
381-
* empty array will be returned.
489+
* Finds the type parameters of the most specific super type of the specified
490+
* subType whose erasure is the specified superErasure. Hence, will return the
491+
* type parameters of superErasure possibly narrowed down by subType. If
492+
* superErasure is not raw or not a super type of subType, an empty array will
493+
* be returned.
382494
*
383495
* @param subType
384496
* the type to narrow down type parameters
385497
* @param superErasure
386-
* the erasure of an super type of subType to get the parameters
387-
* from
388-
* @return type parameters of superErasure possibly narrowed down by
389-
* subType, or empty type array if no exists or superErasure is not
390-
* a super type of subtype
498+
* the erasure of an super type of subType to get the parameters from
499+
* @return type parameters of superErasure possibly narrowed down by subType, or
500+
* empty type array if no exists or superErasure is not a super type of
501+
* subtype
391502
*/
392503
public static Type[] getParams(Class<?> subType, Class<?> superErasure) {
393504
Type pt = Types.parameterizeRaw(subType);
@@ -416,8 +527,8 @@ public static Class<?> getClass(final Object obj) {
416527

417528
/**
418529
* Finds the levels of casting between <code>origin</code> and
419-
* <code>dest</code>. Returns 0 if dest and origin are the same. Returns -1
420-
* if dest is not assignable from origin.
530+
* <code>dest</code>. Returns 0 if dest and origin are the same. Returns -1 if
531+
* dest is not assignable from origin.
421532
*/
422533
public static int findCastLevels(final Class<?> dest, final Class<?> origin) {
423534
if (dest.equals(origin))

src/main/java/org/scijava/ops/matcher/OpRef.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public boolean typesMatch(final Type opType, final Map<TypeVariable<?>, Type> ty
148148
return true;
149149
for (Type t : types) {
150150
if(t instanceof ParameterizedType) {
151-
if (!MatchingUtils.checkGenericAssignability(opType, (ParameterizedType) t, typeVarAssigns)) {
151+
if (!MatchingUtils.checkGenericAssignability(opType, (ParameterizedType) t, typeVarAssigns, true)) {
152152
return false;
153153
}
154154
} else {

0 commit comments

Comments
 (0)