Skip to content

feat(v3/bindings): add support for binding standard time.Time type as JS Date object#5398

Open
fbbdev wants to merge 6 commits into
wailsapp:masterfrom
fbbdev:feat/v3/time-type
Open

feat(v3/bindings): add support for binding standard time.Time type as JS Date object#5398
fbbdev wants to merge 6 commits into
wailsapp:masterfrom
fbbdev:feat/v3/time-type

Conversation

@fbbdev
Copy link
Copy Markdown
Contributor

@fbbdev fbbdev commented May 10, 2026

Description

This PR adds support for mapping the standard Go time.Time type to a Date object in generated bindings.

To avoid breaking existing apps, Time-to-Date translation is disabled by default and only enabled through the addition of a new flag -time-type=Date.

When the flag is not provided, or the binding generator runs in interface mode, time.Time values map to the JS string type rather than any as before.

The taskfile template has been updated so that newly created projects will be automatically opted in to the new behaviour. Users have the choice to opt out by editing their taskfile.

Fixes #4753

NOTE: No documentation update is required because the docs already state (wrongly) that time.Time maps to JS Date objects.

Design choices

  • Initial plans for this PR included temporal API support (opt in through -time-type=Temporal. This has been postponed because Temporal objects serialize to JSON as RFC9557 strings (a superset of RFC3339), while Go's marshal/json v1 only supports strict RFC3339.
  • I briefly considered the idea of adding a facility in the binding generator to make it easy to add more special cases beyond time.Time, but I scrapped it because IMO special casing is harmful and best avoided except in rare and justified cases.

Type of change

Please select the option that is relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration using wails doctor.

  • Windows
  • macOS
  • Linux Debian 13.4 (trixie)

New and existing binding generator tests verified to pass.
Binding example updated and tested manually.

Test Configuration

 Wails (v3.0.0-dev)  Wails Doctor 
                                                                                                                                                      
# System 

┌──────────────────────────────────────────────────────┐
| Name                | Debian GNU/Linux               |
| Version             | 13                             |
| ID                  | debian                         |
| Branding            | 13 (trixie)                    |
| Platform            | linux                          |
| Architecture        | amd64                          |
| Desktop Environment | unset                          |
| NVIDIA Driver       | N/A                            |
| XDG_SESSION_TYPE    | unset                          |
| CPU                 | Intel(R) Core(TM) Ultra 7 265K |
| GPU                 | Unknown                        |
| Memory              | 31GB                           |
└──────────────────────────────────────────────────────┘

# Build Environment 

┌──────────────────────────────────────────────────────────────────────┐
| Wails CLI      | v3.0.0-dev                                          |
| Go Version     | go1.26.2                                            |
| Revision       | e46b3d3d41856f2764de4d709a289d572982e23d            |
| Modified       | true                                                |
| -buildmode     | exe                                                 |
| -compiler      | gc                                                  |
| CGO_CFLAGS     |                                                     |
| CGO_CPPFLAGS   |                                                     |
| CGO_CXXFLAGS   |                                                     |
| CGO_ENABLED    | 1                                                   |
| CGO_LDFLAGS    |                                                     |
| DefaultGODEBUG | cryptocustomrand=1,tlssecpmlkem=0,urlstrictcolons=0 |
| GOAMD64        | v1                                                  |
| GOARCH         | amd64                                               |
| GOOS           | linux                                               |
| vcs            | git                                                 |
| vcs.modified   | true                                                |
| vcs.revision   | e46b3d3d41856f2764de4d709a289d572982e23d            |
| vcs.time       | 2026-05-10T16:25:12Z                                |
└──────────────────────────────────────────────────────────────────────┘

# Dependencies 

┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| webkitgtk-6.0 (experimental) | [Optional] not installed. Install with: sudo apt install libwebkitgtk-6.0-dev |
| gcc                          | 12.12                                                                         |
| gtk3                         | 3.24.49-3                                                                     |
| gtk4 (experimental)          | [Optional] not installed. Install with: sudo apt install libgtk-4-dev         |
| npm                          | 9.2.0                                                                         |
| pkg-config                   | 1.8.1-4                                                                       |
| webkit2gtk                   | 2.52.3-2~deb13u1                                                              |
| docker                       | *Not installed (optional - for cross-compilation)                             |
|                                                                                                              |
└────────────────────────────────────────── * - Optional Dependency ───────────────────────────────────────────┘

# Checking for issues 

 SUCCESS  No issues found

# Diagnosis 

 SUCCESS  Your system is ready for Wails development!

Need documentation? Run: wails3 docs
 ♥   If Wails is useful to you or your company, please consider sponsoring the project: wails3 sponsor

Checklist:

  • (v2 only) I have updated website/src/pages/changelog.mdx with details of this PR (v3 changelog entries are added automatically)
  • My code follows the general coding style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • New Features
    • Bindings now support Go time values with an option to emit Date objects or strings; runtime includes DateFromTime conversion.
  • New Flags
    • Added -time-type option to choose Date vs string output for time values.
  • Documentation
    • Updated binding-generation docs/commands to include the new time-type flag.
  • Chores
    • Example app updated to demonstrate birthday handling in greetings.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a26ab3df-213f-4858-82a3-299d9cf5eb7a

📥 Commits

Reviewing files that changed from the base of the PR and between ead1c2d and f720ed9.

📒 Files selected for processing (9)
  • v3/internal/generator/testcases/time/main.go
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
✅ Files skipped from review due to trivial changes (8)
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • v3/internal/generator/testcases/time/main.go

Walkthrough

Adds configurable support to map Go time.Time to JS Date or string in bindings: TimeType flag, SpecialTypeCache, system-path/time resolution, generator validation, render/create adjustments, runtime DateFromTime, example updates, and testdata for multiple JS/TS modes.

Changes

time.Time Binding Conversion

Layer / File(s) Summary
Configuration & Flags
v3/internal/flags/bindings.go, v3/internal/generator/config/paths.go, v3/internal/generator/errors.go, v3/internal/commands/build_assets/Taskfile.tmpl.yml
Adds TimeType flag (default "string", allows "Date"), adds SystemPaths.TimePackage, standardizes related error messages, and updates build template to pass -time-type=Date.
Special Type Cache
v3/internal/generator/collect/special.go
Creates SpecialTypeCache with IsSpecialType(), IsStdTime(), and IsVoidAlias() using atomic caching for fast repeated checks.
Collector Refactoring
v3/internal/generator/collect/collector.go, v3/internal/generator/collect/imports.go, v3/internal/generator/collect/void.go
Replaces old void-type cache with SpecialTypeCache, removes Collector.IsVoidAlias, and adjusts import handling to short-circuit on special types.
System Path Resolution
v3/internal/generator/load.go
Resolves the std time package path and populates SystemPaths.TimePackage; adds ErrNoTimePackage and related resolution checks.
Options Validation
v3/internal/generator/generate.go, v3/internal/generator/generate_test.go
Adds validateOptions() enforcing allowed TimeType values and rejecting "Date" when UseInterfaces is enabled; tests set TimeType explicitly.
Type Rendering
v3/internal/generator/render/type.go
renderNamedType() now handles std time by returning configured m.TimeType rather than a generated model type name.
Default Value Rendering
v3/internal/generator/render/default.go
renderNamedDefault() emits zero-value for time.Time as ISO string or Date(...) depending on TimeType; void alias renders an empty IIFE.
Creation & Marshaling
v3/internal/generator/render/create.go
needsCreateImpl() and JSCreateWithParams() special-case std time: Date mode returns DateFromTime creator; string mode uses generic Any creator.
Runtime Helper
v3/internal/runtime/desktop/@wailsio/runtime/src/create.ts
Adds exported DateFromTime(source: any): Date converting RFC3339 time strings into JavaScript Date.
Example Binding Updates
v3/examples/binding/GreetService.go, v3/examples/binding/data/person.go, v3/examples/binding/assets/bindings/.../data/models.js, v3/examples/binding/assets/bindings/.../greetservice.js, v3/examples/binding/assets/index.html, v3/examples/binding/README.md
Adds Birthday time.Time to Person, changes GetPerson signature to accept/return birthday, updates generated JS model/binding to include birthday, updates example HTML to pass a Date, and documents -time-type=Date usage.
Test Cases & Testdata
v3/internal/generator/testcases/time/*, v3/internal/generator/testdata/output/...
Adds a time test package covering many time shapes and extensive generated testdata across JS/TS and interface/name permutations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

v3, bindings

Suggested reviewers

  • leaanthony

Poem

"🐰 I hopped through flags and time, with code neat and petite,
I taught Go's Time to wear a Date, so JS and Go can meet.
A special cache remembers names, the runtime forms the key,
Now birthdays show as Dates for all — hooray from rabbit me!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: adding support for binding Go's standard time.Time type to JavaScript Date objects.
Description check ✅ Passed The PR description follows the template with all major sections completed: it explains the change, identifies the linked issue (#4753), specifies the change type (New feature), documents testing on Linux, and includes comprehensive testing and self-review checklist confirmations.
Linked Issues check ✅ Passed The PR fully addresses issue #4753 by implementing the ability to map Go's time.Time type to JavaScript Date objects via a new -time-type=Date flag, allowing time.Time values to be properly represented as Date in generated bindings.
Out of Scope Changes check ✅ Passed All changes in the PR are directly related to the feature objective: adding time.Time-to-Date mapping support. This includes updates to example bindings, the binding generator code, special type handling, test cases, and template files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.1)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
v3/examples/binding/GreetService.go (1)

35-36: 💤 Low value

Consider timezone implications of Local() formatting.

The code formats the birthday using Local(), which converts to the server's timezone. For user-facing dates (especially birthdays), the displayed date might differ from what the user expects if the server is in a different timezone.

For production code, consider either:

  • Formatting on the client side (JavaScript has timezone-aware formatting)
  • Using UTC consistently: person.Birthday.UTC().Format("2 Jan 2006")
  • Including timezone information in the output

This is acceptable for an example, but worth noting for real-world applications.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/examples/binding/GreetService.go` around lines 35 - 36, The code uses
person.Birthday.Local() which converts the date to the server timezone; update
the formatting to avoid server-timezone surprises by using a timezone-consistent
representation — e.g., replace person.Birthday.Local().Format("2 Jan 2006") with
person.Birthday.UTC().Format("2 Jan 2006") or include explicit timezone info
(e.g., Format with time.RFC3339 or append " UTC") in the greeting construction
inside the GreetService (the code that builds greeting using person.Birthday) so
displayed birthdays are not shifted by server locale.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@v3/examples/binding/assets/index.html`:
- Around line 112-113: The hard-coded birthday uses local-time construction
which causes timezone shifts when serialized; update the call to
GreetService.GetPerson to pass a UTC-based Date (e.g., construct with Date.UTC)
so the calendar date is preserved across timezones—replace new Date(1947, 0, 8)
with a UTC construction (new Date(Date.UTC(1947, 0, 8))) when invoking
GreetService.GetPerson.

In `@v3/internal/generator/errors.go`:
- Line 16: The error variable ErrNoContextPackage contains an unnecessary
trailing space in its message; update the string in ErrNoContextPackage to
remove the trailing space so the message ends with "...('context')" and then
scan the same file for the other error variables referenced in the review (the
error message lines at 21, 26, 31, and 35) and remove any trailing spaces from
their literal strings as well to keep messages consistent.

In
`@v3/internal/generator/testdata/output/lang`=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js:
- Around line 9-11: The typedefs TimeAliasStruct and TimeStruct are being
emitted as "any" instead of the configured scalar (string) for types derived
from time.Time; update the emitter logic that generates typedefs for
time-derived types so that when a field or typedef maps to time.Time (affecting
TimeAliasStruct, TimeStruct and usages like TimeFieldStruct) it emits the
configured scalar ("string" in this mode) rather than "any". Locate the code
path that decides the JS typedef/type name for time-based types (the
function/method generating typedefs for alias/struct types and the type resolver
used by the field emitter), add a branch to detect time.Time (or the generator's
time scalar mapping) and return the scalar representation, and ensure both the
typedef declarations and any referenced field types (e.g., TimeFieldStruct) use
that scalar.

In
`@v3/internal/generator/testdata/output/lang`=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts:
- Line 109: The map converter for $$createType3 is using
$Create.Map($Create.Any, $Create.Any) but for field M (a map whose values are
time.Time in Date mode) the value converter must be $Create.DateFromTime; update
the binding generator so that when generating the converter for maps with
time.Time values it emits $Create.Map($Create.Any, $Create.DateFromTime) (ensure
the generation logic that produces $$createType3 detects the time.Time value
type in Date mode and substitutes DateFromTime accordingly).
- Line 22: The generated TypeScript field "M" is incorrectly typed as `{ [_ in
string]?: string }` but in Date mode it should use Date for values (i.e. `{ [_
in string]?: Date }`); update the map-type rendering logic in the generator (the
code that emits the TS type for map fields, referenced by the field name "M" and
the map-type code path) so that when Date mode is enabled and the Go map value
is time.Time it emits Date as the value type instead of string.

In
`@v3/internal/generator/testdata/output/lang`=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts:
- Around line 78-79: The runtime converters $$createType0 and $$createType1 are
using $Create.Any but must match the declared field types S (string[]) and M
({[_ in string]?: string}); update $$createType0 to use
$Create.Array($Create.String) so array elements are strings, and update
$$createType1 to use $Create.Map($Create.String, $Create.String) so map
keys/values are typed as strings; make the changes where $$createType0 and
$$createType1 are defined to restore type-safe runtime conversion for fields S
and M.

---

Nitpick comments:
In `@v3/examples/binding/GreetService.go`:
- Around line 35-36: The code uses person.Birthday.Local() which converts the
date to the server timezone; update the formatting to avoid server-timezone
surprises by using a timezone-consistent representation — e.g., replace
person.Birthday.Local().Format("2 Jan 2006") with
person.Birthday.UTC().Format("2 Jan 2006") or include explicit timezone info
(e.g., Format with time.RFC3339 or append " UTC") in the greeting construction
inside the GreetService (the code that builds greeting using person.Birthday) so
displayed birthdays are not shifted by server locale.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b596ea1-c17a-4353-8460-9951d50e1ac4

📥 Commits

Reviewing files that changed from the base of the PR and between c97756e and ead1c2d.

📒 Files selected for processing (47)
  • v3/examples/binding/GreetService.go
  • v3/examples/binding/README.md
  • v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/data/models.js
  • v3/examples/binding/assets/bindings/github.com/wailsapp/wails/v3/examples/binding/greetservice.js
  • v3/examples/binding/assets/index.html
  • v3/examples/binding/data/person.go
  • v3/internal/commands/build_assets/Taskfile.tmpl.yml
  • v3/internal/flags/bindings.go
  • v3/internal/generator/collect/collector.go
  • v3/internal/generator/collect/imports.go
  • v3/internal/generator/collect/special.go
  • v3/internal/generator/collect/void.go
  • v3/internal/generator/config/paths.go
  • v3/internal/generator/errors.go
  • v3/internal/generator/generate.go
  • v3/internal/generator/generate_test.go
  • v3/internal/generator/load.go
  • v3/internal/generator/render/create.go
  • v3/internal/generator/render/default.go
  • v3/internal/generator/render/type.go
  • v3/internal/generator/testcases/time/bound_types.json
  • v3/internal/generator/testcases/time/main.go
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/index.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/service.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/index.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/service.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/index.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/service.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/index.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
  • v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/service.js
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/index.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/service.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/index.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=false/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/service.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/index.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=false/github.com/wailsapp/wails/v3/internal/generator/testcases/time/service.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/index.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.ts
  • v3/internal/generator/testdata/output/lang=TS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/service.ts
  • v3/internal/runtime/desktop/@wailsio/runtime/src/create.ts
💤 Files with no reviewable changes (1)
  • v3/internal/generator/collect/void.go

Comment thread v3/examples/binding/assets/index.html
Comment thread v3/internal/generator/errors.go
Comment on lines +9 to +11
/**
* @typedef {any} TimeAliasStruct
*/
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

time.Time-derived typedefs are still emitted as any

Line 10 and Line 29 keep TimeAliasStruct/TimeStruct as any, which undermines the new time.Time mapping behavior and leaks weak typing into downstream APIs (e.g., TimeFieldStruct). These should resolve to the configured scalar (string in this interface-mode output).

Proposed expected output shape
 /**
- * `@typedef` {any} TimeAliasStruct
+ * `@typedef` {string} TimeAliasStruct
  */
 
 /**
- * `@typedef` {any} TimeStruct
+ * `@typedef` {string} TimeStruct
  */

Also applies to: 28-30

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@v3/internal/generator/testdata/output/lang`=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js
around lines 9 - 11, The typedefs TimeAliasStruct and TimeStruct are being
emitted as "any" instead of the configured scalar (string) for types derived
from time.Time; update the emitter logic that generates typedefs for
time-derived types so that when a field or typedef maps to time.Time (affecting
TimeAliasStruct, TimeStruct and usages like TimeFieldStruct) it emits the
configured scalar ("string" in this mode) rather than "any". Locate the code
path that decides the JS typedef/type name for time-based types (the
function/method generating typedefs for alias/struct types and the type resolver
used by the field emitter), add a branch to detect time.Time (or the generator's
time scalar mapping) and return the scalar representation, and ensure both the
typedef declarations and any referenced field types (e.g., TimeFieldStruct) use
that scalar.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a very hard problem. Will add a detailed comment about the issue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@fbbdev, thanks for flagging this — that context will be very helpful. I can see why it's non-trivial: TimeStruct (struct { time.Time }) and TimeAliasStruct (struct embedding a time.Time alias) are struct wrappers around time.Time, not aliases of it. Their JSON marshaling behavior may differ from bare time.Time depending on whether additional fields exist or whether the promoted methods are sufficient, and in interface mode there's no class/constructor to attach conversion logic to. The any fallback is likely the safe, conservative choice until the right approach is worked out.

Happy to look at this more carefully once you've shared your analysis. Feel free to take your time.


✏️ Learnings added
Learnt from: fbbdev
Repo: wailsapp/wails PR: 5398
File: v3/internal/generator/testdata/output/lang=JS/UseInterfaces=true/UseNames=true/github.com/wailsapp/wails/v3/internal/generator/testcases/time/models.js:9-11
Timestamp: 2026-05-10T18:25:13.625Z
Learning: In the Wails v3 binding generator (v3/internal/generator), struct types that embed `time.Time` (e.g. `TimeStruct struct { time.Time }`) or embed a `time.Time` alias (e.g. `TimeAliasStruct`) are intentionally emitted as `any` in interface mode. Collapsing them to the configured time scalar (`string` or `Date`) is a hard, unsolved problem because determining whether a wrapper struct is semantically equivalent to `time.Time` requires deep structural analysis, and their JSON marshaling behavior may differ. The `any` fallback is a deliberate conservative choice.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@fbbdev
Copy link
Copy Markdown
Contributor Author

fbbdev commented May 10, 2026

Re: @coderabbitai 's observation here.
Structs that embed time.Time currently render as any because they inherit its MarshalJSON method:

type EmbedsTime struct {
    time.Time
}

maps to:

type EmbedsTime = any:

Simple type-level matching is not enough to work around this issue; one should instead retrieve the method set of the type, check whether the MarshalJSON method is present and identical to time.Time's method object, and somehow force the binding generator to treat this type as an alias.

This is doable, but at the cost of a significant increase in complexity, and IMO out of scope for this PR. If we decide to pay this price, we might as well go all the way and enable people to specify their own deserialization logic for custom marshaler types (which would be a nice addition).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

Tip

For best results, initiate chat on the files or code changes.

@fbbdev Thanks for the detailed explanation! That makes perfect sense — since the generator detects MarshalJSON to classify a type as a custom marshaler (and thus emits any), a struct that embeds time.Time inherits that method and gets the same treatment, which is indistinguishable at the type-level from an intentional custom marshaler.

Your proposed approach of comparing the MarshalJSON method object identity against time.Time's own method is the right way to handle this, but as you noted, it significantly increases complexity. Generalizing it to support user-defined deserialization hints for custom marshaler types would be a much more powerful and principled solution, and seems worth doing as a dedicated follow-up.

Would you like me to open a GitHub issue to track this limitation and the idea of supporting custom deserialization logic for custom marshaler types?


✏️ Learnings added
Learnt from: fbbdev
URL: https://github.com/wailsapp/wails/pull/5398

Timestamp: 2026-05-10T18:47:29.308Z
Learning: In the Wails v3 binding generator (v3/internal/generator), structs that embed `time.Time` (e.g., `type EmbedsTime struct { time.Time }`) currently render as `any` in generated bindings because they inherit `time.Time`'s `MarshalJSON` method, which causes the generator to treat them as custom marshalers. Simple type-level matching cannot distinguish this case from an intentional custom marshaler. The correct fix would require inspecting the type's method set and comparing the `MarshalJSON` method object identity to that of `time.Time`. This is considered out of scope for PR `#5398` and is a candidate for a follow-up that could generalize custom deserialization logic for custom marshaler types.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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.

[v3] Method binding is converted from the time.Time type to its own Time type

1 participant