Replacing the Type Checker

Ok, but I still don't understand. Can you be more specific? Though I'm no compilers expert (clearly!) I get that semantic analysis has to do more than just assign types to expressions. It has to check that variables are declared, provide good error messages, etc.

Overload resolution means deciding which of the overloads is actually called, right?

I think we're talking about 2 different things β€”

I think Taylor is asking, why doesn't the typechecker work top-down, or maybe ascription-out, and only lazily expand overload sets, and expand them in the context that can be deduced without such an expansion, preemptively rejecting options that don't unify with what's already known.

In that case, whether there's multiple matching overloads is relevant, and could still result in terrible performance. But here, in the stdlib-only case, you can imagine that the typechecker works in an order such that it always knows that it's dealing with [Double], and therefore never needs to consider [T]+T -> [T] or [] -> Set<T> when expanding the overload sets for the operators/literals.

But I think how the typechecker actually works, is that it just tosses all the possibilities for all of these things into a big pool. So each of those + has to chuck in all of the + in the stdlib, each of those [] has to chuck in all of the ExpressibleByArrayLiteral in the stdlib, and at that point you've just got an enormous pool of possibilities to sort through. But even so, I have to ask, shouldn't it prioritize reductions that use ascribed (or otherwise fully-known) types? Far better than wasting time effectively hypothesizing that [] + [] might be appending a Set<T> to an [Set<T>]?

1 Like

I think the suggestion is for a two-stage "type checker", quick&dirty + exhaustive. But this thread started with an expression that caused the compiler to time-out beause of a simple bug. So the question becomes: can the compiler detect 'simple bugs' in the 'quick&dirty' phase without false postives?

1 Like

Yep. Might be able to combine the two by having the constraint solver do all possible deductive reasoning (until it can no longer make progress) and only then do any guesses. Maybe it already does that and I'm missing something.

My suspicion is that a two-phase type-checker won't help much for the general case, assuming that clever heuristics are already implemented. So I wanted to focus on the specific problem where the compiler times out on a silly little bug. It's more about (SwiftUI) developer efficiency and less about new type systems.

Is the silly little bug you're talking about this one mentioned by @bdkjones ?

Where the problem is actually on line 127 and CueSheet needed to be CueSheet?.

In that case the compiler knows targetCue is of type SheetCue? therefore targetCue?.parentCueSheet is of type CueSheet? (optional chaining) and it should reach the error quickly, I think, if it were to do all possible deductive reasoning before making any guesses. I don't even understand why it times out in that example.

@Slava_Pestov said above:

Sure seems that way.

2 Likes

Yes, and also this one. Could a hypothetical quick&dirty phase find these bugs without false positives?

let address = "127.0.0.1"
let username = "steve"
let password = "1234"
let channel = 11

let url = "http://" + username 
            + ":" + password 
            + "@" + address 
            + "/api/" + channel 
            + "/picture"

print(url)

I suspect so. But first, I don't think there's any risk of false positives. Every deduction has to be correct. The risk is more that the approach isn't very useful.

In that example, amazingly, it still times out if you add all these annotations:

let address: String = "127.0.0.1"
let username: String = "steve"
let password: String = "1234"
let channel: Int = 11

let url: String = String("http://") + username
            + String(":") + password
            + String("@") + address
            + String("/api/") + channel
            + String("/picture")

print(url)

So starting with what we know, that String("http://") is a String and username is a String, so (hopefully!) the only overload that matches is:

func + (lhs: String, rhs: String) -> String

so therefore String("http://") + username is also a String.

then proceeding until we get to lhs + channel where lhs is a String and channel is an Int. Hopefully there is no overload of + that has arguments that unify with (String, Int) so we have an error and we're done. All deductive reasoning and no guesses.

4 Likes

@codafi has a great talk which covers that topic and some other interesting facets of the computational complexity of type checking/inference in Swift.

6 Likes

I didn't realize how much worse it can be in SwiftUI-like projects than in imperative Swift code until very recently, particularly because it compounds with non-Xcode tooling. I was working on a large SwiftCrossUI view the other day (on Linux, so in VS Code), and not only was I getting type checker timeouts for things that should have obviously been wrong (such as passing a URL to a function that expects a String), autocomplete and semantic highlighting also stopped working until the code compiled, which made it unnecessarily difficult to fix. Furthermore, not only did I get the dreaded type checker timeout errors already mentioned in this thread, I even got some "Failed to produce diagnostic" errors. (Though I am on 6.0.2, so I'd have to update to 6.1.1 and check if I can still reproduce it before submitting any bug reports.) When it would produce one vs the other seemed completely arbitrary.

3 Likes

^-- THIS.

I get the impression that Apple and the Swift Compiler team are simply unaware of exactly how bad the compiler's performance in real SwiftUI apps has gotten. The people working on the compiler are (probably) not also working on complex SwiftUI apps, so no dogfooding is happening.

Everyone admits the type-checker's performance is a weakness. But everyone has admitted that for a long time, so I hope to convince the team that improving the type-checker should be a MUCH higher priority.

Simply put: there is NO other feature that Swift could implement that would improve developers' lives more than fixing the type checker's performance in SwiftUI code.

19 Likes

For a concrete and rather surprising example: looking up static variables is orders of magnitude slower than looking up global variables.

Here's the view I mentioned a moment ago: mariokart-optimizer-native/Sources/Views/OptimizationPage.swift at 66304f674a3859a632a880b208e80bc63b4877b9 Β· bbrk24/mariokart-optimizer-native Β· GitHub Clone the repo and this should compile just fine.

Go ahead and type a nonexistent identifier anywhere in the body. Compile it and you get an error pretty much immediately:

$ swift build
Building for debugging...
/home/bbrk24/workspace/mariokart-optimizer-native/Sources/Views/OptimizationPage.swift:599:21: error: cannot find 'asdf' in scope
597 |                     Text(localization.uiElements.resultsTruncatedWarning)
598 | 
599 |                     asdf
    |                     `- error: cannot find 'asdf' in scope
600 |                 }
601 | 
[4/7] Compiling MariokartOptimizer OptimizationPage.swift

Now change that to be a static property of View instead, i.e. View.asdf. Suddenly, the fact that it's a static property confuses the compiler so bad it just gives up:

$ swift build
Building for debugging...
/home/bbrk24/workspace/mariokart-optimizer-native/Sources/Views/OptimizationPage.swift:272:25: error: the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
270 |     }
271 | 
272 |     var body: some View {
    |                         `- error: the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
273 |         if let data {
274 |             VStack {
[4/7] Compiling MariokartOptimizer OptimizationPage.swift

With heavily overloaded declarations like + I get it. Lookup for those is difficult. But for things like this I don't understand why there should be any difference in the performance, let alone one this drastic.

2 Likes

One thing I haven't seen touched upon in this thread (or I've missed it), but: a desirable property of a compiler is good error reporting. Instead of just silently reporting "no this doesn't compile", the Swift type checker (and constituent constraint system) make several attempts to figure out potential solutions to a failed compilation in order to report things like:

  • Ambiguity (are there multiple methods/members which could satisfy what you're trying to access? and if so, which is the most likely one to report?)
  • Typos
  • Mismatched access control, optionality, sendability, throwing, sync/async, etc.
  • Missing conformances

etc. Having the compiler give up and completely bail silently isn't helpful, nor is going the C++ route and outputting reams of "well, here's every single combination I tried, you figure it out".

Doing this work means trying to figure out what the most reasonable solution would be, and reporting that as a likely cause; reporting a failure on the very first attempt to figure things out would end up with error reporting that's beyond useless.

But, this does mean that the compiler needs to wade through a lot of options: potentially all possible methods, properties, associated types, extensions, conformances, etc. on a type, and when multiple types are involved, yes, this requires a lot of work.

A global asdf vs. View.asdf is actually a fantastic example

  • Resolving a "bare" identifier like asdf requires a search upwards through scopes for a top-level name: local variables, members of your local type, members of outer types, the global scope β€” but not recursively back down
  • Resolving View.asdf now opens up the totality of View and all of its conformances, conforming types (of which there are many), extensions (of which there are many more), and so on:
    1. Does any extension define View.asdf as a static member? As an associated type?
    2. Does View.asdf refer to an instance variable/member/method that's being incorrectly applied?
    3. Are there any members anywhere that are close to View.asdf that we can report?

Without having knowledge of this, it absolutely feels arbitrary β€” and certainly, knowing why this happens doesn't actually help you when the compiler does time out. But, this behavior isn't for nothing. And like @Slava_Pestov said a few times up-thread: there are additional optimizations that can be added to the system (and plenty have been made), but it's not perfect. Someone just needs to do it. ("Just")

ETA: theoretically, one reasonable way to bridge the gap here is: on "timeout", the compiler could report "I stopped checking for possible solutions because I likely couldn't find anything at this point, but based on what I do have access to, here are some possible warnings/errors to point you in a better direction". (Diagnosed as notes instead of warnings/errors so as to not spew errors.)

11 Likes

Also, there's something to be said here for how SwiftUI absolutely pushes the generics system to its limits β€” this is pretty cleverly hidden behind result builders, view builders, and opaque types, but here's the actual type tree of a pretty simple, not-terribly-nested, real-world member of a View type I pulled at random from a production app:

AnyView(VStack<TupleView<(ModifiedContent<HStack<TupleView<(ModifiedContent<Text, _EnvironmentKeyWritingModifier<Optional<Int>>>, Spacer, ModifiedContent<ModifiedContent<Text, _ForegroundStyleModifier<Color>>, AccessibilityAttachmentModifier>, Optional<ModifiedContent<ModifiedContent<_ConditionalContent<_ConditionalContent<ModifiedContent<ModifiedContent<_ConditionalContent<Image, Image>, _AspectRatioLayout>, _FrameLayout>, ModifiedContent<ModifiedContent<_ConditionalContent<Image, Image>, _AspectRatioLayout>, _FrameLayout>>, _ConditionalContent<ModifiedContent<ModifiedContent<_ConditionalContent<Image, Image>, _AspectRatioLayout>, _FrameLayout>, ModifiedContent<_ConditionalContent<Image, Image>, _FrameLayout>>>, _ForegroundStyleModifier<Color>>, _PaddingLayout>>)>>, _ForegroundStyleModifier<Color>>, Optional<SpendingBreakdownProgressBar>)>>(_tree: SwiftUI._VariadicView.Tree<SwiftUI._VStackLayout, SwiftUI.TupleView<(SwiftUI.ModifiedContent<SwiftUI.HStack<SwiftUI.TupleView<(SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<Swift.Int>>>, SwiftUI.Spacer, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI.AccessibilityAttachmentModifier>, Swift.Optional<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI._PaddingLayout>>)>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, Swift.Optional<Ο„>)>>(root: SwiftUI._VStackLayout(alignment: SwiftUI.HorizontalAlignment(key: SwiftUI.AlignmentKey(bits: 4)), spacing: Optional(8.0)), content: SwiftUI.TupleView<(SwiftUI.ModifiedContent<SwiftUI.HStack<SwiftUI.TupleView<(SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<Swift.Int>>>, SwiftUI.Spacer, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI.AccessibilityAttachmentModifier>, Swift.Optional<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI._PaddingLayout>>)>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, Swift.Optional<Ο„>)>(value: (SwiftUI.ModifiedContent<SwiftUI.HStack<SwiftUI.TupleView<(SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<Swift.Int>>>, SwiftUI.Spacer, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI.AccessibilityAttachmentModifier>, Swift.Optional<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI._PaddingLayout>>)>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>(content: SwiftUI.HStack<SwiftUI.TupleView<(SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<Swift.Int>>>, SwiftUI.Spacer, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI.AccessibilityAttachmentModifier>, Swift.Optional<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI._PaddingLayout>>)>>(_tree: SwiftUI._VariadicView.Tree<SwiftUI._HStackLayout, SwiftUI.TupleView<(SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<Swift.Int>>>, SwiftUI.Spacer, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI.AccessibilityAttachmentModifier>, Swift.Optional<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI._PaddingLayout>>)>>(root: SwiftUI._HStackLayout(alignment: SwiftUI.VerticalAlignment(key: SwiftUI.AlignmentKey(bits: 7)), spacing: Optional(0.0)), content: SwiftUI.TupleView<(SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<Swift.Int>>>, SwiftUI.Spacer, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI.AccessibilityAttachmentModifier>, Swift.Optional<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI._PaddingLayout>>)>(value: (SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<Swift.Int>>>(content: SwiftUI.Text(storage: SwiftUI.Text.Storage.verbatim("..."), modifiers: [SwiftUI.Text.Modifier.font(Optional(SwiftUI.Font(provider: SwiftUI.(unknown context at $1d4c016c0).FontBox<SwiftUI.Font.(unknown context at $1d4c1528c).ModifierProvider<SwiftUI.Font.WeightModifier>>)))]), modifier: SwiftUI._EnvironmentKeyWritingModifier<Swift.Optional<Swift.Int>>(keyPath: \EnvironmentValues.<computed 0x00000001d47246f0 (Optional<Int>)>, value: Optional(1))), SwiftUI.Spacer(minLength: Optional(16.0)), SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI.AccessibilityAttachmentModifier>(content: SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>(content: SwiftUI.Text(storage: SwiftUI.Text.Storage.verbatim("..."), modifiers: [SwiftUI.Text.Modifier.font(Optional(SwiftUI.Font(provider: SwiftUI.(unknown context at $1d4c016c0).FontBox<SwiftUI.Font.(unknown context at $1d4c1528c).ModifierProvider<SwiftUI.Font.WeightModifier>>)))]), modifier: SwiftUI._ForegroundStyleModifier<SwiftUI.Color>(style: primary)), modifier: SwiftUI.AccessibilityAttachmentModifier(storage: SwiftUI.MutableBox<SwiftUI.AccessibilityAttachment>, behavior: nil)), Optional(SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>, SwiftUI._PaddingLayout>(content: SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>, SwiftUI._ForegroundStyleModifier<SwiftUI.Color>>(content: SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>(storage: SwiftUI._ConditionalContent<SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>, SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._FrameLayout>>>.Storage.trueContent(SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>(storage: SwiftUI._ConditionalContent<SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>, SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>>.Storage.trueContent(SwiftUI.ModifiedContent<SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>, SwiftUI._FrameLayout>(content: SwiftUI.ModifiedContent<SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>, SwiftUI._AspectRatioLayout>(content: SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>(storage: SwiftUI._ConditionalContent<SwiftUI.Image, SwiftUI.Image>.Storage.trueContent(SwiftUI.Image(provider: SwiftUI.ImageProviderBox<SwiftUI.Image.(unknown context at $1d4c0d0fc).RenderingModeProvider>))), modifier: SwiftUI._AspectRatioLayout(aspectRatio: nil, contentMode: SwiftUI.ContentMode.fit)), modifier: SwiftUI._FrameLayout(width: Optional(16.0), height: Optional(16.0), alignment: SwiftUI.Alignment(horizontal: SwiftUI.HorizontalAlignment(key: SwiftUI.AlignmentKey(bits: 2)), vertical: SwiftUI.VerticalAlignment(key: SwiftUI.AlignmentKey(bits: 7))))))))), modifier: SwiftUI._ForegroundStyleModifier<SwiftUI.Color>(style: <UIDynamicProviderColor: 0x6000002aa380; provider = <__NSMallocBlock__: 0x600001085680>>)), modifier: SwiftUI._PaddingLayout(edges: SwiftUI.Edge.Set(rawValue: 2), insets: Optional(SwiftUI.EdgeInsets(top: 4.0, leading: 4.0, bottom: 4.0, trailing: 4.0))))))))), modifier: SwiftUI._ForegroundStyleModifier<SwiftUI.Color>(style: <UIDynamicProviderColor: 0x6000002a98c0; provider = <__NSMallocBlock__: 0x6000010a8b70>>)), nil)))))

For fun, try to time how long it takes you to scroll to the end using your scroll wheel without dragging the scroll bar!

11 Likes

I'm confused. That's the whole point of the thread. "Good error reporting" is precisely what we want! We want the compiler to catch and report in SwiftUI code the same sort of simple, stupid errors that it easily catches in non-SwiftUI code. The thread has many examples: typos, incorrect function parameter names (forID: vs withID:), missing optionality, etc.

Outside of SwiftUI, these things are trapped instantly with accurate, useful diagnostics. Inside SwiftUI they are not. Absolutely trivial mistakes produce can't type-check in reasonable time, which is essentially the compiler giving up and silently bailing.

Right. And that worked before SwiftUI came along. But SwiftUI has exponentially increased the amount of that work that the type-checker must do, which frequently leads to a situation where the compiler is useless as a diagnostic tool if SwiftUI is involved.

In short, the type-checker can't keep up anymore.

You've done a great job pointing out why that's the case. It's a hard problem. But how do we get to a type-checker that does perform well in SwiftUI? Because that's sorely needed.

5 Likes

Sorry, you're right β€” this was poor phrasing on my part. What I meant was: I haven't seen good error reporting being discussed as one constraint on the implementation of the type checker, and one limitation on why things are the way they are.

The initial premise of the thread is "theoretically, could we replace the type checker with something different (possibly radically simpler) that's significantly more efficient for compiling SwiftUI code?", but that's a very different question from "could we replace the type checker with something more efficient and which provides just as high-quality (or ideally, better) diagnostics?" asked later, and I think most of the discussion has revolved around the former when we want the latter.

What I'm trying to say is: in my mind, this framing is backwards β€” it's not that the type checker has backed the language into a corner, it's that the fundamental design of SwiftUI that's hitting algorithmic limits of what's reasonably possible, and if you want to significantly improve performance, something has to give: whether it's diagnostics, language capabilities, or otherwise.

I think the problem is far worse than exponential (in the colloquial sense), to be honest; SwiftUI embeds an entire arbitrarily-deep tree of types in the type system that's much much deeper than folks realize, but we expect it to "just work".

I like this framing a lot:

My question is: "If you were designing a successor to the Swift type checker, could you design something for the limits that SwiftUI imposes, and if so, what would you have to give up to get there? Would it be reasonable to evolve Swift toward those choices?"

Looking at the snippet of SwiftUI types above, I'm not aware of any type system which regularly has to represent something of that complexity (save maybe for C++), or is designed with it in mind. I suspect the language would be very different if we were optimizing for that instead, but I'm not sure it's something we'd be happier with (see also: C++).

Now, this doesn't help with the day-to-day limitations of what you might have to work with β€” I just don't necessarily see the answer to "if we redesigned it from scratch, what could we get?" as being radically different unless you were willing to give up something pretty fundamental.

In a practical sense, I think this is a game of tradeoffs. The question I see is: what are we willing to give up in order to work within the constraints we've got? (SwiftUI clearly isn't going away, or going to fundamentally change its design, or get radically simpler somehow.) What's out of the question? (e.g., source compatibility) If we identify the specifics of common failure points in day-to-day code and have to make tradeoffs, what tradeoffs are we willing to make? Is it okay for the compiler to "time out" much more often if it means that diagnostics get a little bit better? How much better?

I don't have any answers, and don't know what specific common failure patterns look like internally to help identify what tradeoffs might need to be made β€” and certainly, I could be entirely wrong about the problem space here. But I think there isn't going to be a magic bullet here, and most of the work is going to continue to be incremental.

10 Likes

Yeah, there's a lot of complexity the compiler's working through here. And coming up with useful diagnostics is useful.

But right now, the error message we get doesn't even tell us that View.asdf was the problem; the error points to the start of the enclosing view builder. Even if the compiler just said "I timed out when looking up possible solutions for View.asdf, it would be a huge improvement. But the compiler currently doesn't even point us to the right line of code, so when you get a problem in a view builder, it becomes a binary search problem to find the problematic line.

8 Likes

Agreed! Hence the paragraph at the end:

This could trivially include the actual offending expression.

2 Likes

Deeply nested generic types are not really that difficult from a computational standpoint. Think of it this way -- your type encodes a view in quite some detail, but it's sort of linear (or maybe quadratic at worst) in the size of the source code that defines the view.

Also being a tree data structure you can imagine that operations like type substitution should be mostly linear or quadratic in the number of nodes.

So in fact, you can construct arbitrarily complicated generic types, but as long as you don't overload any generic functions, you get a constraint system without any disjunctions. There are no choices to make; you just solve constraints.

The step change in the nature of the problem happens once you add type-based overloading: as soon as f(x) can refer to one of two declarations named f that take different argument types, the constraint system becomes much harder to reason about, than if f(x) was generic, but not overloaded.

13 Likes

Agree with Slava. AFAIK for SwiftUI specifically the number of overloads is one of the biggest problems.
e.g. Group.init(content:) has 11 overloads and ForEach.init(_:content:) has 14 overloads which IIUC all (or most?) need to be considered.

6 Likes