Skip to content

feat: allow primitive types when interpolating#1129

Merged
a-h merged 5 commits intomainfrom
primative-interpolation
Apr 23, 2025
Merged

feat: allow primitive types when interpolating#1129
a-h merged 5 commits intomainfrom
primative-interpolation

Conversation

@joerdav
Copy link
Copy Markdown
Collaborator

@joerdav joerdav commented Apr 16, 2025

Fixes #746

@joerdav
Copy link
Copy Markdown
Collaborator Author

joerdav commented Apr 16, 2025

I think this would be the first step to opening up the floodgate for more types to be interpolated!

I like the idea that we can allow either fmt.Stringer or a custom templ.Stringer. However, interfaces like this aren't supported in generics!

So we would have to either:

  • Allow any type and just pass it into fmt.Sprint.
  • Allow any type and return errors on types we don't want to interpolate, and then pass it to fmt.Sprint

@joerdav joerdav force-pushed the primative-interpolation branch from cc0cf1c to d72b6f5 Compare April 16, 2025 09:09
@joerdav
Copy link
Copy Markdown
Collaborator Author

joerdav commented Apr 16, 2025

Earning Adrian his beers @delaneyj 😆

@delaneyj
Copy link
Copy Markdown
Contributor

It's been 84 years...

@delaneyj
Copy link
Copy Markdown
Contributor

delaneyj commented Apr 16, 2025

It's been 84 years... What about Stringer interface?

@joerdav
Copy link
Copy Markdown
Collaborator Author

joerdav commented Apr 16, 2025

See my comment above, we need to decide a way forward on that.

Go doesn't support interfaces with functions in a generic type constraint. So it isn't as simple as adding fmt.Stringer onto that union.

We would instead have to open up the interpolation to work with any so instead of just primatives, it would accept ALL types.

We would either need to just interpolate those and risk interpolating in a way the engineer doesn't expect. Or return a runtime error when a non primative/stringer type is provided (not sure which is worse).

@a-h a-h changed the title feat: allow primative types when interpolating feat: allow primitive types when interpolating Apr 16, 2025
@a-h
Copy link
Copy Markdown
Owner

a-h commented Apr 19, 2025

I would also like to support the fmt.Stringer interface, but Go generics makes it a bit tricky: https://stackoverflow.com/questions/72267243/unioning-an-interface-with-a-type-in-golang

If we accept any, then we introduce the risk of leaking struct fields:

https://go.dev/play/p/VqJPlWYmc_S

package main

import "fmt"

func main() {
	var userA = User{
		SecretKey: "super_secret_key",
		Name:      "Bob",
	}
	fmt.Printf("%v", userA)
}

type User struct {
	SecretKey string
	Name      string
}

Then, we leak the key:

{super_secret_key Bob}

In React, if you pass a complex object to a React component, you get a runtime error: Objects are not valid as a React child (found: object with keys {a}). If you meant to render a collection of children, use an array instead.

import React from 'react';
import ReactDOM from 'react-dom/client';

function App(props) {
  return (
    <div className='App'>
      <h1>{props.data}</h1>
    </div>
  );
}

var data = {
  a: "text"
}

ReactDOM.createRoot( 
  document.querySelector('#root')
).render(<App data={ data }/>)

But, that's a runtime error, rather than a compile time error. At the moment, templ gives you a compile-time error for this scenario, which I think is better.

To avoid leaking private stuff, we'd have to do something like this: https://go.dev/play/p/BonrvLUeXEl - at that point, you might even go a step further and have a runtime pluggable registry of functions for displaying types. Then, you'd be able to set a date format globally, or override it on a per-user basis: https://go.dev/play/p/sGll1QFmwQL

The tradeoff of supporting stringer is that for the reduced effort for developers (don't have to type fmt.Sprintf() etc.), you get increased likelihood of unexpected behaviour / runtime errors / runtime warnings, plus a small hit on runtime performance.

I think @joerdav's suggestion here moves things forward.

You don't have to type { fmt.Sprintf("%d", o.Number) } every time you want to print a number, but it doesn't solve anything for dates and other complex types, which is probably fine too. In those cases, you're back to doing { o.Value.String() } yourself.

@joerdav
Copy link
Copy Markdown
Collaborator Author

joerdav commented Apr 19, 2025

Another benefit of the above, is that if we do open up to 'any', this should be backwards compatible.

@delaneyj
Copy link
Copy Markdown
Contributor

delaneyj commented Apr 19, 2025

Definitely a step in the right direction. If you did accept any I'm fine with the default being coerced into something like "UNSAFE_INVALID"

Copy link
Copy Markdown
Owner

@a-h a-h left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of nits, but looks good to go otherwise.

@joerdav
Copy link
Copy Markdown
Collaborator Author

joerdav commented Apr 22, 2025

Think that's an improvement, thanks for the notes!

I think I'll add some docs around this too.

@joerdav joerdav force-pushed the primative-interpolation branch from 9e0feef to 430d36c Compare April 22, 2025 08:51
@joerdav
Copy link
Copy Markdown
Collaborator Author

joerdav commented Apr 22, 2025

I made a first pass on the docs. @a-h you are usually very good at writing clear documentation, so let me know if you have any notes!

@a-h a-h merged commit 076b696 into main Apr 23, 2025
7 checks passed
@a-h a-h deleted the primative-interpolation branch April 23, 2025 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

proposal: Allow for primitives & fmt.Stringer in addition to string in templates

3 participants