Skip to content

Commit e04ea9f

Browse files
committed
Any support in Conversions
1 parent 5997158 commit e04ea9f

14 files changed

Lines changed: 203 additions & 160 deletions

File tree

scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ private List<RichOp<?>> resolveOpDependencies(OpInfo info,
578578
Map<TypeVariable<?>, Type> dependencyTypeVarAssigns = new HashMap<>();
579579
for (var entry : typeVarAssigns.entrySet()) {
580580
var value = entry.getValue();
581-
if (!value.equals(Any.class) && !(value instanceof Any)) {
581+
if (!Any.is(value)) {
582582
dependencyTypeVarAssigns.put(entry.getKey(), entry.getValue());
583583
}
584584
}

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/adapt/AdaptationMatchingRoutine.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,7 @@ private void captureTypeVarsFromCandidate(Type srcType, OpCandidate candidate,
186186
var existing = map.get(key);
187187
var replacement = assigns.get(key);
188188
// Ignore bounds that are weaker than current bounds.
189-
if (Types.isAssignable(existing, replacement) && !existing.equals(
190-
Any.class) && !(existing instanceof Any))
191-
{
189+
if (Types.isAssignable(existing, replacement) && !Any.is(existing)) {
192190
continue;
193191
}
194192
}

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,14 @@ private static ConvertedOpInfo convert(OpEnvironment env, OpInfo info,
118118
BaseOpHints.Conversion.FORBIDDEN, //
119119
BaseOpHints.History.IGNORE //
120120
);
121-
final RichOp<Function<?, ?>> identity = identityOp(env);
122121
final Map<TypeVariable<?>, Type> vars = new HashMap<>();
123122
// Find input converters
124123
Type[] fromArgs = request.getArgs();
125124
List<Type> toArgs = inputTypesAgainst(info, Types.raw(reqType));
126125
List<RichOp<Function<?, ?>>> preConverters = new ArrayList<>();
127126
for (int i = 0; i < fromArgs.length; i++) {
128127
var opt = findConverter(fromArgs[i], toArgs.get(i), vars, env, h);
129-
preConverters.add(opt.orElse(identity));
128+
preConverters.add(opt);
130129
}
131130

132131
// Find output converter
@@ -137,7 +136,7 @@ private static ConvertedOpInfo convert(OpEnvironment env, OpInfo info,
137136
return opt.get();
138137
}
139138
// Attempt 2: Computer with identity mutable output
140-
opt = postprocessIdentity(info, request, preConverters, identity, env);
139+
opt = postprocessIdentity(info, request, preConverters, env);
141140
if (opt.isPresent()) {
142141
return opt.get();
143142
}
@@ -206,7 +205,9 @@ private static Optional<ConvertedOpInfo> postprocessFunction(OpInfo info,
206205
info, //
207206
request.getType(), //
208207
preConverters, //
208+
Arrays.asList(request.getArgs()), //
209209
postConverter, //
210+
request.getOutType(), //
210211
null, //
211212
env //
212213
));
@@ -223,30 +224,33 @@ private static Optional<ConvertedOpInfo> postprocessFunction(OpInfo info,
223224
* @param request the original {@link OpRequest}
224225
* @param preConverters the {@link List} of {@link RichOp}s responsible for
225226
* converting the inputs to the {@link ConvertedOpInfo}
226-
* @param identity an Op that simply returns the input value
227227
* @param env the {@link OpEnvironment} used to match Ops necessary to create
228228
* the {@code ConvertedOpInfo}
229229
* @return a {@link ConvertedOpInfo}, aligning {@code info} to {@code request}
230230
* if that is possible
231231
*/
232232
private static Optional<ConvertedOpInfo> postprocessIdentity(OpInfo info,
233233
OpRequest request, List<RichOp<Function<?, ?>>> preConverters,
234-
RichOp<Function<?, ?>> identity, OpEnvironment env)
234+
OpEnvironment env)
235235
{
236236
// This procedure only applies to Ops with mutable outputs
237237
int ioIndex = mutableIndexOf(request.getType());
238238
if (ioIndex == -1) {
239239
return Optional.empty();
240240
}
241241
// And only applies when the mutable index was "converted" with identity
242-
if (preConverters.get(ioIndex) == identity) {
242+
if (Ops.info(preConverters.get(ioIndex)).names().contains(
243+
"engine.identity"))
244+
{
243245
// In this case, we need neither a postprocessor nor a copier,
244246
// because the mutable output was directly edited.
245247
return Optional.of(new ConvertedOpInfo( //
246248
info, //
247249
request.getType(), //
248250
preConverters, //
249-
identity, //
251+
Arrays.asList(request.getArgs()), //
252+
null, //
253+
request.getOutType(), //
250254
null, //
251255
env //
252256
));
@@ -304,7 +308,9 @@ private static Optional<ConvertedOpInfo> postprocessConvertAndCopy(
304308
info, //
305309
request.getType(), //
306310
preConverters, //
311+
Arrays.asList(request.getArgs()), //
307312
postConverter, //
313+
request.getOutType(), //
308314
copyOp, //
309315
env //
310316
));
@@ -365,7 +371,9 @@ private static Optional<ConvertedOpInfo> postprocessCopy(OpInfo info,
365371
info, //
366372
request.getType(), //
367373
preConverters, //
374+
Arrays.asList(request.getArgs()), //
368375
postConverter, //
376+
request.getOutType(), //
369377
copyOp, //
370378
env //
371379
));
@@ -387,15 +395,9 @@ private static Optional<ConvertedOpInfo> postprocessCopy(OpInfo info,
387395
* @param hints the {@link Hints} to use in matching
388396
* @return a rich converter Op, if it is both necessary and can be found
389397
*/
390-
private static Optional<RichOp<Function<?, ?>>> findConverter(Type from,
391-
Type to, Map<TypeVariable<?>, Type> vars, OpEnvironment env, Hints hints)
398+
private static RichOp<Function<?, ?>> findConverter(Type from, Type to,
399+
Map<TypeVariable<?>, Type> vars, OpEnvironment env, Hints hints)
392400
{
393-
// If the request argument can be assigned to the info parameter directly,
394-
// we don't need to call a preconverter
395-
if (Types.isAssignable(from, to)) {
396-
return Optional.empty();
397-
}
398-
// If direct assignment fails, we need a preconverter
399401
var source = Nil.of(from);
400402
// If the op parameter type has type variables that have been mapped
401403
// already, substitute those mappings in.
@@ -412,7 +414,7 @@ private static Optional<ConvertedOpInfo> postprocessCopy(OpInfo info,
412414
// The resulting Op can give us further information about type variable
413415
// mappings - let's find them
414416
resolveTypes(from, preDest, rich, vars);
415-
return Optional.of(Ops.rich(op));
417+
return Ops.rich(op);
416418
}
417419

418420
private static void resolveTypes(Type source, Type dest,
@@ -454,15 +456,6 @@ private static Nil<?> wildcardVacuousTypeVars(final Type t) {
454456
return Nil.of(Types.mapVarToTypes(t, vars));
455457
}
456458

457-
private static <T> RichOp<Function<?, ?>> identityOp(OpEnvironment env) {
458-
Nil<T> t = new Nil<>() {};
459-
var op = env.unary("engine.identity") //
460-
.inType(t) //
461-
.outType(t) //
462-
.function();
463-
return Ops.rich(op);
464-
}
465-
466459
/**
467460
* {@link Class}es of array types return "[]" when
468461
* {@link Class#getSimpleName()} is called. Those characters are invalid in a

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/ConvertedOpInfo.java

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@
3232
import java.lang.invoke.MethodHandles;
3333
import java.lang.reflect.*;
3434
import java.lang.reflect.Modifier;
35-
import java.util.ArrayList;
36-
import java.util.HashMap;
37-
import java.util.List;
38-
import java.util.Map;
35+
import java.util.*;
3936
import java.util.function.Function;
4037
import java.util.stream.Collectors;
4138

@@ -56,6 +53,7 @@
5653
import org.scijava.struct.Struct;
5754
import org.scijava.struct.StructInstance;
5855
import org.scijava.struct.Structs;
56+
import org.scijava.types.Any;
5957
import org.scijava.types.Nil;
6058
import org.scijava.types.Types;
6159
import org.scijava.types.inference.FunctionalInterfaces;
@@ -97,7 +95,9 @@ public class ConvertedOpInfo implements OpInfo {
9795
private final OpInfo info;
9896
private final OpEnvironment env;
9997
final List<RichOp<Function<?, ?>>> preconverters;
98+
final List<Type> inTypes;
10099
final RichOp<Function<?, ?>> postconverter;
100+
final Type outType;
101101
final RichOp<Computers.Arity1<?, ?>> copyOp;
102102
private final Type opType;
103103
private final Struct struct;
@@ -113,8 +113,9 @@ public ConvertedOpInfo(OpInfo info,
113113
info, //
114114
generateOpType(info, preconverters, postconverter), //
115115
preconverters, //
116+
Arrays.asList(inTypes(info.inputTypes(), preconverters)), //
116117
postconverter, //
117-
copyOp, //
118+
outType(info.outputType(), postconverter), copyOp, //
118119
env //
119120
);
120121
}
@@ -133,22 +134,21 @@ public ConvertedOpInfo( //
133134
OpInfo info, //
134135
Type opType, //
135136
List<RichOp<Function<?, ?>>> preconverters, //
137+
List<Type> reqInputs, //
136138
RichOp<Function<?, ?>> postconverter, //
139+
Type reqOutput, //
137140
final RichOp<Computers.Arity1<?, ?>> copyOp, //
138141
OpEnvironment env //
139142
) {
140143
this.info = info;
141-
this.opType = opType;
144+
this.opType = mapAnys(opType, info);
142145
this.preconverters = preconverters;
146+
this.inTypes = reqInputs;
143147
this.postconverter = postconverter;
148+
this.outType = reqOutput;
144149
this.copyOp = copyOp;
145150
this.env = env;
146-
this.struct = generateStruct( //
147-
info, //
148-
opType, //
149-
preconverters, //
150-
postconverter //
151-
);
151+
this.struct = generateStruct();
152152
this.hints = info.declaredHints().plus( //
153153
BaseOpHints.Conversion.FORBIDDEN, //
154154
"converted" //
@@ -158,18 +158,9 @@ public ConvertedOpInfo( //
158158
/**
159159
* Helper method to generate the new {@link Struct}
160160
*
161-
* @param info the original {@link OpInfo}
162-
* @param opType the new Op's {@link Type}
163-
* @param preconverters the {@link Function}s responsible for converting Op
164-
* parameters
165-
* @param postconverter the {@link Function} responsible for converting the
166-
* Op's return
167161
* @return the {@link Struct} of the converted Op
168162
*/
169-
private static Struct generateStruct(OpInfo info, Type opType,
170-
List<RichOp<Function<?, ?>>> preconverters,
171-
RichOp<Function<?, ?>> postconverter)
172-
{
163+
private Struct generateStruct() {
173164
List<Type> originalIns = new ArrayList<>(info.inputTypes());
174165
List<Member<?>> ioMembers = new ArrayList<>(info.struct().members());
175166
// If the mutable index differs between the declared Op type and the
@@ -180,17 +171,13 @@ private static Struct generateStruct(OpInfo info, Type opType,
180171
originalIns.add(toIOIdx, originalIns.remove(fromIOIdx));
181172
ioMembers.add(toIOIdx, ioMembers.remove(fromIOIdx));
182173
}
183-
// Determine the new input and output types of the Op
184-
Type[] inTypes = inTypes(originalIns, preconverters);
185-
Type outType = outType(info.outputType(), postconverter);
186-
187174
// Create the functional member types of the new OpInfo
188175
int index = 0;
189176
List<FunctionalMethodType> fmts = new ArrayList<>();
190177
for (Member<?> m : ioMembers) {
191178
if (m.getIOType() == ItemIO.NONE) continue;
192-
Type newType = m.isInput() ? inTypes[index++] : m.isOutput() ? outType
193-
: null;
179+
Type newType = m.isInput() ? this.inTypes.get(index++) : m.isOutput()
180+
? outType : null;
194181
fmts.add(new FunctionalMethodType(newType, m.getIOType()));
195182
}
196183
// generate new struct
@@ -266,7 +253,7 @@ public StructInstance<?> createOpInstance(List<?> dependencies) {
266253
}
267254
return rich.asOpType();
268255
}).collect(Collectors.toList()), //
269-
this.postconverter.asOpType(), //
256+
this.postconverter == null ? IGNORED : this.postconverter.asOpType(), //
270257
this.copyOp == null ? null : this.copyOp.asOpType() //
271258
);
272259
return struct().createInstance(convertedOp);
@@ -380,6 +367,9 @@ private static Type outType( //
380367
Type originalOutput, //
381368
RichOp<Function<?, ?>> postconverter //
382369
) {
370+
if (postconverter == null) {
371+
return originalOutput;
372+
}
383373
// Start by looking at the output type T of the postconverter
384374
var type = postconverter.instance().getType();
385375
var pType = (ParameterizedType) type;
@@ -410,6 +400,31 @@ public double priority() {
410400
return priority;
411401
}
412402

403+
/**
404+
* Helper method that removes any {@link Any}s in the Op type, replacing them
405+
* with the types declared by the original {@link OpInfo}
406+
*
407+
* @param opType the Op {@link Type}, which may have some {@link Any}s
408+
* @param info the original {@link OpInfo} being wrapped
409+
* @return {@code opType}, without any {@link Any}s.
410+
*/
411+
private static Type mapAnys(Type opType, OpInfo info) {
412+
var raw = Types.parameterizeRaw(Types.raw(opType));
413+
Map<TypeVariable<?>, Type> reqMap = new HashMap<>();
414+
GenericAssignability.inferTypeVariables(new Type[] { raw }, new Type[] {
415+
opType }, reqMap);
416+
Map<TypeVariable<?>, Type> infoMap = new HashMap<>();
417+
GenericAssignability.inferTypeVariables(new Type[] { raw }, new Type[] {
418+
info.opType() }, infoMap);
419+
for (var key : reqMap.keySet()) {
420+
var val = reqMap.get(key);
421+
if (Any.is(val)) {
422+
reqMap.put(key, infoMap.get(key));
423+
}
424+
}
425+
return Types.mapVarToTypes(raw, reqMap);
426+
}
427+
413428
/**
414429
* We define the priority of any {@link ConvertedOpInfo} as the sum of the
415430
* following:

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/IdentityCollection.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
package org.scijava.ops.engine.matcher.convert;
3131

3232
import org.scijava.function.Inplaces;
33+
import org.scijava.ops.engine.BaseOpHints;
3334
import org.scijava.ops.engine.BaseOpHints.Conversion;
3435
import org.scijava.ops.spi.OpCollection;
3536
import org.scijava.ops.spi.OpField;
@@ -44,21 +45,23 @@
4445
* @author Gabriel Selzer
4546
* @param <T>
4647
*/
47-
public class IdentityCollection<T> implements OpCollection {
48+
public class IdentityCollection<T, U extends T> implements OpCollection {
4849

4950
/**
5051
* @input t the object to be converted
5152
* @output the converted object (since we are doing an identity conversion,
5253
* this is just a reference to the input object).
5354
*/
54-
@OpHints(hints = { Conversion.FORBIDDEN })
55-
@OpField(names = "engine.convert, engine.identity", priority = Priority.LAST)
56-
public final Function<T, T> identity = (t) -> t;
55+
@OpHints(hints = { Conversion.FORBIDDEN,
56+
BaseOpHints.DependencyMatching.FORBIDDEN })
57+
@OpField(names = "engine.convert, engine.identity", priority = Priority.FIRST)
58+
public final Function<U, T> identity = (t) -> t;
5759

5860
/**
5961
* @mutable t the object to be "mutated"
6062
*/
6163
@OpHints(hints = { Conversion.FORBIDDEN })
62-
@OpField(names = "engine.identity", priority = Priority.LAST)
64+
@OpField(names = "engine.identity", priority = Priority.FIRST)
6365
public final Inplaces.Arity1<T> inplace = (t) -> {};
66+
6467
}

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/MatchingUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ static int checkGenericOutputsAssignability(Type[] froms, Type[] tos,
105105
Type from = froms[i];
106106
Type to = tos[i];
107107

108-
if (to instanceof Any || to.equals(Any.class)) continue;
108+
if (Any.is(to)) continue;
109109

110110
if (from instanceof TypeVariable) {
111111
TypeVarInfo typeVarInfo = typeBounds.get(from);

scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/convert/ConversionTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import java.lang.reflect.Type;
3535
import java.util.function.BiFunction;
36+
import java.util.function.Function;
3637

3738
import org.junit.jupiter.api.Assertions;
3839
import org.junit.jupiter.api.BeforeAll;
@@ -156,4 +157,16 @@ public void testConvertedOpInfo() {
156157
Assertions.assertEquals(expImplName, actImplName);
157158
}
158159

160+
@OpMethod(names = "test.anyConversion", type = Function.class)
161+
public static Integer foo(Integer i1) {
162+
return i1 * i1;
163+
}
164+
165+
@Test
166+
public void testConvertAnys() {
167+
Double in = 2.0;
168+
var out = ops.unary("test.anyConversion").input(in).apply();
169+
Assertions.assertInstanceOf(Integer.class, out);
170+
}
171+
159172
}

scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftNeighborhoodComputersToImg.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ private LiftNeighborhoodComputersToImg() {
6565
/**
6666
* @implNote op names='engine.adapt', priority='100.', type=Function
6767
*/
68-
public static <T, U, F extends RandomAccessibleInterval<T>>
69-
Computers.Arity3<F, Shape, OutOfBoundsFactory<T, F>, RandomAccessibleInterval<U>>
68+
public static <T, U, F extends RandomAccessibleInterval<T>, G extends F>
69+
Computers.Arity3<G, Shape, OutOfBoundsFactory<T, F>, RandomAccessibleInterval<U>>
7070
adapt1UsingShapeAndOOBF(Computers.Arity1<Neighborhood<T>, U> op)
7171
{
7272
return (in, shape, oobf, out) -> {

0 commit comments

Comments
 (0)