Closure tuple does not support destructuring leads to (arg) inlet?

Help! I have no idea what's going on here. In an XC unit test:

import PMKAlamofire
...
	func
	testScan()
	{
		let exp = expectation(description: "testScan");
		
		Alamofire.request("http://localhost:8080/startSweep?uuid=\(uuid)", method: .get)
			.responseJSON()
			.then
			{ inJSON, inResp in
			}
			.catch
			{ inError in
				XCTAssert("Error from startSweep \(inError)")
			}
		
		waitForExpectations(timeout: 5.0, handler: nil);
	}

When I attempt to compile that, I get the error "Closure tuple parameter '(json: Any, response: PMKAlamofireDataResponse)' does not support destructuring". I first tried changing it to (inJSON, inResp), but that gives me the same error. So I let the Xcode apply the fix (which is offered as: Replace '(inJSON, inResp) in' with '(arg) inlet (inJSON, inResp) = arg; '). It rewrites the code as:

Alamofire.request("http://localhost:8080/startSweep?uuid=\(uuid)", method: .get)
	.responseJSON()
	.then
	{ (arg) inlet (inJSON, inResp) = arg;
	}
	.catch
	{ inError in
		XCTAssert("Error from startSweep \(inError)")
	}

Which gives me two errors: "Consecutive statements on a line must be separated by ';'", the proffered fix of which is insert ';', and "Contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored," the proffered fix of which is insert '_ in'. Neither of which work.

Googling for "swift language inlet" returns nothing useful.

Xcode 10.2.1, Swift 5.

UPDATE: OH! It's in let, not inlet.

If applying a fix in Xcode did leave you with inlet instead of in let then that seems worth a bug report.

2 Likes

Done. FB6450077.

Not sure why I can't just have a deconstructed tuple in there, or even (inJSON, inResp) in

1 Like

There could be other reasons as well, but AFAIK, it confuses type checker heavily.

Are you trying to pass closure of 2 parameters, or closure of 1 parameter of type (foo, bar)? Those kind of thing.

We could technically wrap everything in another parenthesis, ie. ((foo, bar)), to signify destructuring (not supported, need Swift Evolution), but I still don't trust that parenthesizing single element does what I want it to do.

So first of all, I have seen this error quite often when it wasn't warranted, the real problem was something unrelated, usually mismatching types later in the expression, and when I fixed that the desctructuring error disappeared.

Secondly, what Swift is trying to do is assign your arguments as a tuple to "arg" with

arg in

and then on the next row splat it to whatever it was you actually wanted, in this case

let (inJSON, inResp) = arg

It is a bit strange that this should even help, and I think tuples worked slightly better in earlier versions of Swift, they were more generous with the implicit splatting, but as I said most of the time when I see this error I don't actually have to manually destructure anything.

It’s sad, but it does help due to the combinatorial nature of the problem at hands (Type Inference).

If you force the closure argument size to be exactly 2, you can safely ignore all other candidates.
Say you have 2 candidates in the beginning A1 takes 1 parameter, and A2 takes 2 parameter. Now you restrict the closure to take exactly 2 parameters, you can safely ignore A1 and never need to try it with all other combinations, cutting the checking time be a factor of 2.

It’s like you split a complex expression:

let result = a + b * c - (d + e) / f

Into

let partial1 = a + b * c
let partial2 = (d + e) / f
let result = partial1 - partial2

It does nothing to the computation (plus-minus type difference), but everything to the type checker. And closure’s argument is a vantage point for that.

Part of the problem is that it's still so hard to infer what Swift really wants. Between Xcode not being very reliable with its code completion, and the diagnostic messages not providing enough information, combined with the type inference engine failing in mysterious ways that often result in red herrings, it can be very challenging sometimes to figure out why code doesn't compile.

1 Like

When you put it like that, it almost seems like it should be easier for the compiler to figure out the original one, because the developer explicitly says that the tuple has two members, right in the "interface" to the outside. And you can also use that information inside the closure itself, so it seems like it would be a net improvement for the compiler?

That’s right. If closure is multi-statement, all statements inside won’t participate in the type inference. It does make sense when you remember that Swift only infers a single expression, one at a time; mostly to avoid massive constraint system. Multi-stmt closure is the antithesis of that. I explicitly said multi-stmt closure because we kinda throw in freebies for single statement ones.

Instead, Swift infers the parent of the closure (likely the function that uses it), then use that inferred type to further work with the closure itself. That’s why we must be able to infer parent’s type unambiguously before working with closures. Ambiguous number of argument is working against that, to the point that it may fail the inference.

Back to the topic. We can add support for explicit destructuring in argument list, but the existing syntax works because all are required to have outermost parentheses. Closure argument list kinda drop that, soooooo it’s a liiiiittle tricky to get the syntax right. Design could become rather complex for a design that aims at simple use site.