From 90566c77f539fb3a05474456488e4c8cc0bad5af Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Thu, 7 Sep 2023 08:53:56 -0400 Subject: [PATCH 1/3] Apply automatic formatting to BuildMenu --- ui/frontend/.eslintrc.js | 1 + ui/frontend/.prettierignore | 1 + ui/frontend/BuildMenu.tsx | 40 +++++++++++++++++-------------------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/ui/frontend/.eslintrc.js b/ui/frontend/.eslintrc.js index 8cafb9689..0f6a93e8f 100644 --- a/ui/frontend/.eslintrc.js +++ b/ui/frontend/.eslintrc.js @@ -62,6 +62,7 @@ module.exports = { { files: [ '.eslintrc.js', + 'BuildMenu.tsx', 'PopButton.tsx', 'compileActions.ts', 'editor/AceEditor.tsx', diff --git a/ui/frontend/.prettierignore b/ui/frontend/.prettierignore index 6842e6ae4..56ee95372 100644 --- a/ui/frontend/.prettierignore +++ b/ui/frontend/.prettierignore @@ -11,6 +11,7 @@ node_modules # Slowly migrate files that we've touched !.eslintrc.js +!BuildMenu.tsx !PopButton.tsx !compileActions.ts !editor/AceEditor.tsx diff --git a/ui/frontend/BuildMenu.tsx b/ui/frontend/BuildMenu.tsx index d42dc93c1..6900f51f7 100644 --- a/ui/frontend/BuildMenu.tsx +++ b/ui/frontend/BuildMenu.tsx @@ -1,13 +1,12 @@ import React, { useCallback } from 'react'; import { useSelector } from 'react-redux'; -import * as actions from './actions'; -import * as selectors from './selectors'; -import { useAppDispatch } from './configureStore'; - import ButtonMenuItem from './ButtonMenuItem'; -import MenuGroup from './MenuGroup'; import MenuAside from './MenuAside'; +import MenuGroup from './MenuGroup'; +import * as actions from './actions'; +import { useAppDispatch } from './configureStore'; +import * as selectors from './selectors'; import styles from './BuildMenu.module.css'; @@ -18,16 +17,13 @@ interface BuildMenuProps { const useDispatchAndClose = (action: () => actions.ThunkAction, close: () => void) => { const dispatch = useAppDispatch(); - return useCallback( - () => { - dispatch(action()); - close(); - }, - [action, close, dispatch] - ); -} + return useCallback(() => { + dispatch(action()); + close(); + }, [action, close, dispatch]); +}; -const BuildMenu: React.FC = props => { +const BuildMenu: React.FC = (props) => { const isHirAvailable = useSelector(selectors.isHirAvailable); const compile = useDispatchAndClose(actions.performCompile, props.close); @@ -42,16 +38,16 @@ const BuildMenu: React.FC = props => { return ( - Build and run the code, showing the output. - Equivalent to cargo run. + Build and run the code, showing the output. Equivalent to{' '} + cargo run. - Build the code without running it. - Equivalent to cargo build. + Build the code without running it. Equivalent to{' '} + cargo build. - Build the code and run all the tests. - Equivalent to cargo test. + Build the code and run all the tests. Equivalent to{' '} + cargo test. Build and show the resulting assembly code. @@ -75,8 +71,8 @@ const BuildMenu: React.FC = props => { const HirAside: React.FC = () => ( - Note: HIR currently requires using the Nightly channel, selecting this - option will switch to Nightly. + Note: HIR currently requires using the Nightly channel, selecting this option will switch to + Nightly. ); From 9c9b6fd9d067cf62544a8bc174d2db2b29937735 Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Thu, 7 Sep 2023 08:56:54 -0400 Subject: [PATCH 2/3] Extract a common inline code component --- ui/frontend/BuildMenu.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/frontend/BuildMenu.tsx b/ui/frontend/BuildMenu.tsx index 6900f51f7..07f531591 100644 --- a/ui/frontend/BuildMenu.tsx +++ b/ui/frontend/BuildMenu.tsx @@ -38,16 +38,13 @@ const BuildMenu: React.FC = (props) => { return ( - Build and run the code, showing the output. Equivalent to{' '} - cargo run. + Build and run the code, showing the output. Equivalent to cargo run. - Build the code without running it. Equivalent to{' '} - cargo build. + Build the code without running it. Equivalent to cargo build. - Build the code and run all the tests. Equivalent to{' '} - cargo test. + Build the code and run all the tests. Equivalent to cargo test. Build and show the resulting assembly code. @@ -69,6 +66,10 @@ const BuildMenu: React.FC = (props) => { ); }; +const Code: React.FC<{ children: string }> = ({ children }) => ( + {children} +); + const HirAside: React.FC = () => ( Note: HIR currently requires using the Nightly channel, selecting this option will switch to From bf603e8562f8e2907a9861c6a940b3da3efdd027 Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Thu, 7 Sep 2023 09:33:24 -0400 Subject: [PATCH 3/3] Guide the user to using `#[crate_type="cdylib"]` when using Wasm. The playground uses `bin` when a main function is detected and `lib` otherwise. However, `lib` is not useful for Wasm and people probably want `cdylib`. This PR adds the `crate_type` attribute to the file when selecting Wasm, if there isn't already one. Fixes #586 --- tests/spec/features/compilation_targets_spec.rb | 17 +++++++++++++++++ ui/frontend/BuildMenu.tsx | 10 ++++++++++ ui/frontend/actions.ts | 17 ++++++++++++++++- ui/frontend/reducers/code.ts | 3 +++ ui/frontend/selectors/index.ts | 9 +++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/tests/spec/features/compilation_targets_spec.rb b/tests/spec/features/compilation_targets_spec.rb index a3bbb787e..11712733c 100644 --- a/tests/spec/features/compilation_targets_spec.rb +++ b/tests/spec/features/compilation_targets_spec.rb @@ -107,6 +107,8 @@ end scenario "compiling to WebAssembly" do + editor.set ['#![crate_type = "bin"]', code].join("\n") + in_build_menu { click_on("Wasm") } within(:output, :code) do @@ -115,6 +117,21 @@ end end + scenario "compiling a library to WebAssembly" do + editor.set <<~EOF + #[no_mangle] + pub fn calculator(a: u8) -> u8 { a + 42 } + EOF + + in_build_menu { click_on("Wasm") } + + within(:output, :code) do + expect(page).to have_content '(func $calculator (export "calculator")' + end + + expect(editor).to have_line('#![crate_type = "cdylib"]') + end + context "when the code doesn't compile" do before { editor.set("fn main() {") } diff --git a/ui/frontend/BuildMenu.tsx b/ui/frontend/BuildMenu.tsx index 07f531591..46cba78a3 100644 --- a/ui/frontend/BuildMenu.tsx +++ b/ui/frontend/BuildMenu.tsx @@ -25,6 +25,7 @@ const useDispatchAndClose = (action: () => actions.ThunkAction, close: () => voi const BuildMenu: React.FC = (props) => { const isHirAvailable = useSelector(selectors.isHirAvailable); + const wasmLikelyToWork = useSelector(selectors.wasmLikelyToWork); const compile = useDispatchAndClose(actions.performCompile, props.close); const compileToAssembly = useDispatchAndClose(actions.performCompileToAssembly, props.close); @@ -61,6 +62,7 @@ const BuildMenu: React.FC = (props) => { Build a WebAssembly module for web browsers, in the .WAT textual representation. + {!wasmLikelyToWork && } ); @@ -77,4 +79,12 @@ const HirAside: React.FC = () => ( ); +const WasmAside: React.FC = () => ( + + Note: WebAssembly works best when using the cdylib crate type, but the source code + does not specify an explicit crate type. Selecting this option will change the code to specify{' '} + cdylib. + +); + export default BuildMenu; diff --git a/ui/frontend/actions.ts b/ui/frontend/actions.ts index 4f23ed38e..fa1d58127 100644 --- a/ui/frontend/actions.ts +++ b/ui/frontend/actions.ts @@ -6,6 +6,7 @@ import { clippyRequestSelector, getCrateType, runAsTest, + wasmLikelyToWork, } from './selectors'; import State from './state'; import { @@ -89,6 +90,7 @@ export enum ActionType { EditCode = 'EDIT_CODE', AddMainFunction = 'ADD_MAIN_FUNCTION', AddImport = 'ADD_IMPORT', + AddCrateType = 'ADD_CRATE_TYPE', EnableFeatureGate = 'ENABLE_FEATURE_GATE', GotoPosition = 'GOTO_POSITION', SelectText = 'SELECT_TEXT', @@ -272,6 +274,15 @@ const performCompileToNightlyHirOnly = (): ThunkAction => dispatch => { dispatch(performCompileToHirOnly()); }; +const performCompileToCdylibWasmOnly = (): ThunkAction => (dispatch, getState) => { + const state = getState(); + + if (!wasmLikelyToWork(state)) { + dispatch(addCrateType('cdylib')); + } + dispatch(performCompileToWasmOnly()); +}; + const PRIMARY_ACTIONS: { [index in PrimaryAction]: () => ThunkAction } = { [PrimaryActionCore.Asm]: performCompileToAssemblyOnly, [PrimaryActionCore.Compile]: performCompileOnly, @@ -310,7 +321,7 @@ export const performCompileToMir = export const performCompileToNightlyHir = performAndSwitchPrimaryAction(performCompileToNightlyHirOnly, PrimaryActionCore.Hir); export const performCompileToWasm = - performAndSwitchPrimaryAction(performCompileToWasmOnly, PrimaryActionCore.Wasm); + performAndSwitchPrimaryAction(performCompileToCdylibWasmOnly, PrimaryActionCore.Wasm); export const editCode = (code: string) => createAction(ActionType.EditCode, { code }); @@ -321,6 +332,9 @@ export const addMainFunction = () => export const addImport = (code: string) => createAction(ActionType.AddImport, { code }); +export const addCrateType = (crateType: string) => + createAction(ActionType.AddCrateType, { crateType }); + export const enableFeatureGate = (featureGate: string) => createAction(ActionType.EnableFeatureGate, { featureGate }); @@ -617,6 +631,7 @@ export type Action = | ReturnType | ReturnType | ReturnType + | ReturnType | ReturnType | ReturnType | ReturnType diff --git a/ui/frontend/reducers/code.ts b/ui/frontend/reducers/code.ts index 2bdf322d6..a7200e89c 100644 --- a/ui/frontend/reducers/code.ts +++ b/ui/frontend/reducers/code.ts @@ -19,6 +19,9 @@ export default function code(state = DEFAULT, action: Action): State { case ActionType.AddImport: return action.code + state; + case ActionType.AddCrateType: + return `#![crate_type = "${action.crateType}"]\n${state}`; + case ActionType.EnableFeatureGate: return `#![feature(${action.featureGate})]\n${state}`; diff --git a/ui/frontend/selectors/index.ts b/ui/frontend/selectors/index.ts index e32998695..a5c517595 100644 --- a/ui/frontend/selectors/index.ts +++ b/ui/frontend/selectors/index.ts @@ -127,6 +127,15 @@ export const isNightlyChannel = (state: State) => ( ); export const isHirAvailable = isNightlyChannel; +export const wasmLikelyToWork = createSelector( + crateTypeSelector, + getCrateType, (userCrateType, crateType) => { + // If the user set it already, assume they know what they are doing + if (userCrateType) { return true } + + return crateType === 'cdylib'; + }); + export const getModeLabel = (state: State) => { const { configuration: { mode } } = state; return `${mode}`;