Roadmap for improving the type checker

More The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions from the SwiftUI world:

struct ExpensesList: View {
    
//    @Environment(\.modelContext) private var modelContext
    
    let expenses: [Expense]
    
    var groupedExpenses: [(key: Date, value: [Expense])] {
        Dictionary(grouping: expenses) {
            Calendar.current.startOfDay(for: $0.date)
        }
        .sorted { $0.key > $1.key }
    }
    
    var body: some View { // ❌ The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
        List {
            ForEach(groupedExpenses, id: \.key) { (date, expenses) in
                Section {
                    ForEach(expenses) { expense in
                        NavigationLink {
                            ExpenseView(expense: expense)
                        } label: {
                            HStack {
                                Text(expense.name)
                                Spacer()
                                if expense.receipt == nil {
                                    Image(systemName: "exclamationmark.circle.fill")
                                        .foregroundStyle(.yellow)
                                        .symbolRenderingMode(.hierarchical)
                                }
                                Text(expense.amount.formatted(.currency(code: "BRL")))
                                    .monospacedDigit()
                                    .foregroundColor(.secondary)
                            }
                        }
                        .swipeActions(edge: .trailing) {
                            Button(
                                "Apagar",
                                systemImage: "trash",
                                role: .destructive
                            ) {
                                modelContext.delete(expense) // 🟑 ERROR EXPECTED HERE
                            }
                        }
                    }
                } header: {
                    LabeledContent {
                        let total = (expenses.reduce(0) { $0 + $1.amount })
                        Text(total.formatted(.currency(code: "BRL")))
                            .foregroundStyle(.tertiary)
                    } label: {
                        Text(date.formatted(date: .abbreviated, time: .omitted))
                            .font(.subheadline)
                    }
                    .padding(.trailing)
                }
            }
        }
    }
    
}

Small changes, like removing header content, causes strange errors:

Section {
    ForEach(expenses) { expense in // ❌ Cannot convert value of type '[Expense]' to expected argument type 'Range<Int>'
        NavigationLink {
            ExpenseView(expense: expense) // ❌ Cannot convert value of type 'Int' to expected argument type 'Expense'
        } label: {
            HStack {
                Text(expense.name) // ❌ Value of type 'Int' has no member 'name'
                Spacer()
                if expense.receipt == nil {
                    Image(systemName: "exclamationmark.circle.fill")
                        .foregroundStyle(.yellow)
                        .symbolRenderingMode(.hierarchical)
                }
                Text(expense.amount.formatted(.currency(code: "BRL")))
                    .monospacedDigit()
                    .foregroundColor(.secondary)
            }
        }
        .swipeActions(edge: .trailing) {
            Button(
                "Apagar",
                systemImage: "trash",
                role: .destructive
            ) {
                modelContext.delete(expense) // 🟑 ERROR EXPECTED HERE
            }
        }
    }
}

With simple view in the ForEach the compiler reports and error in the right place:

ForEach(expenses) { expense in
    Text(expense.name)
        .swipeActions(edge: .trailing) {
            Button(
                "Apagar",
                systemImage: "trash",
                role: .destructive
            ) {
                modelContext.delete(expense) // ❌ Value of type '(ModelContext) -> some View' has no member 'delete'
            }
        }
}

Still not the best in this case because View has a modelContext(_ modelContext: ModelContext) -> some View modifier.

If it’s another word, like context, it correctly reports ❌ Cannot find 'context' in scope.

4 Likes

We have this particular problem on our radar to fix. Missing members have to be treated more aggressively otherwise they cause all sorts of issues you describe.

1 Like

In constraint-solving systems I’ve seen in the past, sometimes reducing/rewriting/simplifying the constraints before or during solving improves results. Has that been considered? Any takes on whether it would be useful?

Yes, that’s what we generally do (i.e. types are getting simplified down and constraints decay into other simpler constraints) and in disjunction optimizer for disjunctions in particular (where some choices get inferred as most likely to match and if they match the disjunction is considered solved).

4 Likes