Skip to content

Add Vim9 generic function support#17313

Closed
yegappan wants to merge 1 commit intovim:masterfrom
yegappan:generics
Closed

Add Vim9 generic function support#17313
yegappan wants to merge 1 commit intovim:masterfrom
yegappan:generics

Conversation

@yegappan
Copy link
Member

@yegappan yegappan commented May 14, 2025

A generic function definition:

def Foo<A, B>(arg1: A, arg2: B): B
     var x: A = arg1
     return arg2
enddef

A generic function invocation:

Foo<string, number>('abc', 10)

The following are supported:

  1. Generic def method.
  2. Generic funcref
  3. Generic object/class method

The following are not yet supported:

  1. generic lambda
  2. generic class

@zzzyxwvut
Copy link
Contributor

Another huge feature in the works, thank you.

Can there be more than 26 capital letters to draw a type
variable name from? Maybe some trailing digits can be
optionally allowed after the first and only letter. It may
aid grouping multiple identifiers for a common type, e.g.:

(example_a.vim)
vim9script

def Add<T1, T2, T3>(x: T1, y: T2): T3
    return <T3>(x + y)
enddef

echo Add<number, number, number>(1, 2)
echo Add<float, float, float>(1.0, 2.0)

If there are plans to gradually introduce support for
generic classes, methods, and lambda expressions, it will
soon become more feasible to use up all 26 letters, e.g.:

(example_b.vim)
vim9script

class M
endclass

class N
endclass

class O
endclass

class P
endclass

class Q
endclass

export class Framework<A, B, C, D, E>
    def _new(F: func, G: func)
        throw 'TODO'
    enddef

    static def Init<I, J, K, L, R>(F: func, G: func, H: func): Framework<I, J, K, L, R>
        return Framework._new<I, J, K, L, R>(
            F((t: <T>, u: <U>, v: <V>, w: <W>) => H(t, u, v, w)),
            G((s: <S>, x: <X>, y: <Y>, z: <Z>) => H(s, x, y, z)))
    enddef
endclass

Can these issues be looked into?

(issues_c.vim)
vim9script

interface I
    def string(): string
endinterface

class A
endclass

class B extends A implements I
    def string(): string
        return "B"
    enddef
endclass




# FIXME: This declaration is ambiguous (in the presence of class A)  
# and should be rejected because the type variable A cannot be used  
# in the function body unless it shadows the identifier of class A.   
# In other words, another type variable name should be chosen (see   
# below) or the function should lose its generic variable and become  
# a non-generic function (see further below).
def GenericString1<A>(o: A): string
    return o.string()
enddef

defcompile GenericString1




def GenericString2<T>(o: T): string
    return o.string()
enddef

echo GenericString2<B>(B.new())

# OK...
echo GenericString2<I>(B.new())
# FIXME: E1325?  The method dispatch works for "I" (above) and doesn't  
# work for "A".  Either reject both calls since subtype instances "B"  
# are passed or correct dispatching for "A".
echo GenericString2<A>(B.new())




def NonGenericString(o: any): string
    return string(o)
enddef

# FIXME: No parsing error?
echo NonGenericString<number>(null)

@vimpostor
Copy link
Contributor

Foo<string, number>('abc', 10)

I wonder if it would be feasible to implement type inference (maybe later, not necessarily now):

Foo('abc', 10)
" or maybe
Foo<>('abc', 10)

@yegappan yegappan force-pushed the generics branch 5 times, most recently from ed77edd to fd5fb23 Compare May 20, 2025 14:30
@ScriptScorpion
Copy link

is it just new structure of function in vim9script?

@ScriptScorpion
Copy link

WOW no errors

@yegappan
Copy link
Member Author

is it just new structure of function in vim9script?

A generic function allows you to operate on different types of data without losing type checks and
reduces code duplication. For a general description of this in different programming languages,
you can refer to the following pages:

https://en.wikipedia.org/wiki/Generic_function
https://www.typescriptlang.org/docs/handbook/2/generics.html
https://docs.oracle.com/javase/tutorial/java/generics/types.html
https://learn.microsoft.com/en-us/cpp/extensions/generics-cpp-component-extensions?view=msvc-170
https://dart.dev/language/generics
https://go.dev/doc/tutorial/generics

@yegappan yegappan force-pushed the generics branch 14 times, most recently from c7cfaca to 75ea81b Compare May 28, 2025 04:33
@yegappan yegappan force-pushed the generics branch 3 times, most recently from 0ff2ce8 to dac21eb Compare May 31, 2025 05:57
@errael
Copy link
Contributor

errael commented Jul 2, 2025

Repeated sourcing of these scripts crashes Vim (1a3352666):
issue_i.vim

issue_j.vim

I wonder if the problem from #17313 (comment) comes from the same place? (except that one doesn't crash vim, it gets a runtime error)

@yegappan
Copy link
Member Author

yegappan commented Jul 2, 2025

Not sure this is a bug, but something feels wrong.

Yes. This is a bug. Good catch. I have updated the PR to address this issue.

Consider

vim9script

export def Foo<A>(a: list<A>)
    echo 'Foo:' a typename(a)
enddef

Foo<list<dict<any>>>([[{}]])

Open this in an editor and source it several times, it works fine.

Doing something similar involving import gives different results.

Create file t2.vim t2.vim

vim9script

export def Foo<A>(a: list<A>)
    echo 'Foo:' a typename(a)
enddef

Then edit a file containing

vim9script

import "./t2.vim"

t2.Foo<list<dict<any>>>([[{}]])

Source the file and see output

Foo: [[{}]] list<list<dict<any>>>

Source the file again and there's an error

Error detected while processing /home/err/play/generics/t2b.vim:
line    5:
E1013: Argument 1: type mismatch, expected list<unknown> but got list<list<dict<any>>>

@errael
Copy link
Contributor

errael commented Jul 2, 2025

Repeated sourcing of these scripts crashes Vim (1a3352666):
issue_i.vim
issue_j.vim

I wonder if the problem from #17313 (comment) comes from the same place? (except that one doesn't crash vim, it gets a runtime error)

Oh, well. The crash is still there. But the import runtime error is fixed

@zzzyxwvut
Copy link
Contributor

It is nice that constructors can be parameterised like other
functions and methods:

(example_k.vim)
vim9script

class Pair
    const _fst: any
    const _snd: any

    def new<T, U>(fst: T, snd: U)
        this._fst = fst
        this._snd = snd
    enddef

    def First<T>(): T
        return this._fst
    enddef

    def Second<T>(): T
        return this._snd
    enddef

    def string(): string
        return printf("(%s, %s)", this._fst, this._snd)
    enddef
endclass

echo Pair.new<string, string>('hello', 'world')

Creating enum values with a generic constructor then doesn't
seem far-fetched (E1123):

(example_l.vim)
vim9script

enum CommonPair
    HelloWorld<string, string>('hello', 'world'),
    Booleans<bool, bool>(true, false)

    const _fst: any
    const _snd: any

    def new<T, U>(fst: T, snd: U)
        this._fst = fst
        this._snd = snd
    enddef

    def First<T>(): T
        return this._fst
    enddef

    def Second<T>(): T
        return this._snd
    enddef

    def string(): string
        return printf("(%s, %s)", this._fst, this._snd)
    enddef
endenum

echo CommonPair.HelloWorld
echo CommonPair.Booleans

@errael
Copy link
Contributor

errael commented Jul 4, 2025

It is nice that constructors can be parameterised like other functions and methods:

In the future there might be generic classes, something like

class Pair<T, U>

Are there any considerations now to insure that adding generic classes in the future won't run into syntactic issues to make a class generic if there are already generic functions in the class? I guess the general case is that a function has both type variables from the class and some of it's own.

@yegappan
Copy link
Member Author

yegappan commented Jul 4, 2025

Repeated sourcing of these scripts crashes Vim (1a3352666):

Thanks for reporting these issues. This should be addressed by the latest PR.
Can you try the latest PR?

issue_i.vim

vim9script

abstract class A
    abstract def Id<T>(o: T): T
endclass

abstract class B extends A
    abstract def Id<U>(o: U): U
endclass

issue_j.vim

vim9script

abstract class A
    abstract def Id<U>(o: U): U
endclass

class B extends A
    def Id<T>(o: T): T
        return o
    enddef
endclass

@yegappan
Copy link
Member Author

yegappan commented Jul 4, 2025

Repeated sourcing of these scripts crashes Vim (1a3352666):
issue_i.vim
issue_j.vim

I wonder if the problem from #17313 (comment) comes from the same place? (except that one doesn't crash vim, it gets a runtime error)

No. These two issues are different. I have addressed the issue with extending generic class
methods in the latest PR.

@yegappan
Copy link
Member Author

yegappan commented Jul 4, 2025

It is nice that constructors can be parameterised like other functions and methods:

In the future there might be generic classes, something like

class Pair<T, U>

Are there any considerations now to insure that adding generic classes in the future won't run into syntactic issues to make a class generic if there are already generic functions in the class? I guess the general case is that a function has both type variables from the class and some of it's own.

I will work on the generic class support after the current PR is merged. I don't expect
the syntax of the generic class methods to change. The syntax will be:

class MyClass<A, B>
    def SomeMethod<T>(t: T, a: A): B
       ....
    enddef
endclass

@zzzyxwvut
Copy link
Contributor

A couple more issues:

(issue_m.vim)
vim9script

def Id_<T>(o: T): T
    return o
enddef

class Test
    def Id<U>(o: U): U
        return Id_<U>(o)
    enddef
endclass


echo 1 "." == Id_<string>(".")
echo 2 0 == Id_<number>(0)
echo 3 !true == Id_<bool>(false)
echo 4 !true == Id_<bool>(false)
echo 5 0 == Id_<number>(0)
echo 6 "." == Id_<string>(".")


# FIXME: The initial call type is retained for other calls.
#	Use "any" as a fix.
##### echo 0 [] == Test.new().Id<any>([])

echo 1 "." == Test.new().Id<string>(".")
# FIXME: E1013.
echo 2 0 == Test.new().Id<number>(0)
# FIXME: E1013.
echo 3 !true == Test.new().Id<bool>(false)
(issue_n.vim)
vim9script

def Id<T>(o: T): T
    return o
enddef

class F
    def Id1<T>(o: T): T
        return o
    enddef

    static def Id2<T>(o: T): T
        return o
    enddef
endclass

class String
    const value: string

    def newValue<T>(value: T)
        this.value = string(value)
    enddef

    def string(): string
        return this.value
    enddef
endclass

const hello: string = Id<string>('hello')
const blank: string = F.Id2<string>(' ')
const world: string = F.new().Id1<string>('world')
const bang: string = String.newValue<string>('!').string()

class Test
    def Id<T>(o: T): T
        return o
    enddef

    # (Calling here the script-local "Id" function.)
    const name: string = Id<string>('Test')

    # FIXME: E488.
    const hello: string = this.Id<string>('hello')
    const blank: string = F.Id2<string>(' ')
    const world: string = F.new().Id1<string>('world')
    const bang: string = String.newValue<string>('!').string()
endclass

@yegappan
Copy link
Member Author

yegappan commented Jul 7, 2025

A couple more issues:

(issue_m.vim)

(issue_n.vim)

Thanks for reporting these issues. These should be addressed in the latest updated PR.
Can you try the latest PR?

@zzzyxwvut
Copy link
Contributor

Set and List operations can now be generified (see below)!

Supporting generic constructors for enums is hardly of any
priority right now, but I hope, together with protected
constructors
for abstract classes and enums, this may be
revisited at a later date.

Congratulations and thank you for developing this feature,
@yegappan.


(list.vim)
vim9script

# See https://github.com/vim/vim/pull/16604#issuecomment-265202845 .
export interface Listable
    def Cons<E>(_: E): Listable
    def Reverse<E>(): Listable
    def Rest(): Listable
    def First<E>(): E
    def empty(): bool
    def len(): number
    def string(): string
endinterface

enum EmptyList implements Listable
    INSTANCE

    def Cons<E>(value: E): Listable
	return List.new<E>(value)
    enddef

    def Reverse<E>(): Listable
	return this
    enddef

    def Rest(): Listable
	return this
    enddef

    def First<E>(): E
	return null
    enddef

    def empty(): bool
	return true
    enddef

    def len(): number
	return 0
    enddef

    def string(): string
	return '[]'
    enddef
endenum

class List implements Listable
    const _value: any
    const _size: number
    var _next: Listable

    def new<E>(value: E)
	this._value = value
	this._size = 1
	this._next = EmptyList.INSTANCE
    enddef

    def _newCons<E>(value: E, size: number)
	this._value = value
	this._size = size
    enddef

    def Cons<E>(value: E): Listable
	const list: List = List._newCons<E>(value, (this._size + 1))
	list._next = this
	return list
    enddef

    def Reverse<E>(): Listable
	var result: Listable = List.new<E>(this.First<E>())
	var list: Listable = this.Rest()

	while !list.empty()
	    result = result.Cons<E>(list.First<E>())
	    list = list.Rest()
	endwhile

	return result
    enddef

    def Rest(): Listable
	return this._next
    enddef

    def First<E>(): E
	return this._value
    enddef

    def empty(): bool
	return (this._size == 0)
    enddef

    def len(): number
	return this._size
    enddef

    def string(): string
	if this.empty()
	    return '[]'
	endif

	var text: string = '[' .. string(this.First<any>()) .. ', '
	var list: Listable = this.Rest()

	while !list.empty()
	    text ..= string(list.First<any>()) .. ', '
	    list = list.Rest()
	endwhile

	return strpart(text, 0, (strlen(text) - 2)) .. ']'
    enddef
endclass

export def MakeEmptyList(): Listable
    return EmptyList.INSTANCE
enddef

export def MakeList<E>(value: E): Listable
    return List.new<E>(value)
enddef

export def Map<T, U>(listable: Listable, Mapper: func(T): U): Listable
    var result: Listable = EmptyList.INSTANCE
    var list: Listable = listable

    while !list.empty()
	result = result.Cons<U>(Mapper(list.First<T>()))
	list = list.Rest()
    endwhile

    return result.Reverse<U>()
enddef

export def Filter<T>(listable: Listable, Predicate: func(T): bool): Listable
    var result: Listable = EmptyList.INSTANCE
    var list: Listable = listable

    while !list.empty()
	if Predicate(list.First<T>())
	    result = result.Cons<T>(list.First<T>())
	endif

	list = list.Rest()
    endwhile

    return result.Reverse<T>()
enddef

############################################################

echo MakeEmptyList()

const listX: Listable = MakeEmptyList()
    .Cons<number>(0).Cons<number>(1).Cons<number>(2).Cons<number>(3)
const listY: Listable = MakeList<number>(0)
    .Cons<number>(1).Cons<number>(2).Cons<number>(3)
echo listX == listY
echo listX
echo listX.Reverse<number>()
echo MakeEmptyList().Reverse<any>()
echo Filter<number>(listX, (value: number) => value % 2 != 0)
echo Map<number, string>(listX, (value: number) => nr2char((value + 60), 1))

echo 4 listX.len() listY.len()
echo listX
echo listY

const list3X: Listable = listX.Rest()
const list3Y: Listable = listY.Rest()
echo 3 list3X.len() list3Y.len()
echo list3X
echo list3Y

const list2X: Listable = list3X.Rest()
const list2Y: Listable = list3Y.Rest()
echo 2 list2X.len() list2Y.len()
echo list2X
echo list2Y

const list1X: Listable = list2X.Rest()
const list1Y: Listable = list2Y.Rest()
echo 1 list1X.len() list1Y.len()
echo list1X
echo list1Y

const list0X: Listable = list1X.Rest()
const list0Y: Listable = list1Y.Rest()
echo 0 list0X.len() list0Y.len()
echo list0X
echo list0Y

const list0X_: Listable = list0X.Rest()
const list0Y_: Listable = list0Y.Rest()
echo 0 list0X_.len() list0Y_.len()
echo list0X_
echo list0Y_

const list0X__: Listable = list0X_.Rest()
const list0Y__: Listable = list0Y_.Rest()
echo 0 list0X__.len() list0Y__.len()
echo list0X__
echo list0Y__


const listZ: Listable = MakeList<Listable>(MakeList<number>(-1))
const listZZ: Listable = listZ.Cons<Listable>(MakeList<number>(0))
    .Cons<Listable>(MakeList<number>(1))
    .Cons<Listable>(MakeList<number>(2))
    .Cons<Listable>(MakeList<number>(3))
echo listZZ
(set.vim)
vim9script

# See https://github.com/vim/vim/issues/14330#issuecomment-2028938515 .
export class Set
    final _elements: dict<number>
    const _Mapper: func(number, string): any
    const ToStringer: func(any): string
    const FromStringer: func(string): any

    static def _Mapper<E>(F: func(string): E): func(number, string): E
	return ((G: func(string): E) => (_: number, v: string): E => G(v))(F)
    enddef

    def new<E>()
	this._elements = {}
	this._Mapper = _Mapper<E>((s: string): E => eval(s))
	this.ToStringer = (a: E): string => string(a)
	this.FromStringer = (s: string): E => eval(s)
    enddef

    def newFromList<E>(elements: list<E>, ToStringer: func(E): string,
					FromStringer: func(string): E)
	this._elements = elements
	    ->reduce(((F: func(E): string) => (d: dict<number>, v: E) =>
		extend({[F(v)]: 1}, d))(ToStringer),
		{})
	this._Mapper = _Mapper<E>(FromStringer)
	this.ToStringer = ToStringer
	this.FromStringer = FromStringer
    enddef

    def _FromList<E>(elements: list<E>): Set
	return Set.newFromList<E>(elements, this.ToStringer, this.FromStringer)
    enddef

    def Contains<E>(element: E): bool
	return has_key(this._elements, this.ToStringer(element))
    enddef

    def Elements<E>(): list<E>
	return keys(this._elements)->mapnew(this._Mapper)
    enddef

    def empty(): bool
	return empty(this._elements)
    enddef

    def len(): number
	return len(this._elements)
    enddef

    def string(): string
	return string(keys(this._elements))
    enddef

    # {1, 2, 3} ⊇ {1, 2}.
    def Superset(that: Set): bool
 	return (len(this._elements) >= len(that._elements)) && that._elements
	    ->keys()
	    ->indexof(((set: Set) => (_: number, v: string) => !set._elements
		->has_key(v))(this)) < 0
    enddef

    # {1, 2} ⊆ {1, 2, 3}.
    def Subset(that: Set): bool
 	return (len(this._elements) <= len(that._elements)) && this._elements
	    ->keys()
	    ->indexof(((set: Set) => (_: number, v: string) => !set._elements
		->has_key(v))(that)) < 0
    enddef

    # {1, 2, 3} ∪ {2, 3, 4} = {1, 2, 3, 4}.
    def Union(that: Set): Set
	return this._FromList<any>({}
	    ->extend(that._elements)
	    ->extend(this._elements)
	    ->keys()
	    ->map(this._Mapper))
    enddef

    # {1, 2, 3} ∩ {2, 3, 4} = {2, 3}.
    def Intersection(that: Set): Set
	return this._FromList<any>(this._elements
	    ->keys()
	    ->filter(((set: Set) => (_: number, v: string) => set._elements
		->has_key(v))(that))
	    ->map(this._Mapper))
    enddef

    # {1, 2, 3} \ {2, 3, 4} = {1}.
    # {2, 3, 4} \ {1, 2, 3} = {4}.
    def SetDifference(that: Set): Set
	return this._FromList<any>(this._elements
	    ->keys()
	    ->filter(((set: Set) => (_: number, v: string) => !set._elements
		->has_key(v))(that))
	    ->map(this._Mapper))
    enddef

    # {1, 2, 3} △ {2, 3, 4} = {1, 4}.
    def SymmetricDifference(that: Set): Set
	return this.Union(that).SetDifference(this.Intersection(that))
    enddef
endclass

############################################################

const ToStr: func(number): string = (s: number) => string(s)
const FromStr: func(string): number = (s: string) => str2nr(s)

echo Set.newFromList<number>([1, 2, 3], ToStr, FromStr)
    .Subset(Set.newFromList<number>([1, 2], ToStr, FromStr))
echo Set.newFromList<number>([1, 2], ToStr, FromStr)
    .Subset(Set.newFromList<number>([1, 2, 3], ToStr, FromStr))
echo Set.newFromList<number>([1, 2], ToStr, FromStr)
    .Superset(Set.newFromList<number>([1, 2, 3], ToStr, FromStr))
echo Set.newFromList<number>([1, 2, 3], ToStr, FromStr)
    .Superset(Set.newFromList<number>([1, 2], ToStr, FromStr))

echo Set.newFromList<number>([1, 2, 3], ToStr, FromStr)
    .Union(Set.newFromList<number>([2, 3, 4], ToStr, FromStr))
    .Elements<number>()
echo Set.newFromList<number>([2, 3, 4], ToStr, FromStr)
    .Union(Set.newFromList<number>([1, 2, 3], ToStr, FromStr))
    .Elements<number>()

echo Set.newFromList<number>([1, 2, 3], ToStr, FromStr)
    .Intersection(Set.newFromList<number>([2, 3, 4], ToStr, FromStr))
    .Elements<number>()
echo Set.newFromList<number>([2, 3, 4], ToStr, FromStr)
    .Intersection(Set.newFromList<number>([1, 2, 3], ToStr, FromStr))
    .Elements<number>()

echo Set.newFromList<number>([1, 2, 3], ToStr, FromStr)
    .SetDifference(Set.newFromList<number>([2, 3, 4], ToStr, FromStr))
    .Elements<number>()
echo Set.newFromList<number>([2, 3, 4], ToStr, FromStr)
    .SetDifference(Set.newFromList<number>([1, 2, 3], ToStr, FromStr))
    .Elements<number>()

echo Set.newFromList<number>([1, 2, 3], ToStr, FromStr)
    .SymmetricDifference(Set.newFromList<number>([2, 3, 4], ToStr, FromStr))
    .Elements<number>()
echo Set.newFromList<number>([2, 3, 4], ToStr, FromStr)
    .SymmetricDifference(Set.newFromList<number>([1, 2, 3], ToStr, FromStr))
    .Elements<number>()

############################################################

const none: Set = Set.new<any>()
echo none.len()
echo none.empty()
echo none.string()
echo string(none.Elements<any>())

const sets: Set = Set.newFromList<Set>(
    [Set.new<any>(), Set.new<any>(), Set.new<any>(), Set.new<any>()],
    (o: Set): string => string(o),
    (_: string): Set => Set.new<any>())
echo sets.len()
echo sets.empty()
echo sets.string()
echo string(sets.Elements<Set>())

const lists: Set = Set.newFromList<list<any>>(
    [[[[[]]]]],
    (o: list<any>): string => string(o),
    (s: string): list<any> => eval(s))
echo lists.len()
echo lists.empty()
echo lists.string()
echo string(lists.Elements<list<any>>())

@dkearns
Copy link
Contributor

dkearns commented Jul 14, 2025

vim9script

def Foo()
  echomsg "Foo"
enddef

def Bar<T>()
  echomsg "Bar"
enddef

var A = Foo
var B = Bar<string>

A()
B()

disassemble A
disassemble B

outputs

Foo
Bar
<SNR>65_Foo
  echomsg "Foo"
   0 PUSHS "Foo"
   1 ECHOMSG 1
   2 RETURN void
Error detected while processing /tmp/gen.vim:
line   18:
E1061: Cannot find function B

@dkearns
Copy link
Contributor

dkearns commented Jul 17, 2025

vim9script
def Foo<T>(a: T)
  var b: T = a
enddef
def Foo

outputs

   def <SNR>65_Foo(a: any)
1    var b: T = a
   enddef

@errael
Copy link
Contributor

errael commented Jul 17, 2025

vim9script
def Foo<T>(a: T)
  var b: T = a
enddef
def Foo

Off topic, I didn't know, or had forgotten, that def Foo outputs annotated source of the function.

Doesn't seem to be documented.

@yegappan
Copy link
Member Author

vim9script

def Bar<T>()
  echomsg "Bar"
enddef

var B = Bar<string>

disassemble B

outputs

Error detected while processing /tmp/gen.vim:
line   18:
E1061: Cannot find function B

Thanks for reporting this issue. This issue should be addressed in the latest updated PR.

@yegappan
Copy link
Member Author

Set and List operations can now be generified (see below)!

Supporting generic constructors for enums is hardly of any priority right now, but I hope, together with protected constructors for abstract classes and enums, this may be revisited at a later date.

Congratulations and thank you for developing this feature, @yegappan.

(list.vim)
(set.vim)

Thanks for creating these example scripts. This exposed some existing memory leaks which were tricky to fix.
All of these should be addressed now in the latest updated PR.

@yegappan
Copy link
Member Author

It is nice that constructors can be parameterised like other functions and methods:

(example_k.vim)
Creating enum values with a generic constructor then doesn't seem far-fetched (E1123):

This is supported now in the latest updated PR.

@zzzyxwvut
Copy link
Contributor

I'm glad that this is taken care of. Oops, I don't have any
broken code to share.

@yegappan
Copy link
Member Author

I'm glad that this is taken care of. Oops, I don't have any broken code to share.

Thanks for continuously testing and reporting the problems.

@chrisbra
Copy link
Member

Thanks, so I assume this is ready now?

@dkearns
Copy link
Contributor

dkearns commented Jul 20, 2025

@yegappan, I'm sorry about that merge commit, I haven't the faintest idea where that came from. You may want to force push again.

I have a 22 year old cat with hyperthyroidism and a love of keyboard dancing as my initial suspect.

@yegappan
Copy link
Member Author

@yegappan, I'm sorry about that merge commit, I haven't the faintest idea where that came from. You may want to force push again.

I have a 22 year old cat with hyperthyroidism and a love of keyboard dancing as my initial suspect.

No problem. I did a force push again.

@yegappan
Copy link
Member Author

Thanks, so I assume this is ready now?

@chrisbra Yes. This PR is ready.

@dkearns
Copy link
Contributor

dkearns commented Jul 21, 2025

@yegappan and @chrisbra, I don't really consider it a blocker but, just in case it's been overlooked, function listing still needs some work.

See: #17313 (comment)

@chrisbra
Copy link
Member

thanks all. Let me merge it and we can do further improvements with a followup PR for the function listing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants