Open KingOfBrian opened 1 year ago
cc @xedin
This is happening because the body of vertical
is not solved separately when there is no more information for solver to infer which means that it tries to solve aligning(anchors: [\.bottomAnchor])
first and attempts [Any]
as a contextual type for the argument.
This is not specific to result builders, all multi-statement closures behave that way:
protocol UI {
var bottomAnchor: Int { get set }
}
protocol Alignable {
associatedtype View : UI
}
struct Stack {
static func vertical<T: Alignable>(_ fn: () -> T) -> T {
fn()
}
}
struct AnchorAlignment<Content: Alignable, Anchor>: Alignable {
typealias View = Content.View
}
extension Alignable {
func aligning<Anchor>(anchors: [(View) -> Anchor]) -> AnchorAlignment<Self, Anchor> {
fatalError()
}
}
struct Label : UI {
var bottomAnchor: Int = 42
}
struct StyledString: Alignable {
typealias View = Label
}
enum Test {
func inferenceFailure() -> some Alignable {
Stack.vertical() {
let result = StyledString()
return result
}
.aligning(anchors: [\.bottomAnchor])
}
}
It might be possible to solve this by prioritizing closure inference over a defaulted binding, I'll give it a shot.
Ah, interesting. What change do you think caused this? I sort of defaulted to the ResultBuilder changes since there was a notice of possible breaking source changes.
Do you think this explanation applies to both problems? It makes some sense that closures work instead of key paths, since the closure probably defers or alters the resolution a little.
But for WithFooter seems a little different. The fact that it finds the extension, but doesn't apply the list builder is bizarre. I added another multi-line / non-result builder and it works ok:
extension ViewRepresentable {
func withFooterNoBuilder(build: @escaping () -> [ListItem]) -> WithFooter<Self> {
return .init(body: self, footer: build)
}
}
extension ChainedResultBuilderIgnored {
func noBuilderOk() -> some ViewRepresentable {
Stack.vertical() {
StyledString(content: "Hi")
}
.withFooterNoBuilder {
let useMultiLineClosure = ListItem(body: StyledString(content: "Hi"))
return [useMultiLineClosure]
}
}
}
Result builders are type-checked as regular multi-statement closures with some implicitly added expressions inside, switching to that is what inference mode is what this regression, so a fix would work for both.
The fact that it finds the extension, but doesn't apply the list builder is bizarre.
Yes, this is indeed interesting, I'm trying to figure out why wouldn't it apply the builder in this case.
Okay this is the same kind of problem as other examples, withFooter
closure is resolved too early (before the result type of StackType
is resolved which means that it cannot pick up @ListBuilder
from the overload.
I've reverted the fix because it caused other issues so this has to be re-opened.
Description We have a SwiftUI-ish declarative API built on top of UIKit from pre SwiftUI days, and have evolved it to use resultBuilder APIs. We have a result builder that type checks fine, but a few of our API's that are chained in one expression on the return value of the resultBuilder fails.
From reading about the changes I don’t understand why it breaks. We have a Stack builder API that returns a Stack1 -> StackN types, with N different buildBlock functions in the result builder. The expressions are valid to type check independently, and there’s no side propagation like mentioned here. We do use the builder to determine a generic result type, and that seems to work, and be inline with the OneWay constraint explanation. I'm not sure I understand the nuance of "back-propagation" as explained above tho.
Steps to reproduce Code below, problematic call sites up top, stripped down types below.
Expected behavior I would expect the return value from the resultBuilder to be fully type checked and able to chain additional calls.
Environment