aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/text
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2022-02-11 14:53:56 -0800
committerIan Lance Taylor <iant@golang.org>2022-02-11 15:01:19 -0800
commit8dc2499aa62f768c6395c9754b8cabc1ce25c494 (patch)
tree43d7fd2bbfd7ad8c9625a718a5e8718889351994 /libgo/go/text
parent9a56779dbc4e2d9c15be8d31e36f2f59be7331a8 (diff)
downloadgcc-8dc2499aa62f768c6395c9754b8cabc1ce25c494.zip
gcc-8dc2499aa62f768c6395c9754b8cabc1ce25c494.tar.gz
gcc-8dc2499aa62f768c6395c9754b8cabc1ce25c494.tar.bz2
libgo: update to Go1.18beta2
gotools/ * Makefile.am (go_cmd_cgo_files): Add ast_go118.go (check-go-tool): Copy golang.org/x/tools directories. * Makefile.in: Regenerate. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/384695
Diffstat (limited to 'libgo/go/text')
-rw-r--r--libgo/go/text/scanner/scanner.go2
-rw-r--r--libgo/go/text/template/doc.go20
-rw-r--r--libgo/go/text/template/exec.go104
-rw-r--r--libgo/go/text/template/exec_test.go71
-rw-r--r--libgo/go/text/template/funcs.go40
-rw-r--r--libgo/go/text/template/multi_test.go10
-rw-r--r--libgo/go/text/template/option.go10
-rw-r--r--libgo/go/text/template/parse/lex.go15
-rw-r--r--libgo/go/text/template/parse/lex_test.go2
-rw-r--r--libgo/go/text/template/parse/node.go36
-rw-r--r--libgo/go/text/template/parse/parse.go55
-rw-r--r--libgo/go/text/template/parse/parse_test.go10
-rw-r--r--libgo/go/text/template/template.go2
13 files changed, 282 insertions, 95 deletions
diff --git a/libgo/go/text/scanner/scanner.go b/libgo/go/text/scanner/scanner.go
index c5fc4ff..f1fbf98 100644
--- a/libgo/go/text/scanner/scanner.go
+++ b/libgo/go/text/scanner/scanner.go
@@ -340,7 +340,7 @@ func (s *Scanner) error(msg string) {
fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg)
}
-func (s *Scanner) errorf(format string, args ...interface{}) {
+func (s *Scanner) errorf(format string, args ...any) {
s.error(fmt.Sprintf(format, args...))
}
diff --git a/libgo/go/text/template/doc.go b/libgo/go/text/template/doc.go
index 7b30294..1009388 100644
--- a/libgo/go/text/template/doc.go
+++ b/libgo/go/text/template/doc.go
@@ -112,6 +112,14 @@ data, defined in detail in the corresponding sections that follow.
T0 is executed; otherwise, dot is set to the successive elements
of the array, slice, or map and T1 is executed.
+ {{break}}
+ The innermost {{range pipeline}} loop is ended early, stopping the
+ current iteration and bypassing all remaining iterations.
+
+ {{continue}}
+ The current iteration of the innermost {{range pipeline}} loop is
+ stopped, and the loop starts the next iteration.
+
{{template "name"}}
The template with the specified name is executed with nil data.
@@ -307,9 +315,10 @@ Predefined global functions are named as follows.
and
Returns the boolean AND of its arguments by returning the
- first empty argument or the last argument, that is,
- "and x y" behaves as "if x then y else x". All the
- arguments are evaluated.
+ first empty argument or the last argument. That is,
+ "and x y" behaves as "if x then y else x."
+ Evaluation proceeds through the arguments left to right
+ and returns when the result is determined.
call
Returns the result of calling the first argument, which
must be a function, with the remaining arguments as parameters.
@@ -344,8 +353,9 @@ Predefined global functions are named as follows.
or
Returns the boolean OR of its arguments by returning the
first non-empty argument or the last argument, that is,
- "or x y" behaves as "if x then x else y". All the
- arguments are evaluated.
+ "or x y" behaves as "if x then x else y".
+ Evaluation proceeds through the arguments left to right
+ and returns when the result is determined.
print
An alias for fmt.Sprint
printf
diff --git a/libgo/go/text/template/exec.go b/libgo/go/text/template/exec.go
index 545820d..7067a1f 100644
--- a/libgo/go/text/template/exec.go
+++ b/libgo/go/text/template/exec.go
@@ -5,6 +5,7 @@
package template
import (
+ "errors"
"fmt"
"internal/fmtsort"
"io"
@@ -127,7 +128,7 @@ func (e ExecError) Unwrap() error {
}
// errorf records an ExecError and terminates processing.
-func (s *state) errorf(format string, args ...interface{}) {
+func (s *state) errorf(format string, args ...any) {
name := doublePercent(s.tmpl.Name())
if s.node == nil {
format = fmt.Sprintf("template: %s: %s", name, format)
@@ -180,7 +181,7 @@ func errRecover(errp *error) {
// the output writer.
// A template may be executed safely in parallel, although if parallel
// executions share a Writer the output may be interleaved.
-func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
+func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error {
tmpl := t.Lookup(name)
if tmpl == nil {
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
@@ -198,11 +199,11 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})
//
// If data is a reflect.Value, the template applies to the concrete
// value that the reflect.Value holds, as in fmt.Print.
-func (t *Template) Execute(wr io.Writer, data interface{}) error {
+func (t *Template) Execute(wr io.Writer, data any) error {
return t.execute(wr, data)
}
-func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
+func (t *Template) execute(wr io.Writer, data any) (err error) {
defer errRecover(&err)
value, ok := data.(reflect.Value)
if !ok {
@@ -245,6 +246,12 @@ func (t *Template) DefinedTemplates() string {
return b.String()
}
+// Sentinel errors for use with panic to signal early exits from range loops.
+var (
+ walkBreak = errors.New("break")
+ walkContinue = errors.New("continue")
+)
+
// Walk functions step through the major pieces of the template structure,
// generating output as they go.
func (s *state) walk(dot reflect.Value, node parse.Node) {
@@ -257,7 +264,11 @@ func (s *state) walk(dot reflect.Value, node parse.Node) {
if len(node.Pipe.Decl) == 0 {
s.printValue(node, val)
}
+ case *parse.BreakNode:
+ panic(walkBreak)
case *parse.CommentNode:
+ case *parse.ContinueNode:
+ panic(walkContinue)
case *parse.IfNode:
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
case *parse.ListNode:
@@ -302,7 +313,7 @@ func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
// and whether the value has a meaningful truth value. This is the definition of
// truth used by if and other such actions.
-func IsTrue(val interface{}) (truth, ok bool) {
+func IsTrue(val any) (truth, ok bool) {
return isTrue(reflect.ValueOf(val))
}
@@ -318,7 +329,7 @@ func isTrue(val reflect.Value) (truth, ok bool) {
truth = val.Bool()
case reflect.Complex64, reflect.Complex128:
truth = val.Complex() != 0
- case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
+ case reflect.Chan, reflect.Func, reflect.Pointer, reflect.Interface:
truth = !val.IsNil()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
truth = val.Int() != 0
@@ -336,6 +347,11 @@ func isTrue(val reflect.Value) (truth, ok bool) {
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
s.at(r)
+ defer func() {
+ if r := recover(); r != nil && r != walkBreak {
+ panic(r)
+ }
+ }()
defer s.pop(s.mark())
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
// mark top of stack before any variables in the body are pushed.
@@ -349,8 +365,14 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
if len(r.Pipe.Decl) > 1 {
s.setTopVar(2, index)
}
+ defer s.pop(mark)
+ defer func() {
+ // Consume panic(walkContinue)
+ if r := recover(); r != nil && r != walkContinue {
+ panic(r)
+ }
+ }()
s.walk(elem, r.List)
- s.pop(mark)
}
switch val.Kind() {
case reflect.Array, reflect.Slice:
@@ -574,11 +596,11 @@ func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ide
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
s.at(node)
name := node.Ident
- function, ok := findFunction(name, s.tmpl)
+ function, isBuiltin, ok := findFunction(name, s.tmpl)
if !ok {
s.errorf("%q is not a defined function", name)
}
- return s.evalCall(dot, function, cmd, name, args, final)
+ return s.evalCall(dot, function, isBuiltin, cmd, name, args, final)
}
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
@@ -603,11 +625,11 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
// Unless it's an interface, need to get to a value of type *T to guarantee
// we see all methods of T and *T.
ptr := receiver
- if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() {
+ if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Pointer && ptr.CanAddr() {
ptr = ptr.Addr()
}
if method := ptr.MethodByName(fieldName); method.IsValid() {
- return s.evalCall(dot, method, node, fieldName, args, final)
+ return s.evalCall(dot, method, false, node, fieldName, args, final)
}
hasArgs := len(args) > 1 || final != missingVal
// It's not a method; must be a field of a struct or an element of a map.
@@ -615,10 +637,13 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
case reflect.Struct:
tField, ok := receiver.Type().FieldByName(fieldName)
if ok {
- field := receiver.FieldByIndex(tField.Index)
+ field, err := receiver.FieldByIndexErr(tField.Index)
if !tField.IsExported() {
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
}
+ if err != nil {
+ s.errorf("%v", err)
+ }
// If it's a function, we must call it.
if hasArgs {
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
@@ -645,7 +670,7 @@ func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node,
}
return result
}
- case reflect.Ptr:
+ case reflect.Pointer:
etyp := receiver.Type().Elem()
if etyp.Kind() == reflect.Struct {
if _, ok := etyp.FieldByName(fieldName); !ok {
@@ -671,7 +696,7 @@ var (
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
// as the function itself.
-func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
+func (s *state) evalCall(dot, fun reflect.Value, isBuiltin bool, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
if args != nil {
args = args[1:] // Zeroth arg is function name/node; not passed to function.
}
@@ -693,6 +718,38 @@ func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, a
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
}
+
+ unwrap := func(v reflect.Value) reflect.Value {
+ if v.Type() == reflectValueType {
+ v = v.Interface().(reflect.Value)
+ }
+ return v
+ }
+
+ // Special case for builtin and/or, which short-circuit.
+ if isBuiltin && (name == "and" || name == "or") {
+ argType := typ.In(0)
+ var v reflect.Value
+ for _, arg := range args {
+ v = s.evalArg(dot, argType, arg).Interface().(reflect.Value)
+ if truth(v) == (name == "or") {
+ // This value was already unwrapped
+ // by the .Interface().(reflect.Value).
+ return v
+ }
+ }
+ if final != missingVal {
+ // The last argument to and/or is coming from
+ // the pipeline. We didn't short circuit on an earlier
+ // argument, so we are going to return this one.
+ // We don't have to evaluate final, but we do
+ // have to check its type. Then, since we are
+ // going to return it, we have to unwrap it.
+ v = unwrap(s.validateType(final, argType))
+ }
+ return v
+ }
+
// Build the arg list.
argv := make([]reflect.Value, numIn)
// Args must be evaluated. Fixed args first.
@@ -730,16 +787,13 @@ func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, a
s.at(node)
s.errorf("error calling %s: %w", name, err)
}
- if v.Type() == reflectValueType {
- v = v.Interface().(reflect.Value)
- }
- return v
+ return unwrap(v)
}
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
func canBeNil(typ reflect.Type) bool {
switch typ.Kind() {
- case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return true
case reflect.Struct:
return typ == reflectValueType
@@ -776,12 +830,12 @@ func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Valu
// are much more constrained, so it makes more sense there than here.
// Besides, one is almost always all you need.
switch {
- case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
+ case value.Kind() == reflect.Pointer && value.Type().Elem().AssignableTo(typ):
value = value.Elem()
if !value.IsValid() {
s.errorf("dereference of nil pointer of type %s", typ)
}
- case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
+ case reflect.PointerTo(value.Type()).AssignableTo(typ) && value.CanAddr():
value = value.Addr()
default:
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
@@ -933,7 +987,7 @@ func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Valu
// if it's nil. If the returned bool is true, the returned value's kind will be
// either a pointer or interface.
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
- for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
+ for ; v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
@@ -971,8 +1025,8 @@ func (s *state) printValue(n parse.Node, v reflect.Value) {
// printableValue returns the, possibly indirected, interface value inside v that
// is best for a call to formatted printer.
-func printableValue(v reflect.Value) (interface{}, bool) {
- if v.Kind() == reflect.Ptr {
+func printableValue(v reflect.Value) (any, bool) {
+ if v.Kind() == reflect.Pointer {
v, _ = indirect(v) // fmt.Fprint handles nil.
}
if !v.IsValid() {
@@ -980,7 +1034,7 @@ func printableValue(v reflect.Value) (interface{}, bool) {
}
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
- if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
+ if v.CanAddr() && (reflect.PointerTo(v.Type()).Implements(errorType) || reflect.PointerTo(v.Type()).Implements(fmtStringerType)) {
v = v.Addr()
} else {
switch v.Kind() {
diff --git a/libgo/go/text/template/exec_test.go b/libgo/go/text/template/exec_test.go
index d64f690..c34db4c 100644
--- a/libgo/go/text/template/exec_test.go
+++ b/libgo/go/text/template/exec_test.go
@@ -46,7 +46,7 @@ type T struct {
MSI map[string]int
MSIone map[string]int // one element, for deterministic output
MSIEmpty map[string]int
- MXI map[interface{}]int
+ MXI map[any]int
MII map[int]int
MI32S map[int32]string
MI64S map[int64]string
@@ -56,11 +56,11 @@ type T struct {
MUI8S map[uint8]string
SMSI []map[string]int
// Empty interfaces; used to see if we can dig inside one.
- Empty0 interface{} // nil
- Empty1 interface{}
- Empty2 interface{}
- Empty3 interface{}
- Empty4 interface{}
+ Empty0 any // nil
+ Empty1 any
+ Empty2 any
+ Empty3 any
+ Empty4 any
// Non-empty interfaces.
NonEmptyInterface I
NonEmptyInterfacePtS *I
@@ -138,7 +138,7 @@ var tVal = &T{
SB: []bool{true, false},
MSI: map[string]int{"one": 1, "two": 2, "three": 3},
MSIone: map[string]int{"one": 1},
- MXI: map[interface{}]int{"one": 1},
+ MXI: map[any]int{"one": 1},
MII: map[int]int{1: 1},
MI32S: map[int32]string{1: "one", 2: "two"},
MI64S: map[int64]string{2: "i642", 3: "i643"},
@@ -209,7 +209,7 @@ func (t *T) Method2(a uint16, b string) string {
return fmt.Sprintf("Method2: %d %s", a, b)
}
-func (t *T) Method3(v interface{}) string {
+func (t *T) Method3(v any) string {
return fmt.Sprintf("Method3: %v", v)
}
@@ -249,7 +249,7 @@ func (u *U) TrueFalse(b bool) string {
return ""
}
-func typeOf(arg interface{}) string {
+func typeOf(arg any) string {
return fmt.Sprintf("%T", arg)
}
@@ -257,7 +257,7 @@ type execTest struct {
name string
input string
output string
- data interface{}
+ data any
ok bool
}
@@ -390,7 +390,7 @@ var execTests = []execTest{
{".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true},
{"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true},
{"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true},
- {"Interface Call", `{{stringer .S}}`, "foozle", map[string]interface{}{"S": bytes.NewBufferString("foozle")}, true},
+ {"Interface Call", `{{stringer .S}}`, "foozle", map[string]any{"S": bytes.NewBufferString("foozle")}, true},
{".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
{"call nil", "{{call nil}}", "", tVal, false},
@@ -481,8 +481,19 @@ var execTests = []execTest{
{"not", "{{not true}} {{not false}}", "false true", nil, true},
{"and", "{{and false 0}} {{and 1 0}} {{and 0 true}} {{and 1 1}}", "false 0 0 1", nil, true},
{"or", "{{or 0 0}} {{or 1 0}} {{or 0 true}} {{or 1 1}}", "0 1 true 1", nil, true},
+ {"or short-circuit", "{{or 0 1 (die)}}", "1", nil, true},
+ {"and short-circuit", "{{and 1 0 (die)}}", "0", nil, true},
+ {"or short-circuit2", "{{or 0 0 (die)}}", "", nil, false},
+ {"and short-circuit2", "{{and 1 1 (die)}}", "", nil, false},
+ {"and pipe-true", "{{1 | and 1}}", "1", nil, true},
+ {"and pipe-false", "{{0 | and 1}}", "0", nil, true},
+ {"or pipe-true", "{{1 | or 0}}", "1", nil, true},
+ {"or pipe-false", "{{0 | or 0}}", "0", nil, true},
+ {"and undef", "{{and 1 .Unknown}}", "<no value>", nil, true},
+ {"or undef", "{{or 0 .Unknown}}", "<no value>", nil, true},
{"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true},
{"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
+ {"boolean if pipe", "{{if true | not | and 1}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
// Indexing.
{"slice[0]", "{{index .SI 0}}", "3", tVal, true},
@@ -564,6 +575,8 @@ var execTests = []execTest{
{"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
{"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
{"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+ {"range []int break else", "{{range .SI}}-{{.}}-{{break}}NOTREACHED{{else}}EMPTY{{end}}", "-3-", tVal, true},
+ {"range []int continue else", "{{range .SI}}-{{.}}-{{continue}}NOTREACHED{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
{"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
{"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
{"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true},
@@ -735,7 +748,7 @@ func add(args ...int) int {
return sum
}
-func echo(arg interface{}) interface{} {
+func echo(arg any) any {
return arg
}
@@ -754,7 +767,7 @@ func stringer(s fmt.Stringer) string {
return s.String()
}
-func mapOfThree() interface{} {
+func mapOfThree() any {
return map[string]int{"three": 3}
}
@@ -764,6 +777,7 @@ func testExecute(execTests []execTest, template *Template, t *testing.T) {
"add": add,
"count": count,
"dddArg": dddArg,
+ "die": func() bool { panic("die") },
"echo": echo,
"makemap": makemap,
"mapOfThree": mapOfThree,
@@ -1454,7 +1468,7 @@ func TestBlock(t *testing.T) {
func TestEvalFieldErrors(t *testing.T) {
tests := []struct {
name, src string
- value interface{}
+ value any
want string
}{
{
@@ -1597,7 +1611,7 @@ func TestInterfaceValues(t *testing.T) {
for _, tt := range tests {
tmpl := Must(New("tmpl").Parse(tt.text))
var buf bytes.Buffer
- err := tmpl.Execute(&buf, map[string]interface{}{
+ err := tmpl.Execute(&buf, map[string]any{
"PlusOne": func(n int) int {
return n + 1
},
@@ -1626,7 +1640,7 @@ func TestInterfaceValues(t *testing.T) {
// Check that panics during calls are recovered and returned as errors.
func TestExecutePanicDuringCall(t *testing.T) {
- funcs := map[string]interface{}{
+ funcs := map[string]any{
"doPanic": func() string {
panic("custom panic string")
},
@@ -1634,7 +1648,7 @@ func TestExecutePanicDuringCall(t *testing.T) {
tests := []struct {
name string
input string
- data interface{}
+ data any
wantErr string
}{
{
@@ -1773,3 +1787,26 @@ func TestIssue39807(t *testing.T) {
wg.Wait()
}
+
+// Issue 48215: embedded nil pointer causes panic.
+// Fixed by adding FieldByIndexErr to the reflect package.
+func TestIssue48215(t *testing.T) {
+ type A struct {
+ S string
+ }
+ type B struct {
+ *A
+ }
+ tmpl, err := New("").Parse(`{{ .S }}`)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = tmpl.Execute(io.Discard, B{})
+ // We expect an error, not a panic.
+ if err == nil {
+ t.Fatal("did not get error for nil embedded struct")
+ }
+ if !strings.Contains(err.Error(), "reflect: indirection through nil pointer to embedded struct field A") {
+ t.Fatal(err)
+ }
+}
diff --git a/libgo/go/text/template/funcs.go b/libgo/go/text/template/funcs.go
index fff833e..dca5ed2 100644
--- a/libgo/go/text/template/funcs.go
+++ b/libgo/go/text/template/funcs.go
@@ -31,7 +31,7 @@ import (
// apply to arguments of arbitrary type can use parameters of type interface{} or
// of type reflect.Value. Similarly, functions meant to return a result of arbitrary
// type can return interface{} or reflect.Value.
-type FuncMap map[string]interface{}
+type FuncMap map[string]any
// builtins returns the FuncMap.
// It is not a global variable so the linker can dead code eliminate
@@ -139,18 +139,18 @@ func goodName(name string) bool {
}
// findFunction looks for a function in the template, and global map.
-func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
+func findFunction(name string, tmpl *Template) (v reflect.Value, isBuiltin, ok bool) {
if tmpl != nil && tmpl.common != nil {
tmpl.muFuncs.RLock()
defer tmpl.muFuncs.RUnlock()
if fn := tmpl.execFuncs[name]; fn.IsValid() {
- return fn, true
+ return fn, false, true
}
}
if fn := builtinFuncs()[name]; fn.IsValid() {
- return fn, true
+ return fn, true, true
}
- return reflect.Value{}, false
+ return reflect.Value{}, false, false
}
// prepareArg checks if value can be used as an argument of type argType, and
@@ -382,31 +382,13 @@ func truth(arg reflect.Value) bool {
// and computes the Boolean AND of its arguments, returning
// the first false argument it encounters, or the last argument.
func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
- if !truth(arg0) {
- return arg0
- }
- for i := range args {
- arg0 = args[i]
- if !truth(arg0) {
- break
- }
- }
- return arg0
+ panic("unreachable") // implemented as a special case in evalCall
}
// or computes the Boolean OR of its arguments, returning
// the first true argument it encounters, or the last argument.
func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
- if truth(arg0) {
- return arg0
- }
- for i := range args {
- arg0 = args[i]
- if truth(arg0) {
- break
- }
- }
- return arg0
+ panic("unreachable") // implemented as a special case in evalCall
}
// not returns the Boolean negation of its argument.
@@ -645,7 +627,7 @@ func HTMLEscapeString(s string) string {
// HTMLEscaper returns the escaped HTML equivalent of the textual
// representation of its arguments.
-func HTMLEscaper(args ...interface{}) string {
+func HTMLEscaper(args ...any) string {
return HTMLEscapeString(evalArgs(args))
}
@@ -736,13 +718,13 @@ func jsIsSpecial(r rune) bool {
// JSEscaper returns the escaped JavaScript equivalent of the textual
// representation of its arguments.
-func JSEscaper(args ...interface{}) string {
+func JSEscaper(args ...any) string {
return JSEscapeString(evalArgs(args))
}
// URLQueryEscaper returns the escaped value of the textual representation of
// its arguments in a form suitable for embedding in a URL query.
-func URLQueryEscaper(args ...interface{}) string {
+func URLQueryEscaper(args ...any) string {
return url.QueryEscape(evalArgs(args))
}
@@ -751,7 +733,7 @@ func URLQueryEscaper(args ...interface{}) string {
// except that each argument is indirected (if a pointer), as required,
// using the same rules as the default string evaluation during template
// execution.
-func evalArgs(args []interface{}) string {
+func evalArgs(args []any) string {
ok := false
var s string
// Fast path for simple common case.
diff --git a/libgo/go/text/template/multi_test.go b/libgo/go/text/template/multi_test.go
index b543ab5..6b81ffe 100644
--- a/libgo/go/text/template/multi_test.go
+++ b/libgo/go/text/template/multi_test.go
@@ -452,3 +452,13 @@ func TestIssue19294(t *testing.T) {
}
}
}
+
+// Issue 48436
+func TestAddToZeroTemplate(t *testing.T) {
+ tree, err := parse.Parse("c", cloneText3, "", "", nil, builtins())
+ if err != nil {
+ t.Fatal(err)
+ }
+ var tmpl Template
+ tmpl.AddParseTree("x", tree["c"])
+}
diff --git a/libgo/go/text/template/option.go b/libgo/go/text/template/option.go
index addce2d..1035afa 100644
--- a/libgo/go/text/template/option.go
+++ b/libgo/go/text/template/option.go
@@ -51,13 +51,11 @@ func (t *Template) setOption(opt string) {
if opt == "" {
panic("empty option string")
}
- elems := strings.Split(opt, "=")
- switch len(elems) {
- case 2:
- // key=value
- switch elems[0] {
+ // key=value
+ if key, value, ok := strings.Cut(opt, "="); ok {
+ switch key {
case "missingkey":
- switch elems[1] {
+ switch value {
case "invalid", "default":
t.option.missingKey = mapInvalid
return
diff --git a/libgo/go/text/template/parse/lex.go b/libgo/go/text/template/parse/lex.go
index 6784071..40d0411 100644
--- a/libgo/go/text/template/parse/lex.go
+++ b/libgo/go/text/template/parse/lex.go
@@ -62,6 +62,8 @@ const (
// Keywords appear after all the rest.
itemKeyword // used only to delimit the keywords
itemBlock // block keyword
+ itemBreak // break keyword
+ itemContinue // continue keyword
itemDot // the cursor, spelled '.'
itemDefine // define keyword
itemElse // else keyword
@@ -76,6 +78,8 @@ const (
var key = map[string]itemType{
".": itemDot,
"block": itemBlock,
+ "break": itemBreak,
+ "continue": itemContinue,
"define": itemDefine,
"else": itemElse,
"end": itemEnd,
@@ -119,6 +123,8 @@ type lexer struct {
parenDepth int // nesting depth of ( ) exprs
line int // 1+number of newlines seen
startLine int // start line of this item
+ breakOK bool // break keyword allowed
+ continueOK bool // continue keyword allowed
}
// next returns the next rune in the input.
@@ -184,7 +190,7 @@ func (l *lexer) acceptRun(valid string) {
// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
-func (l *lexer) errorf(format string, args ...interface{}) stateFn {
+func (l *lexer) errorf(format string, args ...any) stateFn {
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine}
return nil
}
@@ -461,7 +467,12 @@ Loop:
}
switch {
case key[word] > itemKeyword:
- l.emit(key[word])
+ item := key[word]
+ if item == itemBreak && !l.breakOK || item == itemContinue && !l.continueOK {
+ l.emit(itemIdentifier)
+ } else {
+ l.emit(item)
+ }
case word[0] == '.':
l.emit(itemField)
case word == "true", word == "false":
diff --git a/libgo/go/text/template/parse/lex_test.go b/libgo/go/text/template/parse/lex_test.go
index 6510eed..df6aabf 100644
--- a/libgo/go/text/template/parse/lex_test.go
+++ b/libgo/go/text/template/parse/lex_test.go
@@ -35,6 +35,8 @@ var itemName = map[itemType]string{
// keywords
itemDot: ".",
itemBlock: "block",
+ itemBreak: "break",
+ itemContinue: "continue",
itemDefine: "define",
itemElse: "else",
itemIf: "if",
diff --git a/libgo/go/text/template/parse/node.go b/libgo/go/text/template/parse/node.go
index 177482f9..4726822 100644
--- a/libgo/go/text/template/parse/node.go
+++ b/libgo/go/text/template/parse/node.go
@@ -71,6 +71,8 @@ const (
NodeVariable // A $ variable.
NodeWith // A with action.
NodeComment // A comment.
+ NodeBreak // A break action.
+ NodeContinue // A continue action.
)
// Nodes.
@@ -907,6 +909,40 @@ func (i *IfNode) Copy() Node {
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
}
+// BreakNode represents a {{break}} action.
+type BreakNode struct {
+ tr *Tree
+ NodeType
+ Pos
+ Line int
+}
+
+func (t *Tree) newBreak(pos Pos, line int) *BreakNode {
+ return &BreakNode{tr: t, NodeType: NodeBreak, Pos: pos, Line: line}
+}
+
+func (b *BreakNode) Copy() Node { return b.tr.newBreak(b.Pos, b.Line) }
+func (b *BreakNode) String() string { return "{{break}}" }
+func (b *BreakNode) tree() *Tree { return b.tr }
+func (b *BreakNode) writeTo(sb *strings.Builder) { sb.WriteString("{{break}}") }
+
+// ContinueNode represents a {{continue}} action.
+type ContinueNode struct {
+ tr *Tree
+ NodeType
+ Pos
+ Line int
+}
+
+func (t *Tree) newContinue(pos Pos, line int) *ContinueNode {
+ return &ContinueNode{tr: t, NodeType: NodeContinue, Pos: pos, Line: line}
+}
+
+func (c *ContinueNode) Copy() Node { return c.tr.newContinue(c.Pos, c.Line) }
+func (c *ContinueNode) String() string { return "{{continue}}" }
+func (c *ContinueNode) tree() *Tree { return c.tr }
+func (c *ContinueNode) writeTo(sb *strings.Builder) { sb.WriteString("{{continue}}") }
+
// RangeNode represents a {{range}} action and its commands.
type RangeNode struct {
BranchNode
diff --git a/libgo/go/text/template/parse/parse.go b/libgo/go/text/template/parse/parse.go
index 1a63961..b0cbe9d 100644
--- a/libgo/go/text/template/parse/parse.go
+++ b/libgo/go/text/template/parse/parse.go
@@ -24,14 +24,14 @@ type Tree struct {
Mode Mode // parsing mode.
text string // text parsed to create the template (or its parent)
// Parsing only; cleared after parse.
- funcs []map[string]interface{}
+ funcs []map[string]any
lex *lexer
token [3]item // three-token lookahead for parser.
peekCount int
vars []string // variables defined at the moment.
treeSet map[string]*Tree
actionLine int // line of left delim starting action
- mode Mode
+ rangeDepth int
}
// A mode value is a set of flags (or 0). Modes control parser behavior.
@@ -59,7 +59,7 @@ func (t *Tree) Copy() *Tree {
// templates described in the argument string. The top-level template will be
// given the specified name. If an error is encountered, parsing stops and an
// empty map is returned with the error.
-func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (map[string]*Tree, error) {
+func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]any) (map[string]*Tree, error) {
treeSet := make(map[string]*Tree)
t := New(name)
t.text = text
@@ -128,7 +128,7 @@ func (t *Tree) peekNonSpace() item {
// Parsing.
// New allocates a new parse tree with the given name.
-func New(name string, funcs ...map[string]interface{}) *Tree {
+func New(name string, funcs ...map[string]any) *Tree {
return &Tree{
Name: name,
funcs: funcs,
@@ -158,7 +158,7 @@ func (t *Tree) ErrorContext(n Node) (location, context string) {
}
// errorf formats the error and terminates processing.
-func (t *Tree) errorf(format string, args ...interface{}) {
+func (t *Tree) errorf(format string, args ...any) {
t.Root = nil
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format)
panic(fmt.Errorf(format, args...))
@@ -218,12 +218,14 @@ func (t *Tree) recover(errp *error) {
}
// startParse initializes the parser, using the lexer.
-func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet map[string]*Tree) {
+func (t *Tree) startParse(funcs []map[string]any, lex *lexer, treeSet map[string]*Tree) {
t.Root = nil
t.lex = lex
t.vars = []string{"$"}
t.funcs = funcs
t.treeSet = treeSet
+ lex.breakOK = !t.hasFunction("break")
+ lex.continueOK = !t.hasFunction("continue")
}
// stopParse terminates parsing.
@@ -238,7 +240,7 @@ func (t *Tree) stopParse() {
// the template for execution. If either action delimiter string is empty, the
// default ("{{" or "}}") is used. Embedded template definitions are added to
// the treeSet map.
-func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
+func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]any) (tree *Tree, err error) {
defer t.recover(&err)
t.ParseName = t.Name
emitComment := t.Mode&ParseComments != 0
@@ -386,6 +388,10 @@ func (t *Tree) action() (n Node) {
switch token := t.nextNonSpace(); token.typ {
case itemBlock:
return t.blockControl()
+ case itemBreak:
+ return t.breakControl(token.pos, token.line)
+ case itemContinue:
+ return t.continueControl(token.pos, token.line)
case itemElse:
return t.elseControl()
case itemEnd:
@@ -405,6 +411,32 @@ func (t *Tree) action() (n Node) {
return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim))
}
+// Break:
+// {{break}}
+// Break keyword is past.
+func (t *Tree) breakControl(pos Pos, line int) Node {
+ if token := t.next(); token.typ != itemRightDelim {
+ t.unexpected(token, "in {{break}}")
+ }
+ if t.rangeDepth == 0 {
+ t.errorf("{{break}} outside {{range}}")
+ }
+ return t.newBreak(pos, line)
+}
+
+// Continue:
+// {{continue}}
+// Continue keyword is past.
+func (t *Tree) continueControl(pos Pos, line int) Node {
+ if token := t.next(); token.typ != itemRightDelim {
+ t.unexpected(token, "in {{continue}}")
+ }
+ if t.rangeDepth == 0 {
+ t.errorf("{{continue}} outside {{range}}")
+ }
+ return t.newContinue(pos, line)
+}
+
// Pipeline:
// declarations? command ('|' command)*
func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) {
@@ -480,8 +512,14 @@ func (t *Tree) checkPipeline(pipe *PipeNode, context string) {
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
defer t.popVars(len(t.vars))
pipe = t.pipeline(context, itemRightDelim)
+ if context == "range" {
+ t.rangeDepth++
+ }
var next Node
list, next = t.itemList()
+ if context == "range" {
+ t.rangeDepth--
+ }
switch next.Type() {
case nodeEnd: //done
case nodeElse:
@@ -523,7 +561,8 @@ func (t *Tree) ifControl() Node {
// {{range pipeline}} itemList {{else}} itemList {{end}}
// Range keyword is past.
func (t *Tree) rangeControl() Node {
- return t.newRange(t.parseControl(false, "range"))
+ r := t.newRange(t.parseControl(false, "range"))
+ return r
}
// With:
diff --git a/libgo/go/text/template/parse/parse_test.go b/libgo/go/text/template/parse/parse_test.go
index 9b1be27..0c4778c 100644
--- a/libgo/go/text/template/parse/parse_test.go
+++ b/libgo/go/text/template/parse/parse_test.go
@@ -230,6 +230,10 @@ var parseTests = []parseTest{
`{{range $x := .SI}}{{.}}{{end}}`},
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
`{{range $x, $y := .SI}}{{.}}{{end}}`},
+ {"range with break", "{{range .SI}}{{.}}{{break}}{{end}}", noError,
+ `{{range .SI}}{{.}}{{break}}{{end}}`},
+ {"range with continue", "{{range .SI}}{{.}}{{continue}}{{end}}", noError,
+ `{{range .SI}}{{.}}{{continue}}{{end}}`},
{"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
{"template", "{{template `x`}}", noError,
@@ -279,6 +283,10 @@ var parseTests = []parseTest{
{"adjacent args", "{{printf 3`x`}}", hasError, ""},
{"adjacent args with .", "{{printf `x`.}}", hasError, ""},
{"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
+ {"break outside range", "{{range .}}{{end}} {{break}}", hasError, ""},
+ {"continue outside range", "{{range .}}{{end}} {{continue}}", hasError, ""},
+ {"break in range else", "{{range .}}{{else}}{{break}}{{end}}", hasError, ""},
+ {"continue in range else", "{{range .}}{{else}}{{continue}}{{end}}", hasError, ""},
// Other kinds of assignments and operators aren't available yet.
{"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
{"bug0b", "{{$x += 1}}{{$x}}", hasError, ""},
@@ -310,7 +318,7 @@ var parseTests = []parseTest{
{"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
}
-var builtins = map[string]interface{}{
+var builtins = map[string]any{
"printf": fmt.Sprintf,
"contains": strings.Contains,
}
diff --git a/libgo/go/text/template/template.go b/libgo/go/text/template/template.go
index fd74d45..776be9c 100644
--- a/libgo/go/text/template/template.go
+++ b/libgo/go/text/template/template.go
@@ -127,9 +127,9 @@ func (t *Template) copy(c *common) *Template {
// its definition. If it has been defined and already has that name, the existing
// definition is replaced; otherwise a new template is created, defined, and returned.
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
+ t.init()
t.muTmpl.Lock()
defer t.muTmpl.Unlock()
- t.init()
nt := t
if name != t.name {
nt = t.New(name)