hugo

Unnamed repository; edit this file 'description' to name the repository.

git clone git://git.shimmy1996.com/hugo.git
commit 65a78cae1ecdcfcb0701d78fed812083a1a6e82b
parent 4d6d1d08dad5538f4710fc4b56e09419aa0d854b
Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date:   Wed, 16 Mar 2022 08:48:16 +0100

tpl: Sync go_templates for Go 1.18

Using Go tag go1.18 4aa1efed4853ea067d665a952eee77c52faac774

Updates #9677

Diffstat:
Mscripts/fork_go_templates/main.go | 5++---
Mtpl/internal/go_templates/cfg/cfg.go | 3+++
Mtpl/internal/go_templates/fmtsort/sort.go | 2+-
Mtpl/internal/go_templates/fmtsort/sort_test.go | 27+++++++++++++++++++--------
Mtpl/internal/go_templates/htmltemplate/attr.go | 6+++---
Mtpl/internal/go_templates/htmltemplate/clone_test.go | 1+
Mtpl/internal/go_templates/htmltemplate/content.go | 12++++++------
Mtpl/internal/go_templates/htmltemplate/content_test.go | 5+++--
Mtpl/internal/go_templates/htmltemplate/context.go | 9++++++++-
Mtpl/internal/go_templates/htmltemplate/css.go | 4++--
Mtpl/internal/go_templates/htmltemplate/css_test.go | 1+
Mtpl/internal/go_templates/htmltemplate/error.go | 2+-
Mtpl/internal/go_templates/htmltemplate/escape.go | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mtpl/internal/go_templates/htmltemplate/escape_test.go | 37+++++++++++++++++++++++++++++++------
Mtpl/internal/go_templates/htmltemplate/example_test.go | 3++-
Mtpl/internal/go_templates/htmltemplate/examplefiles_test.go | 1+
Mtpl/internal/go_templates/htmltemplate/exec_test.go | 41+++++++++++++++++++++--------------------
Mtpl/internal/go_templates/htmltemplate/html.go | 12++++++------
Mtpl/internal/go_templates/htmltemplate/html_test.go | 1+
Mtpl/internal/go_templates/htmltemplate/js.go | 16+++++++---------
Mtpl/internal/go_templates/htmltemplate/js_test.go | 15++++++++-------
Mtpl/internal/go_templates/htmltemplate/multi_test.go | 1+
Mtpl/internal/go_templates/htmltemplate/template.go | 8++++----
Mtpl/internal/go_templates/htmltemplate/template_test.go | 3++-
Mtpl/internal/go_templates/htmltemplate/transition_test.go | 1+
Mtpl/internal/go_templates/htmltemplate/url.go | 14++++++--------
Mtpl/internal/go_templates/htmltemplate/url_test.go | 3++-
Mtpl/internal/go_templates/testenv/testenv.go | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtpl/internal/go_templates/testenv/testenv_cgo.go | 2+-
Atpl/internal/go_templates/testenv/testenv_notunix.go | 13+++++++++++++
Mtpl/internal/go_templates/testenv/testenv_notwin.go | 2+-
Atpl/internal/go_templates/testenv/testenv_unix.go | 13+++++++++++++
Mtpl/internal/go_templates/texttemplate/doc.go | 20+++++++++++++++-----
Mtpl/internal/go_templates/texttemplate/example_test.go | 1+
Mtpl/internal/go_templates/texttemplate/examplefiles_test.go | 1+
Mtpl/internal/go_templates/texttemplate/examplefunc_test.go | 1+
Mtpl/internal/go_templates/texttemplate/exec.go | 116++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mtpl/internal/go_templates/texttemplate/exec_test.go | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mtpl/internal/go_templates/texttemplate/funcs.go | 51+++++++++++++++++++--------------------------------
Mtpl/internal/go_templates/texttemplate/link_test.go | 7++-----
Mtpl/internal/go_templates/texttemplate/multi_test.go | 11+++++++++++
Mtpl/internal/go_templates/texttemplate/option.go | 10++++------
Mtpl/internal/go_templates/texttemplate/parse/lex.go | 15+++++++++++++--
Mtpl/internal/go_templates/texttemplate/parse/lex_test.go | 3+++
Mtpl/internal/go_templates/texttemplate/parse/node.go | 36++++++++++++++++++++++++++++++++++++
Mtpl/internal/go_templates/texttemplate/parse/parse.go | 59++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtpl/internal/go_templates/texttemplate/parse/parse_test.go | 27++++++++++++++++++++++++++-
Mtpl/internal/go_templates/texttemplate/template.go | 11+++--------
48 files changed, 697 insertions(+), 223 deletions(-)
diff --git a/scripts/fork_go_templates/main.go b/scripts/fork_go_templates/main.go
@@ -17,8 +17,7 @@ import (
 )
 
 func main() {
-	// TODO(bep) git checkout tag
-	// The current is built with Go version 2f0da6d9e29d9b9d5a4d10427ca9f71d12bbacc8 / go1.16
+	// The current is built with Go tag go1.18 4aa1efed4853ea067d665a952eee77c52faac774
 	fmt.Println("Forking ...")
 	defer fmt.Println("Done ...")
 
@@ -40,7 +39,7 @@ func main() {
 
 const (
 	// TODO(bep)
-	goSource = "/Users/bep/dev/go/dump/go/src"
+	goSource = "/Users/bep/dev/go/misc/go/src"
 	forkRoot = "../../tpl/internal/go_templates"
 )
 
diff --git a/tpl/internal/go_templates/cfg/cfg.go b/tpl/internal/go_templates/cfg/cfg.go
@@ -33,12 +33,14 @@ const KnownEnv = `
 	GCCGO
 	GO111MODULE
 	GO386
+	GOAMD64
 	GOARCH
 	GOARM
 	GOBIN
 	GOCACHE
 	GOENV
 	GOEXE
+	GOEXPERIMENT
 	GOFLAGS
 	GOGCCFLAGS
 	GOHOSTARCH
@@ -60,6 +62,7 @@ const KnownEnv = `
 	GOTOOLDIR
 	GOVCS
 	GOWASM
+	GOWORK
 	GO_EXTLINK_ENABLED
 	PKG_CONFIG
 `
diff --git a/tpl/internal/go_templates/fmtsort/sort.go b/tpl/internal/go_templates/fmtsort/sort.go
@@ -130,7 +130,7 @@ func compare(aVal, bVal reflect.Value) int {
 		default:
 			return -1
 		}
-	case reflect.Ptr, reflect.UnsafePointer:
+	case reflect.Pointer, reflect.UnsafePointer:
 		a, b := aVal.Pointer(), bVal.Pointer()
 		switch {
 		case a < b:
diff --git a/tpl/internal/go_templates/fmtsort/sort_test.go b/tpl/internal/go_templates/fmtsort/sort_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
 	"math"
 	"reflect"
+	"sort"
 	"strings"
 	"testing"
 	"unsafe"
@@ -37,12 +38,12 @@ var compareTests = [][]reflect.Value{
 	ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]),
 	ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}),
 	ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}),
-	ct(reflect.TypeOf(interface{}(interface{}(0))), iFace, 1, 2, 3),
+	ct(reflect.TypeOf(any(any(0))), iFace, 1, 2, 3),
 }
 
-var iFace interface{}
+var iFace any
 
-func ct(typ reflect.Type, args ...interface{}) []reflect.Value {
+func ct(typ reflect.Type, args ...any) []reflect.Value {
 	value := make([]reflect.Value, len(args))
 	for i, v := range args {
 		x := reflect.ValueOf(v)
@@ -83,8 +84,8 @@ func TestCompare(t *testing.T) {
 }
 
 type sortTest struct {
-	data  interface{} // Always a map.
-	print string      // Printed result using our custom printer.
+	data  any    // Always a map.
+	print string // Printed result using our custom printer.
 }
 
 var sortTests = []sortTest{
@@ -134,7 +135,7 @@ var sortTests = []sortTest{
 	},
 }
 
-func sprint(data interface{}) string {
+func sprint(data any) string {
 	om := fmtsort.Sort(reflect.ValueOf(data))
 	if om == nil {
 		return "nil"
@@ -188,9 +189,19 @@ func sprintKey(key reflect.Value) string {
 
 var (
 	ints  [3]int
-	chans = [3]chan int{make(chan int), make(chan int), make(chan int)}
+	chans = makeChans()
 )
 
+func makeChans() []chan int {
+	cs := []chan int{make(chan int), make(chan int), make(chan int)}
+	// Order channels by address. See issue #49431.
+	// TODO: pin these pointers once pinning is available (#46787).
+	sort.Slice(cs, func(i, j int) bool {
+		return uintptr(reflect.ValueOf(cs[i]).UnsafePointer()) < uintptr(reflect.ValueOf(cs[j]).UnsafePointer())
+	})
+	return cs
+}
+
 func pointerMap() map[*int]string {
 	m := make(map[*int]string)
 	for i := 2; i >= 0; i-- {
@@ -233,7 +244,7 @@ func TestInterface(t *testing.T) {
 	// A map containing multiple concrete types should be sorted by type,
 	// then value. However, the relative ordering of types is unspecified,
 	// so test this by checking the presence of sorted subgroups.
-	m := map[interface{}]string{
+	m := map[any]string{
 		[2]int{1, 0}:             "",
 		[2]int{0, 1}:             "",
 		true:                     "",
diff --git a/tpl/internal/go_templates/htmltemplate/attr.go b/tpl/internal/go_templates/htmltemplate/attr.go
@@ -143,12 +143,12 @@ func attrType(name string) contentType {
 		// widely applied.
 		// Treat data-action as URL below.
 		name = name[5:]
-	} else if colon := strings.IndexRune(name, ':'); colon != -1 {
-		if name[:colon] == "xmlns" {
+	} else if prefix, short, ok := strings.Cut(name, ":"); ok {
+		if prefix == "xmlns" {
 			return contentTypeURL
 		}
 		// Treat svg:href and xlink:href as href below.
-		name = name[colon+1:]
+		name = short
 	}
 	if t, ok := attrTypeMap[name]; ok {
 		return t
diff --git a/tpl/internal/go_templates/htmltemplate/clone_test.go b/tpl/internal/go_templates/htmltemplate/clone_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
diff --git a/tpl/internal/go_templates/htmltemplate/content.go b/tpl/internal/go_templates/htmltemplate/content.go
@@ -29,16 +29,16 @@ const (
 
 // indirect returns the value, after dereferencing as many times
 // as necessary to reach the base type (or nil).
-func indirect(a interface{}) interface{} {
+func indirect(a any) any {
 	if a == nil {
 		return nil
 	}
-	if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
+	if t := reflect.TypeOf(a); t.Kind() != reflect.Pointer {
 		// Avoid creating a reflect.Value if it's not a pointer.
 		return a
 	}
 	v := reflect.ValueOf(a)
-	for v.Kind() == reflect.Ptr && !v.IsNil() {
+	for v.Kind() == reflect.Pointer && !v.IsNil() {
 		v = v.Elem()
 	}
 	return v.Interface()
@@ -52,12 +52,12 @@ var (
 // indirectToStringerOrError returns the value, after dereferencing as many times
 // as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
 // or error,
-func indirectToStringerOrError(a interface{}) interface{} {
+func indirectToStringerOrError(a any) any {
 	if a == nil {
 		return nil
 	}
 	v := reflect.ValueOf(a)
-	for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
+	for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Pointer && !v.IsNil() {
 		v = v.Elem()
 	}
 	return v.Interface()
@@ -65,7 +65,7 @@ func indirectToStringerOrError(a interface{}) interface{} {
 
 // stringify converts its arguments to a string and the type of the content.
 // All pointers are dereferenced, as in the text/template package.
-func stringify(args ...interface{}) (string, contentType) {
+func stringify(args ...any) (string, contentType) {
 	if len(args) == 1 {
 		switch s := indirect(args[0]).(type) {
 		case string:
diff --git a/tpl/internal/go_templates/htmltemplate/content_test.go b/tpl/internal/go_templates/htmltemplate/content_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
@@ -15,7 +16,7 @@ import (
 )
 
 func TestTypedContent(t *testing.T) {
-	data := []interface{}{
+	data := []any{
 		`<b> "foo%" O'Reilly &bar;`,
 		htmltemplate.CSS(`a[href =~ "//example.com"]#foo`),
 		htmltemplate.HTML(`Hello, <b>World</b> &amp;tc!`),
@@ -452,7 +453,7 @@ func TestEscapingNilNonemptyInterfaces(t *testing.T) {
 
 	// A non-empty interface should print like an empty interface.
 	want := new(bytes.Buffer)
-	data := struct{ E interface{} }{}
+	data := struct{ E any }{}
 	tmpl.Execute(want, data)
 
 	if !bytes.Equal(want.Bytes(), got.Bytes()) {
diff --git a/tpl/internal/go_templates/htmltemplate/context.go b/tpl/internal/go_templates/htmltemplate/context.go
@@ -4,7 +4,11 @@
 
 package template
 
-import "fmt"
+import (
+	"fmt"
+
+	"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+)
 
 // context describes the state an HTML parser must be in when it reaches the
 // portion of HTML produced by evaluating a particular template node.
@@ -20,6 +24,7 @@ type context struct {
 	jsCtx   jsCtx
 	attr    attr
 	element element
+	n       parse.Node // for range break/continue
 	err     *Error
 }
 
@@ -139,6 +144,8 @@ const (
 	// stateError is an infectious error state outside any valid
 	// HTML/CSS/JS construct.
 	stateError
+	// stateDead marks unreachable code after a {{break}} or {{continue}}.
+	stateDead
 )
 
 // isComment is true for any state that contains content meant for template
diff --git a/tpl/internal/go_templates/htmltemplate/css.go b/tpl/internal/go_templates/htmltemplate/css.go
@@ -155,7 +155,7 @@ func isCSSSpace(b byte) bool {
 }
 
 // cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes.
-func cssEscaper(args ...interface{}) string {
+func cssEscaper(args ...any) string {
 	s, _ := stringify(args...)
 	var b strings.Builder
 	r, w, written := rune(0), 0, 0
@@ -218,7 +218,7 @@ var mozBindingBytes = []byte("mozbinding")
 // (inherit, blue), and colors (#888).
 // It filters out unsafe values, such as those that affect token boundaries,
 // and anything that might execute scripts.
-func cssValueFilter(args ...interface{}) string {
+func cssValueFilter(args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeCSS {
 		return s
diff --git a/tpl/internal/go_templates/htmltemplate/css_test.go b/tpl/internal/go_templates/htmltemplate/css_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
diff --git a/tpl/internal/go_templates/htmltemplate/error.go b/tpl/internal/go_templates/htmltemplate/error.go
@@ -229,6 +229,6 @@ func (e *Error) Error() string {
 
 // errorf creates an error given a format string f and args.
 // The template Name still needs to be supplied.
-func errorf(k ErrorCode, node parse.Node, line int, f string, args ...interface{}) *Error {
+func errorf(k ErrorCode, node parse.Node, line int, f string, args ...any) *Error {
 	return &Error{k, node, "", line, fmt.Sprintf(f, args...)}
 }
diff --git a/tpl/internal/go_templates/htmltemplate/escape.go b/tpl/internal/go_templates/htmltemplate/escape.go
@@ -46,7 +46,7 @@ func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
 
 // evalArgs formats the list of arguments into a string. It is equivalent to
 // fmt.Sprint(args...), except that it deferences all pointers.
-func evalArgs(args ...interface{}) string {
+func evalArgs(args ...any) string {
 	// Optimization for simple common case of a single string argument.
 	if len(args) == 1 {
 		if s, ok := args[0].(string); ok {
@@ -98,6 +98,15 @@ type escaper struct {
 	actionNodeEdits   map[*parse.ActionNode][]string
 	templateNodeEdits map[*parse.TemplateNode]string
 	textNodeEdits     map[*parse.TextNode][]byte
+	// rangeContext holds context about the current range loop.
+	rangeContext *rangeContext
+}
+
+// rangeContext holds information about the current range loop.
+type rangeContext struct {
+	outer     *rangeContext // outer loop
+	breaks    []context     // context at each break action
+	continues []context     // context at each continue action
 }
 
 // makeEscaper creates a blank escaper for the given set.
@@ -110,6 +119,7 @@ func makeEscaper(n *nameSpace) escaper {
 		map[*parse.ActionNode][]string{},
 		map[*parse.TemplateNode]string{},
 		map[*parse.TextNode][]byte{},
+		nil,
 	}
 }
 
@@ -125,8 +135,16 @@ func (e *escaper) escape(c context, n parse.Node) context {
 	switch n := n.(type) {
 	case *parse.ActionNode:
 		return e.escapeAction(c, n)
+	case *parse.BreakNode:
+		c.n = n
+		e.rangeContext.breaks = append(e.rangeContext.breaks, c)
+		return context{state: stateDead}
 	case *parse.CommentNode:
 		return c
+	case *parse.ContinueNode:
+		c.n = n
+		e.rangeContext.continues = append(e.rangeContext.breaks, c)
+		return context{state: stateDead}
 	case *parse.IfNode:
 		return e.escapeBranch(c, &n.BranchNode, "if")
 	case *parse.ListNode:
@@ -428,6 +446,12 @@ func join(a, b context, node parse.Node, nodeName string) context {
 	if b.state == stateError {
 		return b
 	}
+	if a.state == stateDead {
+		return b
+	}
+	if b.state == stateDead {
+		return a
+	}
 	if a.eq(b) {
 		return a
 	}
@@ -467,14 +491,27 @@ func join(a, b context, node parse.Node, nodeName string) context {
 
 // escapeBranch escapes a branch template node: "if", "range" and "with".
 func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
+	if nodeName == "range" {
+		e.rangeContext = &rangeContext{outer: e.rangeContext}
+	}
 	c0 := e.escapeList(c, n.List)
-	if nodeName == "range" && c0.state != stateError {
+	if nodeName == "range" {
+		if c0.state != stateError {
+			c0 = joinRange(c0, e.rangeContext)
+		}
+		e.rangeContext = e.rangeContext.outer
+		if c0.state == stateError {
+			return c0
+		}
+
 		// The "true" branch of a "range" node can execute multiple times.
 		// We check that executing n.List once results in the same context
 		// as executing n.List twice.
+		e.rangeContext = &rangeContext{outer: e.rangeContext}
 		c1, _ := e.escapeListConditionally(c0, n.List, nil)
 		c0 = join(c0, c1, n, nodeName)
 		if c0.state == stateError {
+			e.rangeContext = e.rangeContext.outer
 			// Make clear that this is a problem on loop re-entry
 			// since developers tend to overlook that branch when
 			// debugging templates.
@@ -482,11 +519,39 @@ func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) 
 			c0.err.Description = "on range loop re-entry: " + c0.err.Description
 			return c0
 		}
+		c0 = joinRange(c0, e.rangeContext)
+		e.rangeContext = e.rangeContext.outer
+		if c0.state == stateError {
+			return c0
+		}
 	}
 	c1 := e.escapeList(c, n.ElseList)
 	return join(c0, c1, n, nodeName)
 }
 
+func joinRange(c0 context, rc *rangeContext) context {
+	// Merge contexts at break and continue statements into overall body context.
+	// In theory we could treat breaks differently from continues, but for now it is
+	// enough to treat them both as going back to the start of the loop (which may then stop).
+	for _, c := range rc.breaks {
+		c0 = join(c0, c, c.n, "range")
+		if c0.state == stateError {
+			c0.err.Line = c.n.(*parse.BreakNode).Line
+			c0.err.Description = "at range loop break: " + c0.err.Description
+			return c0
+		}
+	}
+	for _, c := range rc.continues {
+		c0 = join(c0, c, c.n, "range")
+		if c0.state == stateError {
+			c0.err.Line = c.n.(*parse.ContinueNode).Line
+			c0.err.Description = "at range loop continue: " + c0.err.Description
+			return c0
+		}
+	}
+	return c0
+}
+
 // escapeList escapes a list template node.
 func (e *escaper) escapeList(c context, n *parse.ListNode) context {
 	if n == nil {
@@ -494,6 +559,9 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context {
 	}
 	for _, m := range n.Nodes {
 		c = e.escape(c, m)
+		if c.state == stateDead {
+			break
+		}
 	}
 	return c
 }
@@ -504,6 +572,7 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context {
 // which is the same as whether e was updated.
 func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
 	e1 := makeEscaper(e.ns)
+	e1.rangeContext = e.rangeContext
 	// Make type inferences available to f.
 	for k, v := range e.output {
 		e1.output[k] = v
@@ -866,7 +935,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 template.HTMLEscaper(args...)
 }
 
@@ -882,12 +951,12 @@ func JSEscapeString(s string) string {
 
 // JSEscaper returns the escaped JavaScript equivalent of the textual
 // representation of its arguments.
-func JSEscaper(args ...interface{}) string {
+func JSEscaper(args ...any) string {
 	return template.JSEscaper(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 template.URLQueryEscaper(args...)
 }
diff --git a/tpl/internal/go_templates/htmltemplate/escape_test.go b/tpl/internal/go_templates/htmltemplate/escape_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
@@ -39,8 +40,8 @@ func TestEscape(t *testing.T) {
 		A, E    []string
 		B, M    json.Marshaler
 		N       int
-		U       interface{} // untyped nil
-		Z       *int        // typed nil
+		U       any  // untyped nil
+		Z       *int // typed nil
 		W       htmltemplate.HTML
 	}{
 		F: false,
@@ -862,7 +863,7 @@ func TestEscapeSet(t *testing.T) {
 
 	// pred is a template function that returns the predecessor of a
 	// natural number for testing recursive templates.
-	fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) {
+	fns := FuncMap{"pred": func(a ...any) (any, error) {
 		if len(a) == 1 {
 			if i, _ := a[0].(int); i > 0 {
 				return i - 1, nil
@@ -924,6 +925,22 @@ func TestErrors(t *testing.T) {
 			"<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>",
 			"",
 		},
+		{
+			"{{range .Items}}<a{{if .X}}{{end}}>{{end}}",
+			"",
+		},
+		{
+			"{{range .Items}}<a{{if .X}}{{end}}>{{continue}}{{end}}",
+			"",
+		},
+		{
+			"{{range .Items}}<a{{if .X}}{{end}}>{{break}}{{end}}",
+			"",
+		},
+		{
+			"{{range .Items}}<a{{if .X}}{{end}}>{{if .X}}{{break}}{{end}}{{end}}",
+			"",
+		},
 		// Error cases.
 		{
 			"{{if .Cond}}<a{{end}}",
@@ -960,6 +977,14 @@ func TestErrors(t *testing.T) {
 			"z:2:8: on range loop re-entry: {{range}} branches",
 		},
 		{
+			"{{range .Items}}<a{{if .X}}{{break}}{{end}}>{{end}}",
+			"z:1:29: at range loop break: {{range}} branches end in different contexts",
+		},
+		{
+			"{{range .Items}}<a{{if .X}}{{continue}}{{end}}>{{end}}",
+			"z:1:29: at range loop continue: {{range}} branches end in different contexts",
+		},
+		{
 			"<a b=1 c={{.H}}",
 			"z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
 		},
@@ -1768,7 +1793,7 @@ func TestEscapeSetErrorsNotIgnorable(t *testing.T) {
 }
 
 func TestRedundantFuncs(t *testing.T) {
-	inputs := []interface{}{
+	inputs := []any{
 		"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
 			"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
 			` !"#$%&'()*+,-./` +
@@ -1788,9 +1813,9 @@ func TestRedundantFuncs(t *testing.T) {
 	}
 
 	for n0, m := range redundantFuncs {
-		f0 := funcMap[n0].(func(...interface{}) string)
+		f0 := funcMap[n0].(func(...any) string)
 		for n1 := range m {
-			f1 := funcMap[n1].(func(...interface{}) string)
+			f1 := funcMap[n1].(func(...any) string)
 			for _, input := range inputs {
 				want := f0(input)
 				if got := f1(want); want != got {
diff --git a/tpl/internal/go_templates/htmltemplate/example_test.go b/tpl/internal/go_templates/htmltemplate/example_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package template_test
@@ -101,7 +102,7 @@ func Example_autoescaping() {
 
 func Example_escape() {
 	const s = `"Fran & Freddie's Diner" <tasty@example.com>`
-	v := []interface{}{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`}
+	v := []any{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`}
 
 	fmt.Println(template.HTMLEscapeString(s))
 	template.HTMLEscape(os.Stdout, []byte(s))
diff --git a/tpl/internal/go_templates/htmltemplate/examplefiles_test.go b/tpl/internal/go_templates/htmltemplate/examplefiles_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package template_test
diff --git a/tpl/internal/go_templates/htmltemplate/exec_test.go b/tpl/internal/go_templates/htmltemplate/exec_test.go
@@ -4,6 +4,7 @@
 
 // Tests for template execution, copied from text/template.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
@@ -53,7 +54,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
@@ -63,11 +64,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
@@ -145,7 +146,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"},
@@ -216,7 +217,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)
 }
 
@@ -256,7 +257,7 @@ func (u *U) TrueFalse(b bool) string {
 	return ""
 }
 
-func typeOf(arg interface{}) string {
+func typeOf(arg any) string {
 	return fmt.Sprintf("%T", arg)
 }
 
@@ -264,7 +265,7 @@ type execTest struct {
 	name   string
 	input  string
 	output string
-	data   interface{}
+	data   any
 	ok     bool
 }
 
@@ -397,7 +398,7 @@ var execTests = []execTest{
 	{".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=&lt;he&#43;llo&gt;", 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},
 
@@ -571,6 +572,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},
@@ -742,7 +745,7 @@ func add(args ...int) int {
 	return sum
 }
 
-func echo(arg interface{}) interface{} {
+func echo(arg any) any {
 	return arg
 }
 
@@ -761,7 +764,7 @@ func stringer(s fmt.Stringer) string {
 	return s.String()
 }
 
-func mapOfThree() interface{} {
+func mapOfThree() any {
 	return map[string]int{"three": 3}
 }
 
@@ -1440,7 +1443,7 @@ func TestBlock(t *testing.T) {
 func TestEvalFieldErrors(t *testing.T) {
 	tests := []struct {
 		name, src string
-		value     interface{}
+		value     any
 		want      string
 	}{
 		{
@@ -1583,7 +1586,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
 			},
@@ -1612,7 +1615,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")
 		},
@@ -1620,7 +1623,7 @@ func TestExecutePanicDuringCall(t *testing.T) {
 	tests := []struct {
 		name    string
 		input   string
-		data    interface{}
+		data    any
 		wantErr string
 	}{
 		{
@@ -1724,8 +1727,6 @@ var v = "v";
 `
 
 func TestEscapeRace(t *testing.T) {
-	// t.Skip("this test currently fails with -race; see issue #39807")
-
 	tmpl := New("")
 	_, err := tmpl.New("templ.html").Parse(raceText)
 	if err != nil {
@@ -1820,7 +1821,7 @@ func TestRecursiveExecuteViaMethod(t *testing.T) {
 func TestTemplateFuncsAfterClone(t *testing.T) {
 	s := `{{ f . }}`
 	want := "test"
-	orig := New("orig").Funcs(map[string]interface{}{
+	orig := New("orig").Funcs(map[string]any{
 		"f": func(in string) string {
 			return in
 		},
diff --git a/tpl/internal/go_templates/htmltemplate/html.go b/tpl/internal/go_templates/htmltemplate/html.go
@@ -12,7 +12,7 @@ import (
 )
 
 // htmlNospaceEscaper escapes for inclusion in unquoted attribute values.
-func htmlNospaceEscaper(args ...interface{}) string {
+func htmlNospaceEscaper(args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeHTML {
 		return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false)
@@ -21,7 +21,7 @@ func htmlNospaceEscaper(args ...interface{}) string {
 }
 
 // attrEscaper escapes for inclusion in quoted attribute values.
-func attrEscaper(args ...interface{}) string {
+func attrEscaper(args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeHTML {
 		return htmlReplacer(stripTags(s), htmlNormReplacementTable, true)
@@ -30,7 +30,7 @@ func attrEscaper(args ...interface{}) string {
 }
 
 // rcdataEscaper escapes for inclusion in an RCDATA element body.
-func rcdataEscaper(args ...interface{}) string {
+func rcdataEscaper(args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeHTML {
 		return htmlReplacer(s, htmlNormReplacementTable, true)
@@ -39,7 +39,7 @@ func rcdataEscaper(args ...interface{}) string {
 }
 
 // htmlEscaper escapes for inclusion in HTML text.
-func htmlEscaper(args ...interface{}) string {
+func htmlEscaper(args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeHTML {
 		return s
@@ -225,7 +225,7 @@ func stripTags(html string) string {
 
 // htmlNameFilter accepts valid parts of an HTML attribute or tag name or
 // a known-safe HTML attribute.
-func htmlNameFilter(args ...interface{}) string {
+func htmlNameFilter(args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeHTMLAttr {
 		return s
@@ -260,6 +260,6 @@ func htmlNameFilter(args ...interface{}) string {
 // content interpolated into comments.
 // This approach is equally valid whether or not static comment content is
 // removed from the template.
-func commentEscaper(args ...interface{}) string {
+func commentEscaper(args ...any) string {
 	return ""
 }
diff --git a/tpl/internal/go_templates/htmltemplate/html_test.go b/tpl/internal/go_templates/htmltemplate/html_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
diff --git a/tpl/internal/go_templates/htmltemplate/js.go b/tpl/internal/go_templates/htmltemplate/js.go
@@ -123,7 +123,7 @@ var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
 
 // indirectToJSONMarshaler returns the value, after dereferencing as many times
 // as necessary to reach the base type (or nil) or an implementation of json.Marshal.
-func indirectToJSONMarshaler(a interface{}) interface{} {
+func indirectToJSONMarshaler(a any) any {
 	// text/template now supports passing untyped nil as a func call
 	// argument, so we must support it. Otherwise we'd panic below, as one
 	// cannot call the Type or Interface methods on an invalid
@@ -133,7 +133,7 @@ func indirectToJSONMarshaler(a interface{}) interface{} {
 	}
 
 	v := reflect.ValueOf(a)
-	for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() {
+	for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Pointer && !v.IsNil() {
 		v = v.Elem()
 	}
 	return v.Interface()
@@ -141,8 +141,8 @@ func indirectToJSONMarshaler(a interface{}) interface{} {
 
 // jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
 // neither side-effects nor free variables outside (NaN, Infinity).
-func jsValEscaper(args ...interface{}) string {
-	var a interface{}
+func jsValEscaper(args ...any) string {
+	var a any
 	if len(args) == 1 {
 		a = indirectToJSONMarshaler(args[0])
 		switch t := a.(type) {
@@ -225,7 +225,7 @@ func jsValEscaper(args ...interface{}) string {
 // jsStrEscaper produces a string that can be included between quotes in
 // JavaScript source, in JavaScript embedded in an HTML5 <script> element,
 // or in an HTML5 event handler attribute such as onclick.
-func jsStrEscaper(args ...interface{}) string {
+func jsStrEscaper(args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeJSStr {
 		return replace(s, jsStrNormReplacementTable)
@@ -237,7 +237,7 @@ func jsStrEscaper(args ...interface{}) string {
 // specials so the result is treated literally when included in a regular
 // expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
 // the literal text of {{.X}} followed by the string "bar".
-func jsRegexpEscaper(args ...interface{}) string {
+func jsRegexpEscaper(args ...any) string {
 	s, _ := stringify(args...)
 	s = replace(s, jsRegexpReplacementTable)
 	if s == "" {
@@ -399,9 +399,7 @@ func isJSType(mimeType string) bool {
 	//   https://tools.ietf.org/html/rfc4329#section-3
 	//   https://www.ietf.org/rfc/rfc4627.txt
 	// discard parameters
-	if i := strings.Index(mimeType, ";"); i >= 0 {
-		mimeType = mimeType[:i]
-	}
+	mimeType, _, _ = strings.Cut(mimeType, ";")
 	mimeType = strings.ToLower(mimeType)
 	mimeType = strings.TrimSpace(mimeType)
 	switch mimeType {
diff --git a/tpl/internal/go_templates/htmltemplate/js_test.go b/tpl/internal/go_templates/htmltemplate/js_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
@@ -105,7 +106,7 @@ func TestNextJsCtx(t *testing.T) {
 
 func TestJSValEscaper(t *testing.T) {
 	tests := []struct {
-		x  interface{}
+		x  any
 		js string
 	}{
 		{int(42), " 42 "},
@@ -142,8 +143,8 @@ func TestJSValEscaper(t *testing.T) {
 		// "\v" == "v" on IE 6 so use "\u000b" instead.
 		{"\t\x0b", `"\t\u000b"`},
 		{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
-		{[]interface{}{}, "[]"},
-		{[]interface{}{42, "foo", nil}, `[42,"foo",null]`},
+		{[]any{}, "[]"},
+		{[]any{42, "foo", nil}, `[42,"foo",null]`},
 		{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
 		{"<!--", `"\u003c!--"`},
 		{"-->", `"--\u003e"`},
@@ -160,7 +161,7 @@ func TestJSValEscaper(t *testing.T) {
 		}
 		// Make sure that escaping corner cases are not broken
 		// by nesting.
-		a := []interface{}{test.x}
+		a := []any{test.x}
 		want := "[" + strings.TrimSpace(test.js) + "]"
 		if js := jsValEscaper(a); js != want {
 			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
@@ -170,7 +171,7 @@ func TestJSValEscaper(t *testing.T) {
 
 func TestJSStrEscaper(t *testing.T) {
 	tests := []struct {
-		x   interface{}
+		x   any
 		esc string
 	}{
 		{"", ``},
@@ -225,7 +226,7 @@ func TestJSStrEscaper(t *testing.T) {
 
 func TestJSRegexpEscaper(t *testing.T) {
 	tests := []struct {
-		x   interface{}
+		x   any
 		esc string
 	}{
 		{"", `(?:)`},
@@ -280,7 +281,7 @@ func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
 
 	tests := []struct {
 		name    string
-		escaper func(...interface{}) string
+		escaper func(...any) string
 		escaped string
 	}{
 		{
diff --git a/tpl/internal/go_templates/htmltemplate/multi_test.go b/tpl/internal/go_templates/htmltemplate/multi_test.go
@@ -4,6 +4,7 @@
 
 // Tests for multiple-template execution, copied from text/template.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
diff --git a/tpl/internal/go_templates/htmltemplate/template.go b/tpl/internal/go_templates/htmltemplate/template.go
@@ -118,7 +118,7 @@ func (t *Template) escape() 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) Execute(wr io.Writer, data interface{}) error {
+func (t *Template) Execute(wr io.Writer, data any) error {
 	if err := t.escape(); err != nil {
 		return err
 	}
@@ -132,7 +132,7 @@ func (t *Template) Execute(wr io.Writer, data interface{}) 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, err := t.lookupAndEscapeTemplate(name)
 	if err != nil {
 		return err
@@ -336,7 +336,7 @@ func (t *Template) Name() string {
 // terminates and Execute returns that error. FuncMap has the same base type
 // as FuncMap in "text/template", copied here so clients need not import
 // "text/template".
-type FuncMap map[string]interface{}
+type FuncMap map[string]any
 
 // Funcs adds the elements of the argument map to the template's function map.
 // It must be called before the template is parsed.
@@ -487,7 +487,7 @@ func parseGlob(t *Template, pattern string) (*Template, error) {
 // 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 template.IsTrue(val)
 }
 
diff --git a/tpl/internal/go_templates/htmltemplate/template_test.go b/tpl/internal/go_templates/htmltemplate/template_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package template_test
@@ -209,7 +210,7 @@ func (c *testCase) mustNotParse(t *Template, text string) {
 	}
 }
 
-func (c *testCase) mustExecute(t *Template, val interface{}, want string) {
+func (c *testCase) mustExecute(t *Template, val any, want string) {
 	var buf bytes.Buffer
 	err := t.Execute(&buf, val)
 	if err != nil {
diff --git a/tpl/internal/go_templates/htmltemplate/transition_test.go b/tpl/internal/go_templates/htmltemplate/transition_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
diff --git a/tpl/internal/go_templates/htmltemplate/url.go b/tpl/internal/go_templates/htmltemplate/url.go
@@ -32,7 +32,7 @@ import (
 // To allow URLs containing other schemes to bypass this filter, developers must
 // explicitly indicate that such a URL is expected and safe by encapsulating it
 // in a template.URL value.
-func urlFilter(args ...interface{}) string {
+func urlFilter(args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeURL {
 		return s
@@ -46,9 +46,7 @@ func urlFilter(args ...interface{}) string {
 // isSafeURL is true if s is a relative URL or if URL has a protocol in
 // (http, https, mailto).
 func isSafeURL(s string) bool {
-	if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
-
-		protocol := s[:i]
+	if protocol, _, ok := strings.Cut(s, ":"); ok && !strings.Contains(protocol, "/") {
 		if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") {
 			return false
 		}
@@ -58,7 +56,7 @@ func isSafeURL(s string) bool {
 
 // urlEscaper produces an output that can be embedded in a URL query.
 // The output can be embedded in an HTML attribute without further escaping.
-func urlEscaper(args ...interface{}) string {
+func urlEscaper(args ...any) string {
 	return urlProcessor(false, args...)
 }
 
@@ -67,13 +65,13 @@ func urlEscaper(args ...interface{}) string {
 // The normalizer does not encode all HTML specials. Specifically, it does not
 // encode '&' so correct embedding in an HTML attribute requires escaping of
 // '&' to '&amp;'.
-func urlNormalizer(args ...interface{}) string {
+func urlNormalizer(args ...any) string {
 	return urlProcessor(true, args...)
 }
 
 // urlProcessor normalizes (when norm is true) or escapes its input to produce
 // a valid hierarchical or opaque URL part.
-func urlProcessor(norm bool, args ...interface{}) string {
+func urlProcessor(norm bool, args ...any) string {
 	s, t := stringify(args...)
 	if t == contentTypeURL {
 		norm = true
@@ -143,7 +141,7 @@ func processURLOnto(s string, norm bool, b *bytes.Buffer) bool {
 
 // Filters and normalizes srcset values which are comma separated
 // URLs followed by metadata.
-func srcsetFilterAndEscaper(args ...interface{}) string {
+func srcsetFilterAndEscaper(args ...any) string {
 	s, t := stringify(args...)
 	switch t {
 	case contentTypeSrcset:
diff --git a/tpl/internal/go_templates/htmltemplate/url_test.go b/tpl/internal/go_templates/htmltemplate/url_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
@@ -50,7 +51,7 @@ func TestURLFilters(t *testing.T) {
 
 	tests := []struct {
 		name    string
-		escaper func(...interface{}) string
+		escaper func(...any) string
 		escaped string
 	}{
 		{
diff --git a/tpl/internal/go_templates/testenv/testenv.go b/tpl/internal/go_templates/testenv/testenv.go
@@ -11,6 +11,7 @@
 package testenv
 
 import (
+	"bytes"
 	"errors"
 	"flag"
 	"github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
@@ -22,6 +23,7 @@ import (
 	"strings"
 	"sync"
 	"testing"
+	"time"
 )
 
 // Builder reports the name of the builder running this test
@@ -306,3 +308,59 @@ func SkipIfShortAndSlow(t testing.TB) {
 		t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
 	}
 }
+
+// RunWithTimeout runs cmd and returns its combined output. If the
+// subprocess exits with a non-zero status, it will log that status
+// and return a non-nil error, but this is not considered fatal.
+func RunWithTimeout(t testing.TB, cmd *exec.Cmd) ([]byte, error) {
+	args := cmd.Args
+	if args == nil {
+		args = []string{cmd.Path}
+	}
+
+	var b bytes.Buffer
+	cmd.Stdout = &b
+	cmd.Stderr = &b
+	if err := cmd.Start(); err != nil {
+		t.Fatalf("starting %s: %v", args, err)
+	}
+
+	// If the process doesn't complete within 1 minute,
+	// assume it is hanging and kill it to get a stack trace.
+	p := cmd.Process
+	done := make(chan bool)
+	go func() {
+		scale := 1
+		// This GOARCH/GOOS test is copied from cmd/dist/test.go.
+		// TODO(iant): Have cmd/dist update the environment variable.
+		if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
+			scale = 2
+		}
+		if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
+			if sc, err := strconv.Atoi(s); err == nil {
+				scale = sc
+			}
+		}
+
+		select {
+		case <-done:
+		case <-time.After(time.Duration(scale) * time.Minute):
+			p.Signal(Sigquit)
+			// If SIGQUIT doesn't do it after a little
+			// while, kill the process.
+			select {
+			case <-done:
+			case <-time.After(time.Duration(scale) * 30 * time.Second):
+				p.Signal(os.Kill)
+			}
+		}
+	}()
+
+	err := cmd.Wait()
+	if err != nil {
+		t.Logf("%s exit status: %v", args, err)
+	}
+	close(done)
+
+	return b.Bytes(), err
+}
diff --git a/tpl/internal/go_templates/testenv/testenv_cgo.go b/tpl/internal/go_templates/testenv/testenv_cgo.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build cgo
+//go:build cgo
 
 package testenv
 
diff --git a/tpl/internal/go_templates/testenv/testenv_notunix.go b/tpl/internal/go_templates/testenv/testenv_notunix.go
@@ -0,0 +1,13 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows || plan9 || (js && wasm)
+
+package testenv
+
+import "os"
+
+// Sigquit is the signal to send to kill a hanging subprocess.
+// On Unix we send SIGQUIT, but on non-Unix we only have os.Kill.
+var Sigquit = os.Kill
diff --git a/tpl/internal/go_templates/testenv/testenv_notwin.go b/tpl/internal/go_templates/testenv/testenv_notwin.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build !windows
+//go:build !windows
 
 package testenv
 
diff --git a/tpl/internal/go_templates/testenv/testenv_unix.go b/tpl/internal/go_templates/testenv/testenv_unix.go
@@ -0,0 +1,13 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
+
+package testenv
+
+import "syscall"
+
+// Sigquit is the signal to send to kill a hanging subprocess.
+// Send SIGQUIT to get a stack trace.
+var Sigquit = syscall.SIGQUIT
diff --git a/tpl/internal/go_templates/texttemplate/doc.go b/tpl/internal/go_templates/texttemplate/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/tpl/internal/go_templates/texttemplate/example_test.go b/tpl/internal/go_templates/texttemplate/example_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package template_test
diff --git a/tpl/internal/go_templates/texttemplate/examplefiles_test.go b/tpl/internal/go_templates/texttemplate/examplefiles_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package template_test
diff --git a/tpl/internal/go_templates/texttemplate/examplefunc_test.go b/tpl/internal/go_templates/texttemplate/examplefunc_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package template_test
diff --git a/tpl/internal/go_templates/texttemplate/exec.go b/tpl/internal/go_templates/texttemplate/exec.go
@@ -5,14 +5,14 @@
 package template
 
 import (
+	"errors"
 	"fmt"
+	"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
+	"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 	"io"
 	"reflect"
 	"runtime"
 	"strings"
-
-	"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
-	"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 )
 
 // maxExecDepth specifies the maximum stack depth of templates within
@@ -126,7 +126,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)
@@ -179,7 +179,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)
@@ -197,11 +197,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 {
@@ -228,7 +228,6 @@ func (t *Template) DefinedTemplates() string {
 		return ""
 	}
 	var b strings.Builder
-	// temporary Hugo-fix
 	t.muTmpl.RLock()
 	defer t.muTmpl.RUnlock()
 	for name, tmpl := range t.tmpl {
@@ -245,6 +244,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 +262,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 +311,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 +327,7 @@ func isTrueOld(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 +345,11 @@ func isTrueOld(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 +363,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 +594,11 @@ func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ide
 func (s *state) evalFunctionOld(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 +623,11 @@ func (s *state) evalFieldOld(dot reflect.Value, fieldName string, node parse.Nod
 	// 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 +635,13 @@ func (s *state) evalFieldOld(dot reflect.Value, fieldName string, node parse.Nod
 	case reflect.Struct:
 		tField, ok := receiver.Type().FieldByName(fieldName)
 		if ok {
-			field := receiver.FieldByIndex(tField.Index)
-			if tField.PkgPath != "" { // field is unexported
+			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 +668,7 @@ func (s *state) evalFieldOld(dot reflect.Value, fieldName string, node parse.Nod
 			}
 			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 +694,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) evalCallOld(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
+func (s *state) evalCallOld(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 +716,38 @@ func (s *state) evalCallOld(dot, fun reflect.Value, node parse.Node, name string
 		// 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.
@@ -728,18 +783,15 @@ func (s *state) evalCallOld(dot, fun reflect.Value, node parse.Node, name string
 	// error to the caller.
 	if err != nil {
 		s.at(node)
-		s.errorf("error calling %s: %v", name, err)
-	}
-	if v.Type() == reflectValueType {
-		v = v.Interface().(reflect.Value)
+		s.errorf("error calling %s: %w", name, err)
 	}
-	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,15 +828,13 @@ 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()
-		case value.IsZero():
-			s.errorf("got <nil>, expected %s", typ)
 		default:
 			s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
 		}
@@ -935,7 +985,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
 		}
@@ -973,8 +1023,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() {
@@ -982,7 +1032,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/tpl/internal/go_templates/texttemplate/exec_test.go b/tpl/internal/go_templates/texttemplate/exec_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
@@ -14,6 +15,7 @@ import (
 	"io"
 	"reflect"
 	"strings"
+	"sync"
 	"testing"
 )
 
@@ -47,7 +49,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
@@ -57,11 +59,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
@@ -139,7 +141,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"},
@@ -210,7 +212,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)
 }
 
@@ -250,7 +252,7 @@ func (u *U) TrueFalse(b bool) string {
 	return ""
 }
 
-func typeOf(arg interface{}) string {
+func typeOf(arg any) string {
 	return fmt.Sprintf("%T", arg)
 }
 
@@ -258,7 +260,7 @@ type execTest struct {
 	name   string
 	input  string
 	output string
-	data   interface{}
+	data   any
 	ok     bool
 }
 
@@ -391,7 +393,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},
 
@@ -482,8 +484,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},
@@ -565,6 +578,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},
@@ -736,7 +751,7 @@ func add(args ...int) int {
 	return sum
 }
 
-func echo(arg interface{}) interface{} {
+func echo(arg any) any {
 	return arg
 }
 
@@ -755,7 +770,7 @@ func stringer(s fmt.Stringer) string {
 	return s.String()
 }
 
-func mapOfThree() interface{} {
+func mapOfThree() any {
 	return map[string]int{"three": 3}
 }
 
@@ -765,6 +780,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,
@@ -904,6 +920,28 @@ func TestExecError(t *testing.T) {
 	}
 }
 
+type CustomError struct{}
+
+func (*CustomError) Error() string { return "heyo !" }
+
+// Check that a custom error can be returned.
+func TestExecError_CustomError(t *testing.T) {
+	failingFunc := func() (string, error) {
+		return "", &CustomError{}
+	}
+	tmpl := Must(New("top").Funcs(FuncMap{
+		"err": failingFunc,
+	}).Parse("{{ err }}"))
+
+	var b bytes.Buffer
+	err := tmpl.Execute(&b, nil)
+
+	var e *CustomError
+	if !errors.As(err, &e) {
+		t.Fatalf("expected custom error; got %s", err)
+	}
+}
+
 func TestJSEscaping(t *testing.T) {
 	testCases := []struct {
 		in, exp string
@@ -1180,8 +1218,11 @@ var cmpTests = []cmpTest{
 	{"eq .Ptr .NilPtr", "false", true},
 	{"eq .NilPtr .NilPtr", "true", true},
 	{"eq .Iface1 .Iface1", "true", true},
-	{"eq .Iface1 .Iface2", "false", true},
-	{"eq .Iface2 .Iface2", "true", true},
+	{"eq .Iface1 .NilIface", "false", true},
+	{"eq .NilIface .NilIface", "true", true},
+	{"eq .NilIface .Iface1", "false", true},
+	{"eq .NilIface 0", "false", true},
+	{"eq 0 .NilIface", "false", true},
 	// Errors
 	{"eq `xy` 1", "", false},       // Different types.
 	{"eq 2 2.0", "", false},        // Different types.
@@ -1196,12 +1237,12 @@ var cmpTests = []cmpTest{
 func TestComparison(t *testing.T) {
 	b := new(bytes.Buffer)
 	var cmpStruct = struct {
-		Uthree, Ufour  uint
-		NegOne, Three  int
-		Ptr, NilPtr    *int
-		Map            map[int]int
-		V1, V2         V
-		Iface1, Iface2 fmt.Stringer
+		Uthree, Ufour    uint
+		NegOne, Three    int
+		Ptr, NilPtr      *int
+		Map              map[int]int
+		V1, V2           V
+		Iface1, NilIface fmt.Stringer
 	}{
 		Uthree: 3,
 		Ufour:  4,
@@ -1430,7 +1471,7 @@ func TestBlock(t *testing.T) {
 func TestEvalFieldErrors(t *testing.T) {
 	tests := []struct {
 		name, src string
-		value     interface{}
+		value     any
 		want      string
 	}{
 		{
@@ -1573,7 +1614,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
 			},
@@ -1602,7 +1643,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")
 		},
@@ -1610,7 +1651,7 @@ func TestExecutePanicDuringCall(t *testing.T) {
 	tests := []struct {
 		name    string
 		input   string
-		data    interface{}
+		data    any
 		wantErr string
 	}{
 		{
@@ -1712,3 +1753,63 @@ func TestIssue43065(t *testing.T) {
 		t.Errorf("%s", err)
 	}
 }
+
+// Issue 39807: data race in html/template & text/template
+func TestIssue39807(t *testing.T) {
+	var wg sync.WaitGroup
+
+	tplFoo, err := New("foo").Parse(`{{ template "bar" . }}`)
+	if err != nil {
+		t.Error(err)
+	}
+
+	tplBar, err := New("bar").Parse("bar")
+	if err != nil {
+		t.Error(err)
+	}
+
+	gofuncs := 10
+	numTemplates := 10
+
+	for i := 1; i <= gofuncs; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			for j := 0; j < numTemplates; j++ {
+				_, err := tplFoo.AddParseTree(tplBar.Name(), tplBar.Tree)
+				if err != nil {
+					t.Error(err)
+				}
+				err = tplFoo.Execute(io.Discard, nil)
+				if err != nil {
+					t.Error(err)
+				}
+			}
+		}()
+	}
+
+	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/tpl/internal/go_templates/texttemplate/funcs.go b/tpl/internal/go_templates/texttemplate/funcs.go
@@ -23,12 +23,15 @@ import (
 // return value evaluates to non-nil during execution, execution terminates and
 // Execute returns that error.
 //
+// Errors returned by Execute wrap the underlying error; call errors.As to
+// uncover them.
+//
 // When template execution invokes a function with an argument list, that list
 // must be assignable to the function's parameter types. Functions meant to
 // 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
@@ -136,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
@@ -344,7 +347,7 @@ func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
 
 		var err error
 		if argv[i], err = prepareArg(arg, argType); err != nil {
-			return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err)
+			return reflect.Value{}, fmt.Errorf("arg %d: %w", i, err)
 		}
 	}
 	return safeCall(fn, argv)
@@ -379,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.
@@ -475,7 +460,9 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
 			case k1 == uintKind && k2 == intKind:
 				truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int())
 			default:
-				return false, errBadComparison
+				if arg1 != zero && arg != zero {
+					return false, errBadComparison
+				}
 			}
 		} else {
 			switch k1 {
@@ -492,7 +479,7 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
 			case uintKind:
 				truth = arg1.Uint() == arg.Uint()
 			default:
-				if arg == zero {
+				if arg == zero || arg1 == zero {
 					truth = arg1 == arg
 				} else {
 					if t2 := arg.Type(); !t2.Comparable() {
@@ -640,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))
 }
 
@@ -731,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))
 }
 
@@ -746,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/tpl/internal/go_templates/texttemplate/link_test.go b/tpl/internal/go_templates/texttemplate/link_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package template_test
@@ -41,11 +42,7 @@ func main() {
 	t.Used()
 }
 `
-	td, err := os.MkdirTemp("", "text_template_TestDeadCodeElimination")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer os.RemoveAll(td)
+	td := t.TempDir()
 
 	if err := os.WriteFile(filepath.Join(td, "x.go"), []byte(prog), 0644); err != nil {
 		t.Fatal(err)
diff --git a/tpl/internal/go_templates/texttemplate/multi_test.go b/tpl/internal/go_templates/texttemplate/multi_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13 && !windows
 // +build go1.13,!windows
 
 package template
@@ -454,3 +455,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/tpl/internal/go_templates/texttemplate/option.go b/tpl/internal/go_templates/texttemplate/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/tpl/internal/go_templates/texttemplate/parse/lex.go b/tpl/internal/go_templates/texttemplate/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/tpl/internal/go_templates/texttemplate/parse/lex_test.go b/tpl/internal/go_templates/texttemplate/parse/lex_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package parse
@@ -37,6 +38,8 @@ var itemName = map[itemType]string{
 	// keywords
 	itemDot:      ".",
 	itemBlock:    "block",
+	itemBreak:    "break",
+	itemContinue: "continue",
 	itemDefine:   "define",
 	itemElse:     "else",
 	itemIf:       "if",
diff --git a/tpl/internal/go_templates/texttemplate/parse/node.go b/tpl/internal/go_templates/texttemplate/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/tpl/internal/go_templates/texttemplate/parse/parse.go b/tpl/internal/go_templates/texttemplate/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.
@@ -39,6 +39,7 @@ type Mode uint
 
 const (
 	ParseComments Mode = 1 << iota // parse comments and add them to AST
+	SkipFuncCheck                  // do not check that functions are defined
 )
 
 // Copy returns a copy of the Tree. Any parsing state is discarded.
@@ -58,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
@@ -127,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,
@@ -157,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...))
@@ -217,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.
@@ -237,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
@@ -385,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:
@@ -404,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) {
@@ -479,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:
@@ -522,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:
@@ -689,7 +729,8 @@ func (t *Tree) operand() Node {
 func (t *Tree) term() Node {
 	switch token := t.nextNonSpace(); token.typ {
 	case itemIdentifier:
-		if !t.hasFunction(token.val) {
+		checkFunc := t.Mode&SkipFuncCheck == 0
+		if checkFunc && !t.hasFunction(token.val) {
 			t.errorf("function %q not defined", token.val)
 		}
 		return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
diff --git a/tpl/internal/go_templates/texttemplate/parse/parse_test.go b/tpl/internal/go_templates/texttemplate/parse/parse_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package parse
@@ -232,6 +233,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,
@@ -281,6 +286,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, ""},
@@ -312,7 +321,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,
 }
@@ -381,6 +390,22 @@ func TestParseWithComments(t *testing.T) {
 	}
 }
 
+func TestSkipFuncCheck(t *testing.T) {
+	oldTextFormat := textFormat
+	textFormat = "%q"
+	defer func() { textFormat = oldTextFormat }()
+	tr := New("skip func check")
+	tr.Mode = SkipFuncCheck
+	tmpl, err := tr.Parse("{{fn 1 2}}", "", "", make(map[string]*Tree))
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	expected := "{{fn 1 2}}"
+	if result := tmpl.Root.String(); result != expected {
+		t.Errorf("got\n\t%v\nexpected\n\t%v", result, expected)
+	}
+}
+
 type isEmptyTest struct {
 	name  string
 	input string
diff --git a/tpl/internal/go_templates/texttemplate/template.go b/tpl/internal/go_templates/texttemplate/template.go
@@ -5,16 +5,15 @@
 package template
 
 import (
+	"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 	"reflect"
 	"sync"
-
-	"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 )
 
 // common holds the information shared by related templates.
 type common struct {
-	muTmpl sync.RWMutex         // protects tmpl (temporary Hugo-fix)
 	tmpl   map[string]*Template // Map from name to defined templates.
+	muTmpl sync.RWMutex         // protects tmpl
 	option option
 	// We use two maps, one for parsing and one for execution.
 	// This separation makes the API cleaner since it doesn't
@@ -90,7 +89,6 @@ func (t *Template) Clone() (*Template, error) {
 	if t.common == nil {
 		return nt, nil
 	}
-	// temporary Hugo-fix
 	t.muTmpl.RLock()
 	defer t.muTmpl.RUnlock()
 	for k, v := range t.tmpl {
@@ -129,10 +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) {
-	// temporary Hugo-fix
+	t.init()
 	t.muTmpl.Lock()
 	defer t.muTmpl.Unlock()
-	t.init()
 	nt := t
 	if name != t.name {
 		nt = t.New(name)
@@ -150,7 +147,6 @@ func (t *Template) Templates() []*Template {
 		return nil
 	}
 	// Return a slice so we don't expose the map.
-	// temporary Hugo-fix
 	t.muTmpl.RLock()
 	defer t.muTmpl.RUnlock()
 	m := make([]*Template, 0, len(t.tmpl))
@@ -193,7 +189,6 @@ func (t *Template) Lookup(name string) *Template {
 	if t.common == nil {
 		return nil
 	}
-	// temporary Hugo-fix
 	t.muTmpl.RLock()
 	defer t.muTmpl.RUnlock()
 	return t.tmpl[name]