Swift Vapor: Type of expression is ambiguous without more context

I have this code below and the compiler gives the error in the title on the .all() line. It seems to indicate that the type is not being inferred but how can I force it to know it's a [UserContent]?

func getContentOfOwners(req: Request) throws -> EventLoopFuture<[UserContent]> {
    let owners = try req.content.decode([User].self)
    let ownerIds = owners.map({ owner in
        owner.id
    })
    
    return UserContent
        .query(on: req.db)
        .filter(\.$owner.$id ~~ ownerIds!)
        .all()
}

I can't say for certain without seeing your models, but what is the inferred type of ownerIds? My guess is that it's [User.id?], and the compiler is actually having issues with the filter, as it's expecting to do the 'contains' operation on [User.id].

Thanks. I tried getting rid of the ownerIds and having a hard coded value on the filter but it did not work. The UserContent model inherits from Model and Content. If I comment out filter the error goes away. So yes it is something to do with filtering. But just not seeing what. Is there no way to indicate the type?

import Vapor
import Fluent

final class UserContent: Model, Content {
static let schema: String = "UserContent"

@ID(key: .id)
var id: UUID?

@Field(key: "Image")
var image: String

@OptionalField(key: "Desc")
var desc: String?

@Parent(key: "User_Id")
var owner: User

@Children(for: \.$parentUserContent)
var comments: [Comment]

@Field(key: "CreatedBy")
var createdBy: String

@Timestamp(key: "CreatedOn", on: .create)
var createdOn: Date?

@Field(key: "LastModifiedBy")
var lastModifiedBy: String

@Timestamp(key: "LastModifiedOn", on: .update)
var lastModifiedOn: Date?

init(){}

init(image: String, desc: String?, owner: User, comments: [Comment]?) {
    self.image = image
    self.desc = desc
    self.$owner.id = owner.id!
    self.comments = comments ?? []
}

}

Try something like this:

let ownerIds: [UUID] = owners.compactMap { $0.id }

That way, you're telling the compiler what you want ownerIds to be, and the compactMap filters out any instances where owner.id would be nil.

What I usually do when I'm trying to figure something like this out is split it up into separate assignments, and then use Xcode to see what the compiler is assuming the type is. If memory serves, you can option-click on the variable name to see the inferred type. So, for example:

let query = UserContent.query(on: req.db)
let filter = query.filter(\.$owner.$id ~~ ownerIds)
let result = filter.all()
return result

And then clicking through each of them to see where it switches from something that makes sense to either something that doesn't or <<< error type >>>

Just tried it did not work. Even making the line .filter(.id == UUID()) gives the same error.

If you do something like

return Usercontent.query(on: req.db).filter(\.$owner.$id == UUID()).all()

does that work?

Moving this question to the “Using Swift” category. The “Development > Compiler” category is intended for discussion on developing the Swift compiler.

1 Like

Sweet! I'm so happy. Thank you. I switched the code like this after I saw your suggestion. So I was trying to force casting at the filter call, but it needed an explicit type at the ownerIds level.

Are you a vapor developer or on their team?

func getContentOfOwners(req: Request) throws -> EventLoopFuture<[UserContent]> {
    let owners = try req.content.decode([User].self)
    let ownerIds: [UUID] = owners.map({ owner in
        owner.id!
    })
    
    return UserContent
        .query(on: req.db)
        .filter(\.$owner.$id ~~ ownerIds)
        .all()
}

Glad to help!

I've been doing a bit of tinkering with Vapor lately, and ran into a pretty similar problem just the other day, so I figured I might be able to help out. :slight_smile:

My last suggestion would just be swapping out

let ownerIds: [UUID] = owners.map({ owner in 
   owner.id!  
})

for

let ownerIds: [UUID] = owners.compactMap({ owner in
    owner.id
})

It's just slightly safer - if one of the owners has a nil id, the map as you have it will crash, while compactMap just skips that one, so you wind up with all non-nil IDs in your array.

Awesome thank you! By way do you know kotlin. Wondering which you prefer?
I'm putting all my eggs into Swift due to recent apple arm announcements, but I prefer kotlin as a language.

Thanks again!

1 Like

I've only ever looked briefly at Kotlin, so I'm definitely not qualified to do a comparison, sorry.

Hey @jsoneaday :)

Glad you were able to solve the problem! But still type of expression is ambiguous without more context is a bad diagnostic from the compiler. So if possible can you please file a bug on https://bugs.swift.org to track down this issue?

Make sure you have import Fluent in the file you're using to perform the filters. The filter custom operators live in the Fluent module, so you won't be able to see them unless you import the module.

@xwu there's a Vapor section in the Related Projects area that this is probably a better place for