google / cel-go

Fast, portable, non-Turing complete expression evaluation with gradual typing (Go)
https://cel.dev
Apache License 2.0
2.19k stars 218 forks source link

Overloads check for generic parameters is too strict #964

Closed patrickpichler closed 3 months ago

patrickpichler commented 3 months ago

Describe the bug When defining an overload for a function that has multiple generic types, the SignatureOverlaps checker looks at each type parameter in isolation, causing it to reject valid overlaps.

To Reproduce

package main

import (
    "github.com/google/cel-go/cel"
    "github.com/google/cel-go/common/types"
    "github.com/google/cel-go/common/types/ref"
)

func main() {
    paramA := types.NewTypeParamType("A")
    listOfA := types.NewListType(paramA)

    reg := types.NewEmptyRegistry()
    env, err := cel.NewEnv(
        cel.CustomTypeAdapter(reg),
        cel.CustomTypeProvider(reg),
        cel.Function("test",
            cel.Overload("test_generic", []*cel.Type{paramA, listOfA}, types.BoolType),
            cel.Overload("test_uint_list_int", []*cel.Type{types.UintType, types.NewListType(types.IntType)}, types.BoolType),
            cel.SingletonBinaryBinding(func(_, _ ref.Val) ref.Val {
                return types.False
            })),
    )
    if err != nil {
        panic(err)
    }
    ast, iss := env.Compile(`hex(file.Hash)`)
    if iss.Err() != nil {
        panic(iss.Err())
    }
}

Expected behavior Creating the environment should not fail.

Additional context After some initial analysis, I guess the problem is caused by the SignatureOverlap function looking at each instance of the type parameter used in isolation. A is assignable to uint and list(A) is assignable to list(int), even though A in this case should point to a single type.

TristonianJones commented 3 months ago

Indeed, what you're seeing is a signature overlap which would make dispatch ambiguous. If you have a singleton binding, then just use the parameterized overload. The type parameter will decay to dyn so it's possible for the more specific overload to collide with the parameterized type.

patrickpichler commented 3 months ago

The concrete problem we are facing is, that we wanted to add an overload to the in and == operators to allow testing for uint == int. It seems like there is code to compare both types, but it sadly requires a typecast of the uint to int to work.

Do you know of any other way of achieving this (given that we do not want to change our data model types from uint to int if possible)?