From c6c63e8a383bb404eea26a417f55d16a9cc95362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Houl=C3=A9?= <13155277+tomhoule@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:27:05 +0200 Subject: [PATCH] extension_api: Add IPv6 support in DAP extensions API (#54984) Fix forward for #52244 Release Notes: - N/A --- crates/extension_api/wit/since_v0.8.0/dap.wit | 12 +- crates/extension_host/src/wasm_host/wit.rs | 28 +- .../src/wasm_host/wit/since_v0_6_0.rs | 326 +++++++++++++++++- .../src/wasm_host/wit/since_v0_8_0.rs | 89 ++--- 4 files changed, 401 insertions(+), 54 deletions(-) diff --git a/crates/extension_api/wit/since_v0.8.0/dap.wit b/crates/extension_api/wit/since_v0.8.0/dap.wit index 693befe02f9..f0fd9bf138d 100644 --- a/crates/extension_api/wit/since_v0.8.0/dap.wit +++ b/crates/extension_api/wit/since_v0.8.0/dap.wit @@ -20,15 +20,23 @@ interface dap { attach(attach-request) } + type ipv4-address = tuple; + type ipv6-address = tuple; + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + record tcp-arguments { port: u16, - host: u32, + host: ip-address, timeout: option, } record tcp-arguments-template { port: option, - host: option, + host: option, timeout: option, } diff --git a/crates/extension_host/src/wasm_host/wit.rs b/crates/extension_host/src/wasm_host/wit.rs index 27847422f01..83bfdbc818e 100644 --- a/crates/extension_host/src/wasm_host/wit.rs +++ b/crates/extension_host/src/wasm_host/wit.rs @@ -16,7 +16,7 @@ use lsp::LanguageServerName; use release_channel::ReleaseChannel; use task::{DebugScenario, SpawnInTerminal, TaskTemplate, ZedDebugConfig}; -use crate::wasm_host::wit::since_v0_6_0::dap::StartDebuggingRequestArgumentsRequest; +use latest::dap::StartDebuggingRequestArgumentsRequest; use super::{WasmState, wasm_engine}; use anyhow::{Context as _, Result, anyhow}; @@ -1072,18 +1072,19 @@ impl Extension { Ok(Ok(dap_binary)) } Extension::V0_6_0(ext) => { + let task: latest::DebugTaskDefinition = task.try_into()?; let dap_binary = ext .call_get_dap_binary( store, &adapter_name, - &task.try_into()?, + &task.into(), user_installed_path.as_ref().and_then(|p| p.to_str()), resource, ) .await? .map_err(|e| anyhow!("{e:?}"))?; - Ok(Ok(dap_binary)) + Ok(Ok(dap_binary.into())) } Extension::V0_5_0(_) | Extension::V0_4_0(_) @@ -1123,7 +1124,7 @@ impl Extension { .await? .map_err(|e| anyhow!("{e:?}"))?; - Ok(Ok(dap_binary)) + Ok(Ok(dap_binary.into())) } Extension::V0_5_0(_) | Extension::V0_4_0(_) @@ -1154,12 +1155,13 @@ impl Extension { Ok(Ok(dap_binary.try_into()?)) } Extension::V0_6_0(ext) => { - let config = config.into(); + let config: latest::DebugConfig = config.into(); let dap_binary = ext - .call_dap_config_to_scenario(store, &config) + .call_dap_config_to_scenario(store, &config.into()) .await? .map_err(|e| anyhow!("{e:?}"))?; + let dap_binary: latest::DebugScenario = dap_binary.into(); Ok(Ok(dap_binary.try_into()?)) } Extension::V0_5_0(_) @@ -1199,18 +1201,20 @@ impl Extension { Ok(dap_binary.map(TryInto::try_into).transpose()?) } Extension::V0_6_0(ext) => { - let build_config_template = build_config_template.into(); + let build_config_template: latest::dap::TaskTemplate = build_config_template.into(); let dap_binary = ext .call_dap_locator_create_scenario( store, &locator_name, - &build_config_template, + &build_config_template.into(), &resolved_label, &debug_adapter_name, ) .await?; - Ok(dap_binary.map(TryInto::try_into).transpose()?) + Ok(dap_binary + .map(|s| latest::DebugScenario::from(s).try_into()) + .transpose()?) } Extension::V0_5_0(_) | Extension::V0_4_0(_) @@ -1242,12 +1246,14 @@ impl Extension { Ok(Ok(dap_request.into())) } Extension::V0_6_0(ext) => { - let build_config_template = resolved_build_task.try_into()?; + let build_config_template: latest::dap::TaskTemplate = + resolved_build_task.try_into()?; let dap_request = ext - .call_run_dap_locator(store, &locator_name, &build_config_template) + .call_run_dap_locator(store, &locator_name, &build_config_template.into()) .await? .map_err(|e| anyhow!("{e:?}"))?; + let dap_request: latest::DebugRequest = dap_request.into(); Ok(Ok(dap_request.into())) } Extension::V0_5_0(_) diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index bc5674b0517..91d446e1637 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -30,7 +30,6 @@ wasmtime::component::bindgen!({ "zed:extension/process": latest::zed::extension::process, "zed:extension/slash-command": latest::zed::extension::slash_command, "zed:extension/context-server": latest::zed::extension::context_server, - "zed:extension/dap": latest::zed::extension::dap, }, }); @@ -384,3 +383,328 @@ impl ExtensionImports for WasmState { latest::ExtensionImports::make_file_executable(self, path).await } } + +impl From for latest::dap::TcpArguments { + fn from(value: dap::TcpArguments) -> Self { + let [a, b, c, d] = std::net::Ipv4Addr::from_bits(value.host).octets(); + Self { + host: latest::dap::IpAddress::Ipv4((a, b, c, d)), + port: value.port, + timeout: value.timeout, + } + } +} + +impl TryFrom for dap::TcpArguments { + type Error = anyhow::Error; + + fn try_from(value: latest::dap::TcpArguments) -> Result { + let host = match value.host { + latest::dap::IpAddress::Ipv4((a, b, c, d)) => { + std::net::Ipv4Addr::new(a, b, c, d).to_bits() + } + latest::dap::IpAddress::Ipv6((a, b, c, d, e, f, g, h)) => { + let addr = std::net::Ipv6Addr::new(a, b, c, d, e, f, g, h); + anyhow::bail!( + "DAP returned IPv6 host {addr}, which the v0.6.0 extension API cannot represent; the extension must be updated to v0.8.0 or later" + ); + } + }; + Ok(Self { + host, + port: value.port, + timeout: value.timeout, + }) + } +} + +impl From for latest::dap::TcpArgumentsTemplate { + fn from(value: dap::TcpArgumentsTemplate) -> Self { + Self { + host: value.host.map(|host| { + let [a, b, c, d] = std::net::Ipv4Addr::from_bits(host).octets(); + latest::dap::IpAddress::Ipv4((a, b, c, d)) + }), + port: value.port, + timeout: value.timeout, + } + } +} + +impl From for dap::TcpArgumentsTemplate { + fn from(value: latest::dap::TcpArgumentsTemplate) -> Self { + Self { + host: value.host.and_then(|host| match host { + latest::dap::IpAddress::Ipv4((a, b, c, d)) => { + Some(std::net::Ipv4Addr::new(a, b, c, d).to_bits()) + } + latest::dap::IpAddress::Ipv6((a, b, c, d, e, f, g, h)) => { + let addr = std::net::Ipv6Addr::new(a, b, c, d, e, f, g, h); + log::warn!( + "Dropping IPv6 host {addr} when handing TCP arguments back to a v0.6.0 extension; update the extension to v0.8.0 or later for IPv6 support" + ); + None + } + }), + port: value.port, + timeout: value.timeout, + } + } +} + +impl From for latest::dap::LaunchRequest { + fn from(value: dap::LaunchRequest) -> Self { + Self { + program: value.program, + cwd: value.cwd, + args: value.args, + envs: value.envs, + } + } +} + +impl From for dap::LaunchRequest { + fn from(value: latest::dap::LaunchRequest) -> Self { + Self { + program: value.program, + cwd: value.cwd, + args: value.args, + envs: value.envs, + } + } +} + +impl From for latest::dap::AttachRequest { + fn from(value: dap::AttachRequest) -> Self { + Self { + process_id: value.process_id, + } + } +} + +impl From for dap::AttachRequest { + fn from(value: latest::dap::AttachRequest) -> Self { + Self { + process_id: value.process_id, + } + } +} + +impl From for latest::DebugRequest { + fn from(value: DebugRequest) -> Self { + match value { + DebugRequest::Launch(req) => Self::Launch(req.into()), + DebugRequest::Attach(req) => Self::Attach(req.into()), + } + } +} + +impl From for DebugRequest { + fn from(value: latest::DebugRequest) -> Self { + match value { + latest::DebugRequest::Launch(req) => Self::Launch(req.into()), + latest::DebugRequest::Attach(req) => Self::Attach(req.into()), + } + } +} + +impl From for latest::DebugConfig { + fn from(value: DebugConfig) -> Self { + Self { + label: value.label, + adapter: value.adapter, + request: value.request.into(), + stop_on_entry: value.stop_on_entry, + } + } +} + +impl From for DebugConfig { + fn from(value: latest::DebugConfig) -> Self { + Self { + label: value.label, + adapter: value.adapter, + request: value.request.into(), + stop_on_entry: value.stop_on_entry, + } + } +} + +impl From for latest::dap::TaskTemplate { + fn from(value: dap::TaskTemplate) -> Self { + Self { + label: value.label, + command: value.command, + args: value.args, + env: value.env, + cwd: value.cwd, + } + } +} + +impl From for dap::TaskTemplate { + fn from(value: latest::dap::TaskTemplate) -> Self { + Self { + label: value.label, + command: value.command, + args: value.args, + env: value.env, + cwd: value.cwd, + } + } +} + +impl From for latest::dap::BuildTaskDefinition { + fn from(value: dap::BuildTaskDefinition) -> Self { + match value { + dap::BuildTaskDefinition::ByName(name) => Self::ByName(name), + dap::BuildTaskDefinition::Template(payload) => { + Self::Template(latest::dap::BuildTaskDefinitionTemplatePayload { + locator_name: payload.locator_name, + template: payload.template.into(), + }) + } + } + } +} + +impl From for dap::BuildTaskDefinition { + fn from(value: latest::dap::BuildTaskDefinition) -> Self { + match value { + latest::dap::BuildTaskDefinition::ByName(name) => Self::ByName(name), + latest::dap::BuildTaskDefinition::Template(payload) => { + Self::Template(dap::BuildTaskDefinitionTemplatePayload { + locator_name: payload.locator_name, + template: payload.template.into(), + }) + } + } + } +} + +impl From for latest::DebugScenario { + fn from(value: DebugScenario) -> Self { + Self { + label: value.label, + adapter: value.adapter, + build: value.build.map(Into::into), + config: value.config, + tcp_connection: value.tcp_connection.map(Into::into), + } + } +} + +impl From for DebugScenario { + fn from(value: latest::DebugScenario) -> Self { + Self { + label: value.label, + adapter: value.adapter, + build: value.build.map(Into::into), + config: value.config, + tcp_connection: value.tcp_connection.map(Into::into), + } + } +} + +impl From for latest::DebugTaskDefinition { + fn from(value: DebugTaskDefinition) -> Self { + Self { + label: value.label, + adapter: value.adapter, + config: value.config, + tcp_connection: value.tcp_connection.map(Into::into), + } + } +} + +impl From for DebugTaskDefinition { + fn from(value: latest::DebugTaskDefinition) -> Self { + Self { + label: value.label, + adapter: value.adapter, + config: value.config, + tcp_connection: value.tcp_connection.map(Into::into), + } + } +} + +impl From + for latest::dap::StartDebuggingRequestArgumentsRequest +{ + fn from(value: dap::StartDebuggingRequestArgumentsRequest) -> Self { + match value { + dap::StartDebuggingRequestArgumentsRequest::Launch => Self::Launch, + dap::StartDebuggingRequestArgumentsRequest::Attach => Self::Attach, + } + } +} + +impl From + for dap::StartDebuggingRequestArgumentsRequest +{ + fn from(value: latest::dap::StartDebuggingRequestArgumentsRequest) -> Self { + match value { + latest::dap::StartDebuggingRequestArgumentsRequest::Launch => Self::Launch, + latest::dap::StartDebuggingRequestArgumentsRequest::Attach => Self::Attach, + } + } +} + +impl From for latest::dap::StartDebuggingRequestArguments { + fn from(value: dap::StartDebuggingRequestArguments) -> Self { + Self { + configuration: value.configuration, + request: value.request.into(), + } + } +} + +impl From for dap::StartDebuggingRequestArguments { + fn from(value: latest::dap::StartDebuggingRequestArguments) -> Self { + Self { + configuration: value.configuration, + request: value.request.into(), + } + } +} + +impl From for latest::DebugAdapterBinary { + fn from(value: DebugAdapterBinary) -> Self { + Self { + command: value.command, + arguments: value.arguments, + envs: value.envs, + cwd: value.cwd, + connection: value.connection.map(Into::into), + request_args: value.request_args.into(), + } + } +} + +impl TryFrom for DebugAdapterBinary { + type Error = anyhow::Error; + + fn try_from(value: latest::DebugAdapterBinary) -> Result { + Ok(Self { + command: value.command, + arguments: value.arguments, + envs: value.envs, + cwd: value.cwd, + connection: value.connection.map(TryInto::try_into).transpose()?, + request_args: value.request_args.into(), + }) + } +} + +impl zed::extension::dap::Host for WasmState { + async fn resolve_tcp_template( + &mut self, + template: dap::TcpArgumentsTemplate, + ) -> wasmtime::Result> { + let result = latest::dap::Host::resolve_tcp_template(self, template.into()).await?; + Ok( + result + .and_then(|args| dap::TcpArguments::try_from(args).map_err(|err| err.to_string())), + ) + } +} diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs index 24cf0affd77..8da53ca638c 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs @@ -1,10 +1,4 @@ -use crate::wasm_host::wit::since_v0_6_0::{ - dap::{ - BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, StartDebuggingRequestArguments, - TcpArguments, TcpArgumentsTemplate, - }, - slash_command::SlashCommandOutputSection, -}; +use crate::wasm_host::wit::since_v0_6_0::slash_command::SlashCommandOutputSection; use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind}; use crate::wasm_host::{WasmState, wit::ToWasmtimeResult}; use ::http_client::{AsyncBody, HttpRequestExt}; @@ -24,7 +18,7 @@ use project::project_settings::ProjectSettings; use semver::Version; use std::{ env, - net::{IpAddr, Ipv4Addr}, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, path::{Path, PathBuf}, str::FromStr, sync::{Arc, OnceLock}, @@ -104,43 +98,66 @@ impl From } } } -impl TryFrom for extension::StartDebuggingRequestArguments { +impl TryFrom for extension::StartDebuggingRequestArguments { type Error = anyhow::Error; - fn try_from(value: StartDebuggingRequestArguments) -> Result { + fn try_from(value: dap::StartDebuggingRequestArguments) -> Result { Ok(Self { configuration: serde_json::from_str(&value.configuration)?, request: value.request.into(), }) } } -impl From for extension::TcpArguments { - fn from(value: TcpArguments) -> Self { +impl From for IpAddr { + fn from(value: dap::IpAddress) -> Self { + match value { + dap::IpAddress::Ipv4((a, b, c, d)) => IpAddr::V4(Ipv4Addr::new(a, b, c, d)), + dap::IpAddress::Ipv6((a, b, c, d, e, f, g, h)) => { + IpAddr::V6(Ipv6Addr::new(a, b, c, d, e, f, g, h)) + } + } + } +} + +impl From for dap::IpAddress { + fn from(value: IpAddr) -> Self { + match value { + IpAddr::V4(v4) => { + let [a, b, c, d] = v4.octets(); + Self::Ipv4((a, b, c, d)) + } + IpAddr::V6(v6) => { + let [a, b, c, d, e, f, g, h] = v6.segments(); + Self::Ipv6((a, b, c, d, e, f, g, h)) + } + } + } +} + +impl From for extension::TcpArguments { + fn from(value: dap::TcpArguments) -> Self { Self { - host: IpAddr::V4(Ipv4Addr::from_bits(value.host)), + host: value.host.into(), port: value.port, timeout: value.timeout, } } } -impl From for TcpArgumentsTemplate { +impl From for dap::TcpArgumentsTemplate { fn from(value: extension::TcpArgumentsTemplate) -> Self { Self { - host: value.host.and_then(|addr| match addr { - IpAddr::V4(v4) => Some(v4.to_bits()), - IpAddr::V6(_) => None, - }), + host: value.host.map(Into::into), port: value.port, timeout: value.timeout, } } } -impl From for extension::TcpArgumentsTemplate { - fn from(value: TcpArgumentsTemplate) -> Self { +impl From for extension::TcpArgumentsTemplate { + fn from(value: dap::TcpArgumentsTemplate) -> Self { Self { - host: value.host.map(|bits| IpAddr::V4(Ipv4Addr::from_bits(bits))), + host: value.host.map(Into::into), port: value.port, timeout: value.timeout, } @@ -238,11 +255,11 @@ impl TryFrom for extension::DebugAdapterBinary { } } -impl From for extension::BuildTaskDefinition { - fn from(value: BuildTaskDefinition) -> Self { +impl From for extension::BuildTaskDefinition { + fn from(value: dap::BuildTaskDefinition) -> Self { match value { - BuildTaskDefinition::ByName(name) => Self::ByName(name.into()), - BuildTaskDefinition::Template(build_task_template) => Self::Template { + dap::BuildTaskDefinition::ByName(name) => Self::ByName(name.into()), + dap::BuildTaskDefinition::Template(build_task_template) => Self::Template { task_template: build_task_template.template.into(), locator_name: build_task_template.locator_name.map(SharedString::from), }, @@ -250,14 +267,14 @@ impl From for extension::BuildTaskDefinition { } } -impl From for BuildTaskDefinition { +impl From for dap::BuildTaskDefinition { fn from(value: extension::BuildTaskDefinition) -> Self { match value { extension::BuildTaskDefinition::ByName(name) => Self::ByName(name.into()), extension::BuildTaskDefinition::Template { task_template, locator_name, - } => Self::Template(BuildTaskDefinitionTemplatePayload { + } => Self::Template(dap::BuildTaskDefinitionTemplatePayload { template: task_template.into(), locator_name: locator_name.map(String::from), }), @@ -901,27 +918,19 @@ impl context_server::Host for WasmState {} impl dap::Host for WasmState { async fn resolve_tcp_template( &mut self, - template: TcpArgumentsTemplate, - ) -> wasmtime::Result> { + template: dap::TcpArgumentsTemplate, + ) -> wasmtime::Result> { maybe!(async { let (host, port, timeout) = ::dap::configure_tcp_connection(task::TcpArgumentsTemplate { port: template.port, - host: template - .host - .map(|bits| IpAddr::V4(Ipv4Addr::from_bits(bits))), + host: template.host.map(Into::into), timeout: template.timeout, }) .await?; - let host_bits = match host { - IpAddr::V4(v4) => v4.to_bits(), - IpAddr::V6(_) => { - anyhow::bail!("IPv6 addresses are not supported in the extension API") - } - }; - Ok(TcpArguments { + Ok(dap::TcpArguments { port, - host: host_bits, + host: host.into(), timeout, }) })