slint-ui/slint · error · std::io::Error

{e:?}

Error message

{e:?}

What it means

After the `load_file` JS callback returns a Promise, the LSP awaits it as a JsFuture; if the promise resolves to a rejection (the file could not actually be read by the browser), the rejection JsValue is Debug-formatted into a std::io::Error. This is thrown because awaiting the file-load promise is mandatory to produce the source text the compiler needs. The Debug form loses the human-readable JS error message.

Solutions

  1. Open the browser devtools console to read the actual rejection reason hidden behind the debug-formatted JsValue.
  2. Ensure the JS callback rejects only on real errors and resolves with a UTF-8 string for success (stringify Blobs/ArrayBuffers before returning).
  3. Fix file serving: correct path mapping, MIME/permissions, and CORS so the fetch inside `load_file` succeeds.
  4. Add retry/backoff in the host JS `load_file` for transient network failures.
  5. Upgrade wasm-bindgen/wasm-bindgen-futures consistently in the LSP build if JsFuture conversion is involved in the failure.

Example fix

// before (host JS)
const load_file = (path) => fetch(path).then(r => r.arrayBuffer());
// after
const load_file = async (path) => {
  const res = await fetch(path);
  if (!res.ok) throw new Error(`HTTP ${res.status} for ${path}`);
  return res.text();
};
Defensive patterns

Strategy: retry

Validate before calling

// JS host side: ensure the endpoint is reachable before init
const probe = await fetch(basePath + '/health');
if (!probe.ok) throw new Error('file server unavailable');

Type guard

// JS host side: only resolve with strings
function asText(v) { return typeof v === 'string' ? v : Promise.reject(new Error('load_file must resolve to a string')); }

Try / catch

const load_file = async (path) => {
  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      const res = await fetch(path);
      if (res.status >= 500 || res.status === 0) throw new Error('transient');
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return await res.text();
    } catch (e) {
      if (attempt === 2) throw e;
      await new Promise(r => setTimeout(r, 500 * 2 ** attempt));
    }
  }
};

Prevention

When it happens

Trigger: The injected JS `load_file` function's returned Promise rejects: fetch returned non-2xx (mapped to rejection), network failure, the callback threw asynchronously, or resolved with a non-string that downstream handling turns into a failure.

Common situations: 404/CORS on the served file in the browser, WebSocket-based file service disconnecting, a Promise polyfill mismatch in older browsers, or the host page's `load_file` rejecting because the path is outside the served virtual filesystem.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of slint-ui/slint@bb937076de (2026-09-16). Data as JSON: /api/errors/ece834d5dc366af2. Report an issue: GitHub.

Appendix: source

Thrown at tools/lsp/wasm_main.rs:480

            .handle_request(method, params, &mut ctx)
            .map_err(|e| JsError::new(&e.to_string()))?;
        Ok(result)
    }

    #[wasm_bindgen]
    pub async fn reload_config(&self) -> JsResult<()> {
        let mut ctx = self.ctx.lock().await;
        language::load_configuration(&mut ctx).await.map_err(|e| JsError::new(&e.to_string()))
    }
}

async fn load_file(path: String, load_file: &Function) -> std::io::Result<String> {
    let string_promise = load_file
        .call1(&JsValue::UNDEFINED, &path.into())
        .map_err(|x| std::io::Error::new(ErrorKind::Other, format!("{x:?}")))?;
    let string_future = wasm_bindgen_futures::JsFuture::from(js_sys::Promise::from(string_promise));
    let js_value =
        string_future.await.map_err(|e| std::io::Error::new(ErrorKind::Other, format!("{e:?}")))?;
    return Ok(js_value.as_string().unwrap_or_default());
}

// Use a JSON friendly representation to avoid using ES maps instead of JS objects.
fn to_value<T: serde::Serialize + ?Sized>(
    value: &T,
) -> std::result::Result<wasm_bindgen::JsValue, serde_wasm_bindgen::Error> {
    value.serialize(&serde_wasm_bindgen::Serializer::json_compatible())
}

View on GitHub (pinned to bb937076de)