Skip to content

Commit ad18f2c

Browse files
committed
Implement len() builtin for type parameters.
Similar to make(), each relevant type constructor has a $len() method that takes an unwrapped type value and returns its length. Whenever len() is called with a type param, the compiler emits a call to `Type.$len(value)`. Unlike make(), I kept compile-time specializations of len(), since they are much more likely to be called in performance-critical path and would be cheaper than a call to `T.$len()`.
1 parent 73df10d commit ad18f2c

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

compiler/expressions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,8 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args
10721072
return fc.formatExpr("(%e ? %e.size : 0)", args[0], args[0])
10731073
case *types.Chan:
10741074
return fc.formatExpr("%e.$buffer.length", args[0])
1075+
case *types.Interface: // *types.TypeParam has interface as underlying type.
1076+
return fc.formatExpr("%s.$len(%e)", fc.typeName(fc.pkgCtx.TypeOf(args[0])), args[0])
10751077
// length of array is constant
10761078
default:
10771079
panic(fmt.Sprintf("Unhandled len type: %T\n", argType))

compiler/prelude/types.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => {
9393
typ.wrapped = true;
9494
typ.wrap = (v) => new typ(v);
9595
typ.keyFor = x => { return "$" + x; };
96+
typ.$len = (v) => v.length;
9697
break;
9798

9899
case $kindFloat32:
@@ -163,6 +164,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => {
163164
typ.ptr.init(typ);
164165
Object.defineProperty(typ.ptr.nil, "nilCheck", { get: $throwNilPointerError });
165166
};
167+
typ.$len = (v) => typ.len;
166168
break;
167169

168170
case $kindChan:
@@ -176,6 +178,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => {
176178
typ.recvOnly = recvOnly;
177179
};
178180
typ.$make = (bufsize) => new $Chan(typ.elem, bufsize ? bufsize : 0);
181+
typ.$len = (v) => v.$buffer.length;
179182
break;
180183

181184
case $kindFunc:
@@ -216,6 +219,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => {
216219
if (size < 0 || size > 2147483647) { $throwRuntimeError("makemap: size out of range"); }
217220
return new $global.Map();
218221
};
222+
typ.$len = (v) => v ? v.size : 0;
219223
break;
220224

221225
case $kindPtr:
@@ -236,6 +240,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => {
236240
}
237241
typ.nil = new typ($throwNilPointerError, $throwNilPointerError);
238242
};
243+
typ.$len = (v) => typ.elem.$len(typ.wrapped ? v : v.$get());
239244
break;
240245

241246
case $kindSlice:
@@ -257,6 +262,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => {
257262
typ.nil = new typ([]);
258263
};
259264
typ.$make = (size, capacity) => $makeSlice(typ, size, capacity);
265+
typ.$len = (v) => v.$length;
260266
break;
261267

262268
case $kindStruct:

tests/typeparams/builtins_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,46 @@ func TestMake(t *testing.T) {
8181
}
8282
})
8383
}
84+
85+
func _len[T []int | *[3]int | map[int]int | chan int | string](x T) int {
86+
return len(x)
87+
}
88+
89+
func TestLen(t *testing.T) {
90+
ch := make(chan int, 2)
91+
ch <- 1
92+
93+
tests := []struct {
94+
desc string
95+
got int
96+
want int
97+
}{{
98+
desc: "string",
99+
got: _len("abcd"),
100+
want: 4,
101+
}, {
102+
desc: "[]int",
103+
got: _len([]int{1, 2, 3}),
104+
want: 3,
105+
}, {
106+
desc: "[3]int",
107+
got: _len(&[3]int{1}),
108+
want: 3,
109+
}, {
110+
desc: "map[int]int",
111+
got: _len(map[int]int{1: 1, 2: 2}),
112+
want: 2,
113+
}, {
114+
desc: "chan int",
115+
got: _len(ch),
116+
want: 1,
117+
}}
118+
119+
for _, test := range tests {
120+
t.Run(test.desc, func(t *testing.T) {
121+
if test.got != test.want {
122+
t.Errorf("Got: len() = %d. Want: %d.", test.got, test.want)
123+
}
124+
})
125+
}
126+
}

0 commit comments

Comments
 (0)