Skip to content

Commit 8632247

Browse files
committed
Fix props aliasing error 🐛
1 parent 0fde62f commit 8632247

9 files changed

Lines changed: 97 additions & 73 deletions

File tree

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"isRoot": true,
44
"tools": {
55
"fable": {
6-
"version": "5.0.0-alpha.14",
6+
"version": "5.0.0-alpha.15",
77
"commands": [
88
"fable"
99
],

playground/src/App.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ open Shared
99
[<ReactComponent>]
1010
let App() =
1111
Html.div [
12-
Components.RecordTypeContainer()
12+
Components.Main()
1313
]

playground/src/Components.fs

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,20 @@ open Fable.Core
55
open Fable.Core.JsInterop
66
open Shared
77

8-
[<AttachMembers>]
9-
type private RecordTypeInput =
10-
{
11-
Name: string;
12-
Job: string;
13-
}
14-
15-
member this.Greet() =
16-
$"Hello, my name is {this.Name} and I work as a {this.Job}."
17-
18-
let private RecordTypeCtx =
19-
let init = Set.empty<RecordTypeInput>
20-
let setter = fun (_: Set<RecordTypeInput>) -> ()
21-
React.createContext({|state = init; setState = setter|})
228

239
[<ReactComponent>]
24-
let private SingleRecordTypeInput(recordInput: RecordTypeInput) =
25-
let ctx = React.useContext RecordTypeCtx
26-
Html.div [
27-
Html.div [
28-
prop.testId "single-greet"
29-
prop.text (recordInput.Greet())
30-
]
31-
Html.div [
32-
prop.testId "single-exists"
33-
prop.text (ctx.state |> Set.contains recordInput |> string)
34-
]
10+
let LogBtn (nb:int, children: ReactElement, props: seq<IReactProperty>) =
11+
Html.button [
12+
for prop in props do prop // same as yield! props
13+
prop.onClick (fun _ -> printfn $"You clicked me {nb}")
14+
prop.children children
3515
]
3616

17+
3718
[<ReactComponent>]
38-
let RecordTypeContainer() =
39-
let record = React.useMemo (fun () -> { Name = "Alice"; Job = "Engineer" })
40-
let records, setRecords = React.useState(Set [record])
19+
let Main() =
4120
Html.div [
42-
RecordTypeCtx.Provider({|state = records; setState = setRecords|}, [
43-
for record in records do
44-
SingleRecordTypeInput(record)
45-
])
21+
Html.h1 "Welcome to the Fable Playground!"
22+
Html.p "This is a simple playground for experimenting with Fable and Feliz."
23+
LogBtn(12, Html.text "Click me", [ prop.style [style.backgroundColor.green; style.color.white]])
4624
]

src/Feliz.CompilerPlugins/AstUtils.fs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ let rec flattenList (head: Fable.Expr) (tail: Fable.Expr) =
5858
match tail with
5959
| Fable.Expr.Value (value, range) ->
6060
match value with
61-
| Fable.ValueKind.NewList(Some(nextHead, nextTail), listType) ->
61+
| Fable.ValueKind.NewList(Some(nextHead, nextTail), _) ->
6262
yield! flattenList nextHead nextTail
63-
| Fable.ValueKind.NewList(None, listType) ->
63+
| Fable.ValueKind.NewList(None, _) ->
6464
yield! [ ]
6565
| _ ->
6666
yield! [ Fable.Expr.Value (value, range) ]
@@ -77,7 +77,7 @@ let makeImport (selector: string) (path: string) =
7777
let isRecord (compiler: PluginHelper) (fableType: Fable.Type) =
7878
match fableType with
7979
| Fable.Type.AnonymousRecordType _ -> true
80-
| Fable.Type.DeclaredType (entity, genericArgs) -> compiler.GetEntity(entity).IsFSharpRecord
80+
| Fable.Type.DeclaredType (entity, _) -> compiler.GetEntity(entity).IsFSharpRecord
8181
| _ -> false
8282

8383
let isAnonymRecord (compiler: PluginHelper) (fableType: Fable.Type) =
@@ -89,7 +89,7 @@ let isPropertyList (compiler: PluginHelper) (fableType: Fable.Type) =
8989
match fableType with
9090
| Fable.Type.List(genericArg) ->
9191
match genericArg with
92-
| Fable.Type.DeclaredType (entity, genericArgs) -> entity.FullName.EndsWith "IReactProperty"
92+
| Fable.Type.DeclaredType (entity, _) -> entity.FullName.EndsWith "IReactProperty"
9393
| _ -> false
9494
| _ -> false
9595

@@ -103,16 +103,16 @@ let isAnonymousRecord (fableType: Fable.Type) =
103103

104104
let isReactElement (fableType: Fable.Type) =
105105
match fableType with
106-
| Fable.Type.DeclaredType (entity, genericArgs) -> entity.FullName.EndsWith "ReactElement"
106+
| Fable.Type.DeclaredType (entity, _) -> entity.FullName.EndsWith "ReactElement"
107107
| _ -> false
108108

109109
let recordHasField name (compiler: PluginHelper) (fableType: Fable.Type) =
110110
match fableType with
111-
| Fable.Type.AnonymousRecordType (fieldNames, genericArgs, _isStruct) ->
111+
| Fable.Type.AnonymousRecordType (fieldNames, _, _) ->
112112
fieldNames
113113
|> Array.exists (fun field -> field = name)
114114

115-
| Fable.Type.DeclaredType (entity, genericArgs) ->
115+
| Fable.Type.DeclaredType (entity, _) ->
116116
compiler.GetEntity(entity).FSharpFields
117117
|> List.exists (fun field -> field.Name = name)
118118

src/Feliz.CompilerPlugins/CHANGELOG.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
**Types of changes**
99

10-
- `Added` for new features.
11-
- 🔄 `Changed` for changes in existing functionality.
12-
- 🗑️ `Deprecated` for soon-to-be removed features.
13-
- 🔥 `Removed` for now removed features.
14-
- 🐛 `Fixed` for any bug fixes.
15-
- 🔒 `Security` in case of vulnerabilities.
10+
-`Added` for new features.
11+
- 🔄 `Changed` for changes in existing functionality.
12+
- 🗑️ `Deprecated` for soon-to-be removed features.
13+
- 🔥 `Removed` for now removed features.
14+
- 🐛 `Fixed` for any bug fixes.
15+
- 🔒 `Security` in case of vulnerabilities.
1616

1717
## [Unreleased]
1818

19+
## 3.0.0-rc.5 - 2025-11-21
20+
21+
### 🐛 Fixed
22+
23+
- Fix `props` aliasing issue. when passing a arg with the name `props` to a `[<ReactComponent>]` it threw with duplication error (by @Freymaurer)
24+
1925
## 3.0.0-rc.4 - 2025-11-18
2026

2127
### 🗑️ Deprecated
@@ -24,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2430

2531
### 🐛 Fixed
2632

27-
- Fix equality issue for single input record types for ReactComponent #603 (by @Freymaurer)
33+
- Fix equality issue for single input record types for ReactComponent #603 (by @Freymaurer)
2834

2935
## 3.0.0-rc.3 - 2025-11-03
3036

@@ -57,4 +63,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5763
### ✨ Added
5864

5965
- Last release before start of Changelog
60-

src/Feliz.CompilerPlugins/ReactComponent.fs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ type ReactComponentAttribute(?exportDefault: bool, ?import: string, ?from: strin
319319
let classEntity = compiler.GetEntity(classDecl.Entity)
320320

321321
match decl.Args[0].Type with
322-
| Type.DeclaredType(entity, _genericArgs) ->
322+
| Type.DeclaredType(entity, _) ->
323323
let declaredEntity = compiler.GetEntity(entity)
324324

325325
if
@@ -385,26 +385,27 @@ type ReactComponentAttribute(?exportDefault: bool, ?import: string, ?from: strin
385385
sprintf
386386
"Function component '%s' is using a single tupled argument as input parameter. This can create issues with correct transpilation. "
387387
decl.Name
388-
"To fix this issue, consider spreading the arguments or using a (anonymous) record type."
388+
"To fix this issue, consider spreading the arguments or using a anonymous record type."
389389
]
390390
compiler.LogWarning(warningMsg, ?range = decl.Body.Range)
391391

392392
let propsArg =
393+
let fieldNames, genericArgs =
394+
decl.Args
395+
|> List.mapi (fun i arg ->
396+
if isPredictedTuple then
397+
"tuple_" + string i, arg.Type
398+
else
399+
arg.DisplayName, arg.Type
400+
)
401+
|> List.unzip
393402
let type_ =
394-
let fieldNames, genericArgs =
395-
decl.Args
396-
|> List.mapi (fun i arg ->
397-
if isPredictedTuple then
398-
"tuple_" + string i, arg.Type
399-
else
400-
arg.DisplayName, arg.Type
401-
)
402-
|> List.unzip
403-
404403
Fable.Type.AnonymousRecordType(Array.ofList fieldNames, genericArgs, false)
405-
// let name = sprintf "%sInputProps" (AstUtils.camelCase decl.Name)
406-
let name = "props"
407-
AstUtils.makeIdent type_ name
404+
let mutable propsName = "props"
405+
while fieldNames |> List.contains propsName do
406+
propsName <- propsName + "_"
407+
408+
AstUtils.makeIdent type_ propsName
408409

409410
let propBindings =
410411
([], decl.Args)

src/Feliz/CHANGELOG.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
**Types of changes**
99

10-
- `Added` for new features.
11-
- 🔄 `Changed` for changes in existing functionality.
12-
- 🗑️ `Deprecated` for soon-to-be removed features.
13-
- 🔥 `Removed` for now removed features.
14-
- 🐛 `Fixed` for any bug fixes.
15-
- 🔒 `Security` in case of vulnerabilities.
10+
-`Added` for new features.
11+
- 🔄 `Changed` for changes in existing functionality.
12+
- 🗑️ `Deprecated` for soon-to-be removed features.
13+
- 🔥 `Removed` for now removed features.
14+
- 🐛 `Fixed` for any bug fixes.
15+
- 🔒 `Security` in case of vulnerabilities.
1616

1717
## [Unreleased]
1818

19+
## 3.0.0-rc.11 - 2025-11-21
20+
21+
### 🐛 Fixed
22+
23+
- Fix `props` aliasing issue. when passing a arg with the name `props` to a `[<ReactComponent>]` it threw with duplication error (by @Freymaurer)
24+
1925
## 3.0.0-rc.10 - 2025-11-18
2026

2127
### 🗑️ Deprecated
@@ -24,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2430

2531
### 🐛 Fixed
2632

27-
- Fix equality issue for single input record types for ReactComponent #603 (by @Freymaurer)
33+
- Fix equality issue for single input record types for ReactComponent #603 (by @Freymaurer)
2834

2935
## 3.0.0-rc.9 - 2025-11-03
3036

@@ -60,7 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6066

6167
### ✨ Added
6268

63-
- `style.fontsize._` module with `smaller`, `larger`, ... styles. By @Linschlager #613
69+
- `style.fontsize._` module with `smaller`, `larger`, ... styles. By @Linschlager #613
6470

6571
### 🔄 Changed
6672

@@ -95,4 +101,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
95101
### ✨ Added
96102

97103
- Last release before start of Changelog
98-

tests/Feliz/Basic.fs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,29 @@ type Components =
172172
]
173173
]
174174
175+
[<ReactComponent>]
176+
static member private PropsAliasingInner(props: IReactProperty list, test: string) =
177+
Html.div [
178+
for prop in props do
179+
prop
180+
prop.children [
181+
Html.h1 "Welcome to the Fable Playground!"
182+
Html.p "This is a simple playground for experimenting with Fable and Feliz."
183+
]
184+
]
185+
186+
[<ReactComponent>]
187+
static member PropsAliasing(props: IReactProperty list) =
188+
Html.div [
189+
for prop in props do
190+
prop
191+
prop.children [
192+
Components.PropsAliasingInner(props, "test")
193+
Html.h1 "Welcome to the Fable Playground!"
194+
Html.p "This is a simple playground for experimenting with Fable and Feliz."
195+
]
196+
]
197+
175198
module RecordTypeInputTesting =
176199
177200
[<AttachMembers>]

tests/Feliz/Basic.test.fs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,15 @@ describe "Record Type Input Tests #606, #603" <| fun _ ->
244244

245245
expect(RTL.screen.getByTestId "single-greet").toHaveTextContent("Hello, my name is Alice and I work as a Developer.")
246246
expect(RTL.screen.getByTestId "single-exists").toHaveTextContent("true")
247+
248+
describe "Props Aliasing Tests #687" <| fun _ ->
249+
250+
test "Component with arg called 'props' must alias main input to 'props_'" <| fun _ ->
251+
RTL.render(
252+
Components.PropsAliasing([prop.style [ style.padding 20; style.backgroundColor.blanchedAlmond ]; prop.testId "main-component"])
253+
) |> ignore
254+
255+
let components = RTL.screen.getAllByTestId "main-component"
256+
expect(components).toHaveLength(2)
257+
for c in components do
258+
expect(c).toBeInTheDocument()

0 commit comments

Comments
 (0)