Skip to content

Commit de82505

Browse files
gazerrozapateo
andauthored
compiler, runtime: extend the selector expression to maps in templates
This change allows to use the selector expression, in templates, to access map keys. Close #937 Co-authored-by: Gianluca Mondini <mondini@open2b.com>
1 parent 966faea commit de82505

15 files changed

+347
-33
lines changed

internal/compiler/builder_instructions.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,9 @@ func (fb *functionBuilder) emitIndex(ki bool, expr, i, dst int8, t reflect.Type,
438438
case reflect.Map:
439439
op = runtime.OpMapIndex
440440
fb.addOperandKinds(0, t.Key().Kind(), t.Elem().Kind())
441+
case reflect.Interface:
442+
op = runtime.OpMapIndexAny
443+
fb.addPosAndPath(pos)
441444
case reflect.String:
442445
op = runtime.OpIndexString
443446
default:

internal/compiler/checker_assignment.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (tc *typechecker) checkAssignment(node *ast.Assignment) {
5353
} else {
5454
lh = tc.checkExpr(lhExpr)
5555
}
56-
tc.checkAssignTo(lh, lhExpr)
56+
node.Lhs[i] = tc.checkAssignTo(lh, lhExpr)
5757
lhs[i] = lh
5858
}
5959

@@ -107,7 +107,7 @@ func (tc *typechecker) checkAssignmentOperation(node *ast.Assignment) {
107107
lh := tc.checkExpr(node.Lhs[0])
108108
rh := tc.checkExpr(node.Rhs[0])
109109

110-
tc.checkAssignTo(lh, node.Lhs[0])
110+
node.Lhs[0] = tc.checkAssignTo(lh, node.Lhs[0])
111111

112112
op := operatorFromAssignmentType(node.Type)
113113
_, err := tc.binaryOp(node.Lhs[0], op, node.Rhs[0])
@@ -254,7 +254,7 @@ func (tc *typechecker) checkIncDecStatement(node *ast.Assignment) {
254254
}
255255

256256
lh := tc.checkExpr(node.Lhs[0])
257-
tc.checkAssignTo(lh, node.Lhs[0])
257+
node.Lhs[0] = tc.checkAssignTo(lh, node.Lhs[0])
258258

259259
if !isNumeric(lh.Type.Kind()) {
260260
panic(tc.errorf(node, "invalid operation: %s (non-numeric type %s)", node, lh))
@@ -466,14 +466,29 @@ func (tc *typechecker) declareVariable(lh *ast.Identifier, typ reflect.Type) {
466466
}
467467

468468
// checkAssignTo checks that it is possible to assign to the expression expr.
469-
func (tc *typechecker) checkAssignTo(ti *typeInfo, expr ast.Expression) {
470-
if ti.Addressable() && !ti.IsMacroDeclaration() || tc.isMapIndexing(expr) {
471-
return
469+
// The caller must replace expr in the AST with the returned expression.
470+
func (tc *typechecker) checkAssignTo(ti *typeInfo, expr ast.Expression) ast.Expression {
471+
if ti.IsKeySelector() {
472+
if ti.replacement != nil {
473+
expr = ti.replacement.(ast.Expression)
474+
return expr
475+
}
476+
} else if ti.Addressable() && !ti.IsMacroDeclaration() || tc.isMapIndexing(expr) {
477+
return expr
472478
}
473479
format := "cannot assign to %s"
474480
switch e := expr.(type) {
475481
case *ast.Selector:
476-
if tc.isMapIndexing(e.Expr) {
482+
if ti.IsKeySelector() {
483+
for {
484+
if t := tc.compilation.typeInfos[e.Expr]; t.replacement != nil {
485+
break
486+
}
487+
e = e.Expr.(*ast.Selector)
488+
}
489+
expr = e.Expr
490+
format = "cannot index %s (map index expression of type interface{})"
491+
} else if tc.isMapIndexing(e.Expr) {
477492
format = "cannot assign to struct field %s in map"
478493
}
479494
case *ast.Index:
@@ -592,6 +607,14 @@ func (tc *typechecker) rebalancedRightSide(node ast.Node) []ast.Expression {
592607
tc.compilation.typeInfos[v1] = &typeInfo{Type: ti.Type}
593608
tc.compilation.typeInfos[v2] = untypedBoolTypeInfo
594609
return []ast.Expression{v1, v2}
610+
case *ast.Selector:
611+
if ti := tc.checkExpr(rhExpr); ti.IsKeySelector() {
612+
v1 := ast.NewSelector(v.Pos(), v.Expr, v.Ident)
613+
v2 := ast.NewSelector(v.Pos(), v.Expr, v.Ident)
614+
tc.compilation.typeInfos[v1] = &typeInfo{Type: ti.Type, Properties: ti.Properties, replacement: ti.replacement}
615+
tc.compilation.typeInfos[v2] = untypedBoolTypeInfo
616+
return []ast.Expression{v1, v2}
617+
}
595618
case *ast.Index:
596619
v1 := ast.NewIndex(v.Pos(), v.Expr, v.Index)
597620
v2 := ast.NewIndex(v.Pos(), v.Expr, v.Index)

internal/compiler/checker_expressions.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,10 @@ func (tc *typechecker) typeof(expr ast.Expression, typeExpected bool) *typeInfo
749749
if mv, ok := tc.checkMethodValue(t, expr); ok {
750750
return mv
751751
}
752+
// Key selector.
753+
if ti, ok := tc.checkKeySelector(t, expr); ok {
754+
return ti
755+
}
752756
// Field selector.
753757
return tc.checkFieldSelector(t, expr)
754758

@@ -2550,6 +2554,30 @@ func (tc *typechecker) checkMethodValue(t *typeInfo, expr *ast.Selector) (*typeI
25502554
}, true
25512555
}
25522556

2557+
// checkKeySelector checks a key selector.
2558+
func (tc *typechecker) checkKeySelector(t *typeInfo, expr *ast.Selector) (*typeInfo, bool) {
2559+
2560+
if tc.opts.mod != templateMod {
2561+
return nil, false
2562+
}
2563+
2564+
switch {
2565+
case t.Type.Kind() == reflect.Map:
2566+
// Type must be 'map[K]E' where K is a string type or the 'interface{}' type and E is any type.
2567+
if k := t.Type.Key(); t.Type.Name() != "" || k.Kind() != reflect.String && k != emptyInterfaceType {
2568+
panic(tc.errorf(expr, "invalid operation: cannot select %s (type %s does not support key selection)", expr, t.Type))
2569+
}
2570+
// Remember to replace 'm.x' with 'm["x"]'.
2571+
replacement := ast.NewIndex(expr.Pos(), expr.Expr, ast.NewBasicLiteral(expr.Pos(), ast.StringLiteral, "`"+expr.Ident+"`"))
2572+
tc.checkExpr(replacement)
2573+
return &typeInfo{Type: t.Type.Elem(), Properties: propertyKeySelector, replacement: replacement}, true
2574+
case t.IsKeySelector() && t.Type == emptyInterfaceType:
2575+
return &typeInfo{Type: t.Type, Properties: t.Properties}, true
2576+
}
2577+
2578+
return nil, false
2579+
}
2580+
25532581
// checkFieldSelector checks a field selector.
25542582
func (tc *typechecker) checkFieldSelector(t *typeInfo, expr *ast.Selector) *typeInfo {
25552583

internal/compiler/checker_template_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,17 @@ var stringArrayTypeInfo = &typeInfo{Type: reflect.ArrayOf(2, stringType), Proper
4444
var boolSliceTypeInfo = &typeInfo{Type: reflect.SliceOf(boolType), Properties: propertyAddressable}
4545
var boolArrayTypeInfo = &typeInfo{Type: reflect.ArrayOf(2, boolType), Properties: propertyAddressable}
4646
var interfaceSliceTypeInfo = &typeInfo{Type: reflect.SliceOf(emptyInterfaceType), Properties: propertyAddressable}
47+
var stringToAnyMapTypeInfo = &typeInfo{Type: reflect.MapOf(stringType, emptyInterfaceType), Properties: propertyAddressable}
4748

4849
var stringToIntMapTypeInfo = &typeInfo{Type: reflect.MapOf(stringType, intType), Properties: propertyAddressable}
4950
var intToStringMapTypeInfo = &typeInfo{Type: reflect.MapOf(intType, stringType), Properties: propertyAddressable}
51+
var intToAnyMapTypeInfo = &typeInfo{Type: reflect.MapOf(intType, emptyInterfaceType), Properties: propertyAddressable}
52+
var anyToIntMapTypeInfo = &typeInfo{Type: reflect.MapOf(emptyInterfaceType, intType), Properties: propertyAddressable}
53+
var definedStringToStringMapTypeInfo = &typeInfo{Type: reflect.MapOf(definedStringTypeInfo.Type, stringType), Properties: propertyAddressable}
5054
var definedIntToStringMapTypeInfo = &typeInfo{Type: reflect.MapOf(definedIntTypeInfo.Type, stringType), Properties: propertyAddressable}
5155

56+
var stringToStringToIntMapTypeInfo = &typeInfo{Type: reflect.MapOf(stringType, reflect.MapOf(stringType, intType)), Properties: propertyAddressable}
57+
5258
var definedIntTypeInfo = &typeInfo{Type: reflect.TypeOf(definedInt(0)), Properties: propertyAddressable}
5359
var definedIntSliceTypeInfo = &typeInfo{Type: reflect.SliceOf(definedIntTypeInfo.Type), Properties: propertyAddressable}
5460

@@ -70,6 +76,12 @@ func tiMarkdown() *typeInfo {
7076
return &typeInfo{Type: formatTypes[ast.FormatMarkdown]}
7177
}
7278

79+
type TF struct {
80+
F int
81+
}
82+
83+
var stringToTFMapTypeInfo = &typeInfo{Type: reflect.MapOf(stringType, reflect.TypeOf(TF{})), Properties: propertyAddressable}
84+
7385
var checkerTemplateExprs = []struct {
7486
src string
7587
ti *typeInfo
@@ -156,6 +168,16 @@ var checkerTemplateExprs = []struct {
156168
// conversion from markdown to html
157169
{`html(a)`, tiHTMLConst("<h1>title</h1>"), map[string]*typeInfo{"a": tiMarkdownConst("# title")}},
158170
{`html(a)`, tiHTML(), map[string]*typeInfo{"a": tiMarkdown()}},
171+
172+
// key selector
173+
{`m.x`, tiInt(), map[string]*typeInfo{"m": stringToIntMapTypeInfo}},
174+
{`m.x`, tiString(), map[string]*typeInfo{"m": definedStringToStringMapTypeInfo}},
175+
{`m.x`, tiInt(), map[string]*typeInfo{"m": anyToIntMapTypeInfo}},
176+
{`m.x.y`, tiInt(), map[string]*typeInfo{"m": stringToStringToIntMapTypeInfo}},
177+
{`m.x.y`, tiInterface(), map[string]*typeInfo{"m": stringToAnyMapTypeInfo}},
178+
{`m.x.y.z`, tiInterface(), map[string]*typeInfo{"m": stringToAnyMapTypeInfo}},
179+
{`m.x.nil`, tiInterface(), map[string]*typeInfo{"m": stringToAnyMapTypeInfo}},
180+
{`m.x.F`, tiInt(), map[string]*typeInfo{"m": stringToTFMapTypeInfo}},
159181
}
160182

161183
func TestCheckerTemplateExpressions(t *testing.T) {
@@ -236,6 +258,14 @@ var checkerTemplateExprErrors = []struct {
236258
// slicing of a format type
237259
{`a[1:2]`, tierr(1, 5, `invalid operation a[1:2] (slice of compiler.html)`), map[string]*typeInfo{"a": tiHTMLConst("<b>a</b>")}},
238260
{`a[1:2]`, tierr(1, 5, `invalid operation a[1:2] (slice of compiler.html)`), map[string]*typeInfo{"a": tiHTML()}},
261+
262+
// key selector
263+
{`m.x`, tierr(1, 5, `invalid operation: cannot select m.x (type map[int]string does not support key selection)`), map[string]*typeInfo{"m": intToStringMapTypeInfo}},
264+
{`m.x`, tierr(1, 5, `invalid operation: cannot select m.x (type map[int]interface {} does not support key selection)`), map[string]*typeInfo{"m": intToAnyMapTypeInfo}},
265+
{`m.x.y`, tierr(1, 7, `m.x.y undefined (type int has no field or method y)`), map[string]*typeInfo{"m": stringToIntMapTypeInfo}},
266+
{`m.x.y.z`, tierr(1, 7, `m.x.y undefined (type int has no field or method y)`), map[string]*typeInfo{"m": stringToIntMapTypeInfo}},
267+
{`nil.x.y.z`, tierr(1, 4, `use of untyped nil`), nil},
268+
{`m._`, tierr(1, 5, `cannot refer to blank field or method`), map[string]*typeInfo{"m": stringToAnyMapTypeInfo}},
239269
}
240270

241271
func TestCheckerTemplateExpressionErrors(t *testing.T) {
@@ -613,6 +643,23 @@ var checkerTemplateStmts = []struct {
613643
{src: `{% L: select %}{% default %}{% break L %}{% end %}`, expected: ok},
614644
{src: `{% _ = func() { L: goto L } %}`, expected: ok},
615645
{src: `{% L: for %}{% _ = func() { goto L } %}{% end %}`, expected: `label L not defined`},
646+
647+
// Key selector.
648+
{src: `{% m := map[string]int{} %}{% m.x = 5 %}{% m.x += 1 %}{% m.x++ %}{{ m.x + 2 }}`, expected: ok},
649+
{src: `{% m := map[DS]int{} %}{% m.x = 5 %}{% m.x += 1 %}{% m.x++ %}{{ m.x + 2 }}`, expected: ok},
650+
{src: `{% m := map[string]bool{} %}{% m.nil = true %}{{ m.nil && false }}`, expected: ok},
651+
{src: `{% m := map[interface{}]string{} %}{% m.x = "a" %}{{ m.a + "b" }}`, expected: ok},
652+
{src: `{% m := map[string]interface{}{} %}{% m.x = 6.89 %}{{ m.x.(float64) - 1.4 }}`, expected: ok},
653+
{src: `{% m := map[string]interface{}{} %}{% m.x = map[string]interface{}{} %}{% m.x.y = true %}`, expected: `cannot index m.x (map index expression of type interface{})`},
654+
{src: `{% m := map[interface{}]interface{}{} %}{% m.x = map[string]interface{}{} %}{% m.x.y = 'v' %}`, expected: `cannot index m.x (map index expression of type interface{})`},
655+
{src: `{% m := map[string]map[string]int{} %}{% m.x = map[string]int{} %}{% m.x.y = 3 %}{% m.x.y += 1 %}{% m.x.y++ %}{{ m.x.y * 2 }}`, expected: ok},
656+
{src: `{% m := map[string]map[string]interface{}{} %}{% m.x = map[string]interface{}{} %}{% m.x.y = "a" %}{{ m.x.y.(string) + "b" }}`, expected: ok},
657+
{src: `{% m := map[string]int{} %}{% m.x = "a" %}`, expected: `cannot use "a" (type untyped string) as type int in assignment`},
658+
{src: `{% m := map[interface{}]int{} %}{% m.x = "a" %}`, expected: `cannot use "a" (type untyped string) as type int in assignment`},
659+
{src: `{% m := map[string]int{} %}{% m.x := "a" %}`, expected: `non-name m.x on left side of :=`},
660+
{src: `{% m := map[interface{}]int{} %}{% m.x := "a" %}`, expected: `non-name m.x on left side of :=`},
661+
{src: `{% m := map[string]int{} %}{{ m.x + "a" }}`, expected: `invalid operation: m.x + "a" (cannot convert "a" (type untyped string) to type int)`},
662+
{src: `{% m := map[interface{}]int{} %}{{ m.x + "a" }}`, expected: `invalid operation: m.x + "a" (cannot convert "a" (type untyped string) to type int)`},
616663
}
617664

618665
func TestCheckerTemplatesStatements(t *testing.T) {
@@ -633,6 +680,7 @@ func TestCheckerTemplatesStatements(t *testing.T) {
633680
"Ui": native.UntypedNumericConst("5"),
634681
"Uf": native.UntypedNumericConst("5.0"),
635682
"R": 'r',
683+
"DS": reflect.TypeOf(definedString("")),
636684
},
637685
}
638686
for _, cas := range checkerTemplateStmts {

internal/compiler/disassembler.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,10 @@ func disassembleInstruction(fn *runtime.Function, globals []Global, addr runtime
595595
s += " " + disassembleOperand(fn, a, reflect.Interface, false)
596596
s += " " + disassembleOperand(fn, b, getKind('b', fn, addr), k)
597597
s += " " + disassembleOperand(fn, c, getKind('c', fn, addr), false)
598+
case runtime.OpMapIndexAny:
599+
s += " " + disassembleOperand(fn, a, reflect.Interface, false)
600+
s += " " + disassembleOperand(fn, b, reflect.String, k)
601+
s += " " + disassembleOperand(fn, c, reflect.Interface, false)
598602
case runtime.OpMethodValue:
599603
s += " " + disassembleOperand(fn, a, reflect.Interface, false)
600604
s += " " + disassembleOperand(fn, b, reflect.String, true)
@@ -1067,7 +1071,8 @@ var operationName = [...]string{
10671071

10681072
runtime.OpMakeStruct: "MakeStruct",
10691073

1070-
runtime.OpMapIndex: "MapIndex",
1074+
runtime.OpMapIndex: "MapIndex",
1075+
runtime.OpMapIndexAny: "MapIndex",
10711076

10721077
runtime.OpMethodValue: "MethodValue",
10731078

internal/compiler/emitter_assignment.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,21 @@ func (em *emitter) assignValuesToAddresses(addresses []address, values []ast.Exp
447447
addresses[0].assign(false, value, valueType)
448448
addresses[1].assign(false, okReg, okType)
449449

450+
case *ast.Selector: // key selector.
451+
exprType := em.typ(valueExpr.Expr)
452+
expr := em.emitExpr(valueExpr.Expr, exprType)
453+
key := em.fb.makeStringValue(valueExpr.Ident)
454+
value := em.fb.newRegister(reflect.Interface)
455+
okType := addresses[1].addressedType
456+
okReg := em.fb.newRegister(reflect.Bool)
457+
pos := valueExpr.Pos()
458+
em.fb.emitIndex(true, expr, key, value, exprType, pos, false)
459+
em.fb.emitMove(true, 1, okReg, reflect.Bool)
460+
em.fb.emitIf(false, 0, runtime.ConditionOK, 0, reflect.Interface, pos)
461+
em.fb.emitMove(true, 0, okReg, reflect.Bool)
462+
addresses[0].assign(false, value, emptyInterfaceType)
463+
addresses[1].assign(false, okReg, okType)
464+
450465
case *ast.TypeAssertion:
451466
typ := em.typ(valueExpr.Type)
452467
expr := em.emitExpr(valueExpr.Expr, emptyInterfaceType)

internal/compiler/emitter_expressions.go

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -263,31 +263,7 @@ func (em *emitter) _emitExpr(expr ast.Expression, dstType reflect.Type, reg int8
263263

264264
case *ast.Index:
265265

266-
exprType := em.typ(expr.Expr)
267-
exprReg := em.emitExpr(expr.Expr, exprType)
268-
var indexType reflect.Type
269-
if exprType.Kind() == reflect.Map {
270-
indexType = exprType.Key()
271-
} else {
272-
indexType = intType
273-
}
274-
index, kindex := em.emitExprK(expr.Index, indexType)
275-
var elemType reflect.Type
276-
if exprType.Kind() == reflect.String {
277-
elemType = uint8Type
278-
} else {
279-
elemType = exprType.Elem()
280-
}
281-
pos := expr.Pos()
282-
if canEmitDirectly(elemType.Kind(), dstType.Kind()) {
283-
em.fb.emitIndex(kindex, exprReg, index, reg, exprType, pos, true)
284-
return reg, false
285-
}
286-
em.fb.enterStack()
287-
tmp := em.fb.newRegister(elemType.Kind())
288-
em.fb.emitIndex(kindex, exprReg, index, tmp, exprType, pos, true)
289-
em.changeRegister(false, tmp, reg, elemType, dstType)
290-
em.fb.exitStack()
266+
em.emitIndex(expr, reg, dstType)
291267

292268
case *ast.Render:
293269

@@ -704,11 +680,59 @@ func (em *emitter) emitCompositeLiteral(expr *ast.CompositeLiteral, reg int8, ds
704680
return reg, false
705681
}
706682

683+
// emitIndex emits an index in register reg.
684+
func (em *emitter) emitIndex(v *ast.Index, reg int8, dstType reflect.Type) {
685+
exprType := em.typ(v.Expr)
686+
exprReg := em.emitExpr(v.Expr, exprType)
687+
var indexType reflect.Type
688+
if exprType.Kind() == reflect.Map {
689+
indexType = exprType.Key()
690+
} else {
691+
indexType = intType
692+
}
693+
index, kindex := em.emitExprK(v.Index, indexType)
694+
var elemType reflect.Type
695+
if exprType.Kind() == reflect.String {
696+
elemType = uint8Type
697+
} else {
698+
elemType = exprType.Elem()
699+
}
700+
pos := v.Pos()
701+
if canEmitDirectly(elemType.Kind(), dstType.Kind()) {
702+
em.fb.emitIndex(kindex, exprReg, index, reg, exprType, pos, true)
703+
return
704+
}
705+
em.fb.enterStack()
706+
tmp := em.fb.newRegister(elemType.Kind())
707+
em.fb.emitIndex(kindex, exprReg, index, tmp, exprType, pos, true)
708+
em.changeRegister(false, tmp, reg, elemType, dstType)
709+
em.fb.exitStack()
710+
}
711+
707712
// emitSelector emits selector in register reg.
708713
func (em *emitter) emitSelector(v *ast.Selector, reg int8, dstType reflect.Type) {
709714

710715
ti := em.ti(v)
711716

717+
// Key selector.
718+
if ti.IsKeySelector() {
719+
// Key selector on the empty interface type.
720+
if typ := em.typ(v.Expr); typ == emptyInterfaceType {
721+
exprReg := em.emitExpr(v.Expr, typ)
722+
keyReg := em.fb.makeStringValue(v.Ident)
723+
pos := v.Pos()
724+
em.fb.enterStack()
725+
dst := em.fb.newRegister(reflect.Interface)
726+
em.fb.emitIndex(true, exprReg, keyReg, dst, typ, pos, false)
727+
em.changeRegister(false, dst, reg, typ, dstType)
728+
em.fb.exitStack()
729+
return
730+
}
731+
// Key selector on a map type.
732+
em.emitIndex(ti.replacement.(*ast.Index), reg, dstType)
733+
return
734+
}
735+
712736
// Method value on concrete and interface values.
713737
if ti.MethodType == methodValueConcrete || ti.MethodType == methodValueInterface {
714738
expr := v.Expr

internal/compiler/typeinfo.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package compiler
77
import (
88
"reflect"
99

10+
"github.com/open2b/scriggo/ast"
1011
"github.com/open2b/scriggo/internal/runtime"
1112
)
1213

@@ -24,6 +25,7 @@ const (
2425
propertyHasValue // has a value
2526
propertyIsMacroDeclaration // is macro declaration
2627
propertyMacroDeclaredInFileWithExtends // is macro declared in file with extends
28+
propertyKeySelector // is a key selector
2729
)
2830

2931
// A typeInfo holds the type checking information. For example, every expression
@@ -39,6 +41,7 @@ type typeInfo struct {
3941
MethodType methodType // Method type.
4042
value interface{} // value; for packages has type *Package.
4143
valueType reflect.Type // When value is a native type holds the original type of value.
44+
replacement ast.Node // Replacement node.
4245
}
4346

4447
// methodType represents the type of a method, intended as a combination of a
@@ -131,6 +134,11 @@ func (ti *typeInfo) MacroDeclaredInExtendingFile() bool {
131134
return ti.Properties&propertyMacroDeclaredInFileWithExtends != 0
132135
}
133136

137+
// IsKeySelector reports whether it is a key selector.
138+
func (ti *typeInfo) IsKeySelector() bool {
139+
return ti.Properties&propertyKeySelector != 0
140+
}
141+
134142
// TypeName returns the name of the type. If it is an alias, it returns the
135143
// name of the alias. Panics if it is not a type.
136144
func (ti *typeInfo) TypeName() string {

0 commit comments

Comments
 (0)