Fable 5.0.0-alpha.4 JSX.jsx problem with not recognizing interpolated string
Description
The code from in this repository: 9dee1b64a0f98b41e42242dfa4fa5e60d4961f5a
works with the fable 4 releases but not with the latest 5.0.0-alpha.4 release
client: ./App.fs(270,12): (279,19) error FABLE: Expecting a string literal or interpolation without formatting
Repro code
- See link to repository.
- Clone the repository
- Install the latest alpha version fable tool
- Start with dotnet run
- Compare with the current 4.x tool
Expected and actual results
The JSX.jsx templates to be recognized as interpolated strings
Related information
.NET SDK: Version: 9.0.101 Commit: eedb237549 Workload version: 9.0.100-manifests.3068a692 MSBuild version: 17.12.12+1cce77968
Runtime Environment: OS Name: Mac OS X OS Version: 12.7 OS Platform: Darwin RID: osx-x64 Base Path: /usr/local/share/dotnet/sdk/9.0.101/
.NET workloads installed: There are no installed workloads to display. Configured to use loose manifests when installing new manifests.
Host: Version: 9.0.0 Architecture: x64 Commit: 9d5a6a9aa4
.NET SDKs installed: 6.0.302 [/usr/local/share/dotnet/sdk] 6.0.403 [/usr/local/share/dotnet/sdk] 7.0.100 [/usr/local/share/dotnet/sdk] 8.0.100 [/usr/local/share/dotnet/sdk] 9.0.100 [/usr/local/share/dotnet/sdk] 9.0.101 [/usr/local/share/dotnet/sdk]
Hello @halcwb,
Can you please try to make a smaller reproduction code?
Hello @halcwb,
Can you please try to make a smaller reproduction code?
Hope this is small enough: https://github.com/halcwb/Fable5Issue.git. I have gutted all code except just this part that seems to be the problem:
module App
open Fable.Core
open Browser
open Fable.React
let inline private toReact (el: JSX.Element) : ReactElement = unbox el
[<JSX.Component>]
let View () =
let display showProgress (s: string) =
if showProgress then
JSX.jsx
$"""
import LinearProgress from '@mui/material/LinearProgress';
<LinearProgress>{s}</LinearProgress>
"""
else
JSX.jsx
$"""
import Typography from '@mui/material/Typography';
<React.Fragment>
<Typography variant="h6" gutterBottom >
{s}
</Typography>
</React.Fragment>
"""
let content = display true "Testing Fable 5"
JSX.jsx
$"""
import React from 'react';
<React.StrictMode>
<React.Fragment>
{content}
</React.Fragment>
</React.StrictMode>
"""
let app = ReactDomClient.createRoot (document.getElementById "app")
app.render (View() |> toReact)
Hope this helps.
@halcwb Looks related to https://github.com/dotnet/fsharp/pull/16556 which converts some interpolated strings into string concatenation (for performance reasons), which then interferes with the Fable JSX.
Before (Fable 4.24, F# 8.0):
`
import Typography from '@mui/material/Typography';
<React.Fragment>
<Typography variant="h6" gutterBottom >
${s}
</Typography>
</React.Fragment>
`
After (Fable 5.0.0-x, F# 9.0)
concat("\n import LinearProgress from \'@mui/material/LinearProgress\';\n <LinearProgress>", s, ..."</LinearProgress>\n ");
I'm not sure if this F# 9.0 behavior can be suppressed with some directive, but as a quick workaround adding a few dummy arguments helps avoid it, e.g.: instead of
JSX.jsx
$"""
{s}
"""
use something like this:
JSX.jsx
$"""{""}{""}
{s}
"""
Not saying this is the best workaround ever, hopefully there is a better way to turn that feature off where needed (LanguageFeature.LowerInterpolatedStringToConcat).
@ncave Thanks for your quick reaction! Is there a way to inspect the output of the string interpolation as you show in your examples?
Is there a way to inspect the output of the string interpolation as you show in your examples?
I am not sure what you mean but that, but if you are speaking about the generated JavaScript, you can look at the generated files on the disk.
Is there a way to inspect the output of the string interpolation as you show in your examples?
I am not sure what you mean but that, but if you are speaking about the generated JavaScript, you can look at the generated files on the disk.
Thanks. but the compiler crashes, there is no output other than this:
import React from "react";
import * as client from "react-dom/client";
export function View() {
const display = (showProgress, s) => {
if (showProgress) {
return null;
}
else {
return null;
}
};
const content = display(true, "Testing Fable 5");
return <React.StrictMode>
<React.Fragment>
{content}
</React.Fragment>
</React.StrictMode>
;
}
export const app = client.createRoot(document.getElementById("app"));
app.render(<View></View>);
//# sourceMappingURL=App.jsx.map
So you just have a `null`` instead of the original jsx string
Thanks. but the compiler crashes, there is no output other than this:
Ah sorry, didn't know about that.
Then, I think the only way to explore the output/generation is by debugging Fable itself via this repository.
This can be done by running ./build.sh quicktest javascript which use src/quicktest/QuickTest.fs as the source. If you use VSCode, you can also run ./build.sh fable-library --javascript once (to get some file generated) and then debug it using VSCode debugger.
From what I understand from @ncave, we probably would like to be able to make this check return false for JSX cases. But I don't think this is possible out of the box.
https://github.com/dotnet/fsharp/blob/749853e179ad3d10a2c10b95fc2c0631422f3037/src/Compiler/Checking/Expressions/CheckExpressions.fs#L7565-L7568
Or perhaps, we can look at the generated code of concat and find the information needed in order to reverse the transformation for JSX cases.
@halcwb
Is there a way to inspect the output of the string interpolation?
Yes, if you make a function that returns just the interpolated string (without JSX.jsx), you can see it (after it's transpiled by Fable). Perhaps in a different file.
@halcwb Looks related to dotnet/fsharp#16556 which converts some interpolated strings into string concatenation (for performance reasons), which then interferes with the Fable JSX.
Before (Fable 4.24, F# 8.0):
` import Typography from '@mui/material/Typography'; <React.Fragment> <Typography variant="h6" gutterBottom > ${s} </Typography> </React.Fragment> `After (Fable 5.0.0-x, F# 9.0)
concat("\n import LinearProgress from \'@mui/material/LinearProgress\';\n <LinearProgress>", s, ..."</LinearProgress>\n ");I'm not sure if this F# 9.0 behavior can be suppressed with some directive, but as a quick workaround adding a few dummy arguments helps avoid it, e.g.: instead of
JSX.jsx $""" {s} """use something like this:
JSX.jsx $"""{""}{""} {s} """Not saying this is the best workaround ever, hopefully there is a better way to turn that feature off where needed (
LanguageFeature.LowerInterpolatedStringToConcat).
Your suggestion kind of works, but still results in incorrect javascript (see display1). With some experimenting I found that these solutions seems to work:
// doses not work
let display1 showProgress (text: string) =
if showProgress then
JSX.jsx
$"""{""}{""}
import LinearProgress from '@mui/material/LinearProgress';
<LinearProgress>{text}</LinearProgress>
"""
else
JSX.jsx
$"""{""}{""}
import Typography from '@mui/material/Typography';
<React.Fragment>
<Typography variant="h6" gutterBottom >
{text}
</Typography>
</React.Fragment>
"""
// shows corectly
let display2 showProgress (text: string) =
if showProgress then
printfn
$"""
import LinearProgress from '@mui/material/LinearProgress';
<LinearProgress>{text}</LinearProgress>
"""
else
printfn
$"""
import Typography from '@mui/material/Typography';
<React.Fragment>
<Typography variant="h6" gutterBottom >
{text}
</Typography>
</React.Fragment>
"""
// works
let display3 showProgress (text: obj) =
if showProgress then
JSX.jsx
$"""
import LinearProgress from '@mui/material/LinearProgress';
<LinearProgress>{text}</LinearProgress>
"""
else
JSX.jsx
$"""
import Typography from '@mui/material/Typography';
<React.Fragment>
<Typography variant="h6" gutterBottom >
{text}
</Typography>
</React.Fragment>
"""
// works
let display4 showProgress (text: string) =
if showProgress then
JSX.jsx
$"""
import LinearProgress from '@mui/material/LinearProgress';
<React.Fragment>
<LinearProgress>{text :> obj}</LinearProgress>
</React.Fragment>
"""
else
JSX.jsx
$"""
import Typography from '@mui/material/Typography';
<React.Fragment>
<Typography variant="h6" gutterBottom >
{text :> obj}
</Typography>
</React.Fragment>
"""
Transpiles to
const display1 = (showProgress, text) => {
if (showProgress) {
return {""}{""}
import LinearProgress from '@mui/material/LinearProgress';
<LinearProgress>{text}</LinearProgress>
;
}
else {
return {""}{""}
import Typography from '@mui/material/Typography';
<React.Fragment>
<Typography variant="h6" gutterBottom >
{text}
</Typography>
</React.Fragment>
;
}
};
const display2 = (showProgress_1, text_1) => {
if (showProgress_1) {
toConsole(`
import LinearProgress from '@mui/material/LinearProgress';
<LinearProgress>${text_1}</LinearProgress>
`);
}
else {
toConsole(`
import Typography from '@mui/material/Typography';
<React.Fragment>
<Typography variant="h6" gutterBottom >
${text_1}
</Typography>
</React.Fragment>
`);
}
};
const display3 = (showProgress_2, text_2) => {
if (showProgress_2) {
return <LinearProgress>{text_2}</LinearProgress>
;
}
else {
return <React.Fragment>
<Typography variant="h6" gutterBottom >
{text_2}
</Typography>
</React.Fragment>
;
}
};
const display4 = (showProgress_3, text_3) => {
if (showProgress_3) {
return <React.Fragment>
<LinearProgress>{text_3}</LinearProgress>
</React.Fragment>
;
}
else {
return <React.Fragment>
<Typography variant="h6" gutterBottom >
{text_3}
</Typography>
</React.Fragment>
;
}
};
const content = display3(true, "Testing Fable 5");
return <React.StrictMode>
<React.Fragment>
{content}
</React.Fragment>
</React.StrictMode>
;
This is because the concat optimisation only applies when the interpolation is with a string.
So, by just casting the interpolation arguments to objects you can avoid this issue.
Does this issue still needs the label "needs reproduction"?