Skip to content

fix: use old type name in Union migration type signatures for renamed types#81

Open
CharlonTank wants to merge 2 commits intolamdera:lamdera-nextfrom
CharlonTank:fix/migration-union-type-rename
Open

fix: use old type name in Union migration type signatures for renamed types#81
CharlonTank wants to merge 2 commits intolamdera:lamdera-nextfrom
CharlonTank:fix/migration-union-type-rename

Conversation

@CharlonTank
Copy link
Contributor

Summary

  • Fixes migration generator producing incorrect type signatures when a Union type is renamed between versions
  • The old type name is now correctly threaded through migrateUnionDefinitionmigrateUnionDefinition_, mirroring how migrateAliasDefinition already handles alias renames

Problem

When a Union type is renamed between versions (e.g. MoralType in V51 → CompanyType in V52), the migration generator was using the new type name for the old version in the generated type signature:

migrate_LegalEntity_CompanyType : Evergreen.V51.LegalEntity.CompanyType -> Evergreen.V52.LegalEntity.CompanyType

But CompanyType doesn't exist in V51 — it was called MoralType. This causes a compilation error in the generated migration file.

Fix

The correct signature should be:

migrate_LegalEntity_CompanyType : Evergreen.V51.LegalEntity.MoralType -> Evergreen.V52.LegalEntity.CompanyType

Changes in MigrationGenerator.hs:

  1. coreTypeMigration: Pass typeNameOld (from pattern match) instead of typeName (new name) to migrateUnionDefinition
  2. migrateUnionDefinition: Renamed parameter from typeNameNew to typeNameOld, derive typeNameNew from the identifier's tipe component, and thread both to migrateUnionDefinition_
  3. migrateUnionDefinition_: Added typeNameOld parameter; use it in the type signature for the old version's type reference (line 279)
  4. migrateTypeDef: Pass typeNameOld instead of typeNameNew to migrateUnionDefinition

Reference

The Alias case (migrateAliasDefinition) already correctly passes and uses both typeNameOld and typeNameNew — this fix brings the Union case in line with that pattern.

Test plan

  • Rename a Union type between two Evergreen versions (e.g. type MoralType = ...type CompanyType = ...)
  • Run lamdera check and verify the generated migration file uses the old type name for the old version in the type signature
  • Verify the generated migration file compiles without errors

… types

When a Union type is renamed between versions (e.g. MoralType in V51 →
CompanyType in V52), the migration generator was using the NEW type name
for the OLD version in the generated type signature:

  migrate_LegalEntity_CompanyType : Evergreen.V51.LegalEntity.CompanyType -> ...

But CompanyType doesn't exist in V51 - it was called MoralType. The correct
signature should be:

  migrate_LegalEntity_CompanyType : Evergreen.V51.LegalEntity.MoralType -> ...

The fix threads typeNameOld through migrateUnionDefinition →
migrateUnionDefinition_, mirroring how migrateAliasDefinition already
correctly handles this case for type alias renames.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@CharlonTank
Copy link
Contributor Author

I built the fixed compiler locally and tested it against my project which has a Union type rename (MoralTypeCompanyType between V51 and V52).

Before the fix:

migrate_LegalEntity_CompanyType : Evergreen.V51.LegalEntity.CompanyType -> Evergreen.V52.LegalEntity.CompanyType

→ Compilation error: Evergreen.V51.LegalEntity.CompanyType doesn't exist

After the fix:

migrate_LegalEntity_CompanyType : Evergreen.V51.LegalEntity.MoralType -> Evergreen.V52.LegalEntity.CompanyType

→ Correct type signature, compiles fine

When a migration for a container type parameter is a pipeline
(e.g. nested SeqDict producing `SeqDict.toList |> List.map ... |> SeqDict.fromList`),
it must be wrapped in a lambda before being passed as a function argument
to Tuple.mapBoth, Tuple.mapFirst, List.map, etc.

Without this fix, the generated code produces:
  Tuple.mapBoth (migrate_Email) (SeqDict.toList |> ...)
which is syntactically invalid (pipeline used as a value argument).

With this fix:
  Tuple.mapBoth (migrate_Email) (\v__ -> v__ |> SeqDict.toList |> ...)
@CharlonTank
Copy link
Contributor Author

Added second fix: nested Dict/SeqDict pipeline wrapping

When a container type has a nested Dict/SeqDict parameter, the recursive migration produces a pipeline (SeqDict.toList |> ... |> SeqDict.fromList) that gets inserted directly as a function argument to Tuple.mapBoth, which is syntactically invalid.

Before: Tuple.mapBoth (migrate_Email) (SeqDict.toList |> List.map (...) |> SeqDict.fromList)
After: Tuple.mapBoth (migrate_Email) (\v__ -> v__ |> SeqDict.toList |> List.map (...) |> SeqDict.fromList)

Fix: In applyMigration, detect pipeline migrations (containing |>) and wrap them in a lambda — following the same pattern already used for anonymous records.

Both fixes tested locally — built the compiler and verified the generated migration compiles correctly.

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.

1 participant