This code highlights an issue with type argument inference:
operation` Foo<'T> (arg : 'T) : Unit { }
operation DoTwo<'A, 'B>(op1 : ('A => Unit), arg1 : 'A, op2: ('B => Unit), arg2 : 'B) : Unit {
op1(arg1);
op2(arg2);
}
operation CombineTwo<'A, 'B>(op1 : ('A => Unit), op2: ('B => Unit)) : (('A, 'B) => Unit) {
return DoTwo(op1, _, op2, _);
}
operation Main() : Unit {
(CombineTwo(Foo, Foo))("Hi", 3);
}
The expression in the Main operation has an issue here. The type parameter resolution data structure in the syntax tree doesn't explicitly contain any way of keeping track of which instance of a type parameter the resolution is for. It only contains the callable name, the type parameter name, and the resolving type. In the code above, in Main, the expression has two conflicting resolutions because it is trying to resolve Foo.T to String and its trying to resolve Foo.T to Int. It sees this conflict because it can't differentiate between the resolution for the first Foo and the resolution for the second Foo. As a result it throws an error complaining about ambiguity.
However if we instead have two different functions passed into CombineTwo:
operation Bar<'T> (arg : 'T) : Unit { }
operation Main() : Unit {
(CombineTwo(Foo, Bar))("Hi", 3);
}
Now there is no more conflict because it sees Foo.T resolving to String and Bar.T resolving to Int. The fact that one of these scenarios is supported and not the other is the issue.