Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: text/template: annotations to enable static checking of syntax and semantics #64543

Open
adonovan opened this issue Dec 4, 2023 · 2 comments
Labels
Milestone

Comments

@adonovan
Copy link
Member

adonovan commented Dec 4, 2023

Proposal Details

(As they say in the movie business, this is only an idea, not yet a concept.)

Go's text/template package defines a dynamically typed sublanguage for templates, analogous to---but much richer than---the sublanguage of fmt.Printf. It would be convenient for template users if their LSP-enabled editor could give them real-time feedback of syntax and type errors while they are editing, just as it does for Go code or for the untyped sublanguage of fmt.Printf.

In some cases it's easy to detect statically that a string literal is passed to template.Parse and is thus subject to discipline of the template parser. In other cases it's trickier because of the complex flow of template string values through the program, or because the templates are in standalone files, perhaps loaded via go:embed. But parsing is only the first step: the parsed template then flows to a call to Execute, along with an operand value and perhaps also with a FuncMap that maps various names to functions. A static checking tool would need to know the template literal, the keys and value types of the FuncMap, and the type of the operand, in order to perform semantic checks on the template.

One approach, taken by the GoLand IDE (https://www.jetbrains.com/help/go/integration-with-go-templates.html), is to require the user to write special comments in the template to indicate the type of the corresponding Go operand. This is a good start, and I imagine that many users are willing to pay the cost of modifying their templates to benefit from improved static checking. But I wonder whether we can take this approach a step further by declaring a standard form for the necessary type annotations, one that would support FuncMaps as well. In the general case, value flow---of template strings, parsed templates, FuncMaps, and operand values--can be arbitrarily complex, but in most cases I suspect the relationships are simple enough that a few heuristics would go a long way.

For example, this case could be supported without annotations:

const src = `{{..template..}}`

var t *template.Template

func parse(bonusFeature bool) {
        fm := template.FuncMap{   	   
   	   "foo":  foo,
	   ...
	}
	if bonusFeature {
	     fm["bonus"] = bonus
	}	
        t, _ = template.New("name").Parse(src).Funcs(fm)
}

func expand(x *MyData) {
        t.Execute(os.Stdout, x)
})

func foo(string) int
func bonus(string) string

An analyzer could infer that t was parsed from src, with functions "foo" of type func(string) int and (optionally) "bonus" of type func(int) string, enabling static checking of its syntax and inference of a fancy type for t. The call in expand would combine this fancy type with the type of the operand x and report semantic errors.

But its easy to imagine more complex logic surrounding src, fm, t, and x that obfuscates the checker. The purpose of this issue is to gather ideas about what kinds of relationships it would be necessary to express using an annotation mechanism, to come up with a design, and evaluate an experimental implementation.

@gopherbot gopherbot added this to the Proposal milestone Dec 4, 2023
@adonovan adonovan changed the title proposal: test/template: annotations to enable static checking of syntax and semantics proposal: text/template: annotations to enable static checking of syntax and semantics Dec 4, 2023
@adonovan
Copy link
Member Author

TIL that @jba, who sits a mere jellybean-throw away from me, wrote https://pkg.go.dev/github.com/jba/templatecheck, which looks highly relevant.

@jba
Copy link
Contributor

jba commented Dec 24, 2023

I see this idea as complementary to the templatecheck package.

If you just want to be pretty sure that your template won't crash in production, then running templatecheck in tests suffices, as we do for pkgsite. It's not perfect. The biggest hole is sub-templates established with define: you have to check those separately by providing a type (search for subs in the code I linked above), and templatecheck does not verify that all calls to the sub-template pass that type.

Of course, test time is too late to provide in-editor syntax checking.

I recently added "strict" checking to templatecheck, which guarantees no type errors provided you stick to restricted semantics. Again, the biggest restriction is passing the same type in every call to a sub-template.

The type CheckedTemplate[T] denotes a template that has satisfied strict checking. Its Execute method accepts values only of type T. If a static analyzer observes a CheckedTemplate[T] and can associate the template text with it, then it should have an easy job. I don't think any annotations in the template text would be needed.

In general, I think the most valuable annotations, and maybe the only ones you'd need, are for the types of sub-templates (and perhaps the main template). I think if you had those then you could derive all the other types, and I think uses of the more relaxed template semantics will be rare outside of passing different types to (sub-)templates. For example, I'd be surprised if there were many instances where the same variable was assigned values of different types, or where an and call whose arguments were of different types was used for its actual value rather than its truth-value.

But I see that GoLand lets you write a type for any part of a template, so maybe I'm wrong about that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

3 participants