Optimize LSP analysis a Lot
This commit is contained in:
parent
8595da0c30
commit
97a5c3a65e
@ -1,6 +1,5 @@
|
|||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
|
||||||
use reid::{
|
use reid::{
|
||||||
ast::lexer::FullToken,
|
ast::lexer::FullToken,
|
||||||
compile_module,
|
compile_module,
|
||||||
@ -9,12 +8,12 @@ use reid::{
|
|||||||
perform_all_passes,
|
perform_all_passes,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::CompileResult;
|
type TokenAnalysisMap = HashMap<usize, SemanticAnalysis>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StaticAnalysis {
|
pub struct StaticAnalysis {
|
||||||
pub tokens: Vec<FullToken>,
|
pub tokens: Vec<FullToken>,
|
||||||
pub token_analysis: HashMap<usize, SemanticAnalysis>,
|
pub token_analysis: TokenAnalysisMap,
|
||||||
pub error: Option<ReidError>,
|
pub error: Option<ReidError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,8 +28,6 @@ pub fn analyze(
|
|||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
map: &mut ErrorModules,
|
map: &mut ErrorModules,
|
||||||
) -> Result<Option<StaticAnalysis>, ReidError> {
|
) -> Result<Option<StaticAnalysis>, ReidError> {
|
||||||
let mut token_analysis = HashMap::new();
|
|
||||||
|
|
||||||
let (module, error) = match compile_module(module_id, tokens, map, Some(path.clone()), true)? {
|
let (module, error) = match compile_module(module_id, tokens, map, Some(path.clone()), true)? {
|
||||||
Ok(module) => (module, None),
|
Ok(module) => (module, None),
|
||||||
Err((m, err)) => (m.process(module_id), Some(err)),
|
Err((m, err)) => (m.process(module_id), Some(err)),
|
||||||
@ -40,255 +37,203 @@ pub fn analyze(
|
|||||||
let mut context = Context::from(vec![module], path.parent().unwrap().to_owned());
|
let mut context = Context::from(vec![module], path.parent().unwrap().to_owned());
|
||||||
perform_all_passes(&mut context, map)?;
|
perform_all_passes(&mut context, map)?;
|
||||||
|
|
||||||
for module in context.modules.into_values() {
|
for module in context.modules.values() {
|
||||||
if module.module_id != module_id {
|
if module.module_id != module_id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for idx in 0..module.tokens.len() {
|
return Ok(Some(analyze_context(&context, &module, error)));
|
||||||
token_analysis.insert(
|
|
||||||
idx,
|
|
||||||
SemanticAnalysis {
|
|
||||||
ty: find_type_in_context(&module, idx),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(Some(StaticAnalysis {
|
|
||||||
tokens: module.tokens,
|
|
||||||
token_analysis: token_analysis,
|
|
||||||
error,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_type_in_context(module: &mir::Module, token_idx: usize) -> Option<TypeKind> {
|
pub fn set_tokens(map: &mut TokenAnalysisMap, meta: &mir::Metadata, analysis: SemanticAnalysis) {
|
||||||
for import in &module.imports {
|
for token in meta.range.start..meta.range.end {
|
||||||
if import.1.contains(token_idx) {
|
map.insert(token, analysis.clone());
|
||||||
return Some(TypeKind::CustomType(mir::CustomTypeKey(
|
|
||||||
"d".to_owned(),
|
|
||||||
SourceModuleId(1),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for typedef in &module.typedefs {
|
}
|
||||||
if !typedef.meta.contains(token_idx) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn analyze_context(context: &mir::Context, module: &mir::Module, error: Option<ReidError>) -> StaticAnalysis {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
for import in &module.imports {
|
||||||
|
set_tokens(&mut map, &import.1, SemanticAnalysis { ty: None });
|
||||||
|
}
|
||||||
|
|
||||||
|
for typedef in &module.typedefs {
|
||||||
match &typedef.kind {
|
match &typedef.kind {
|
||||||
mir::TypeDefinitionKind::Struct(StructType(fields)) => {
|
mir::TypeDefinitionKind::Struct(StructType(fields)) => {
|
||||||
for field in fields {
|
for field in fields {
|
||||||
if field.2.contains(token_idx) {
|
set_tokens(
|
||||||
return Some(field.1.clone());
|
&mut map,
|
||||||
}
|
&field.2,
|
||||||
|
SemanticAnalysis {
|
||||||
|
ty: Some(field.1.clone()),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for binop in &module.binop_defs {
|
for binop in &module.binop_defs {
|
||||||
if let Some(meta) = binop.block_meta() {
|
match &binop.fn_kind {
|
||||||
if !meta.contains(token_idx) {
|
mir::FunctionDefinitionKind::Local(block, _) => analyze_block(context, module, block, &mut map),
|
||||||
continue;
|
mir::FunctionDefinitionKind::Extern(_) => {}
|
||||||
}
|
mir::FunctionDefinitionKind::Intrinsic(_) => {}
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return match &binop.fn_kind {
|
|
||||||
mir::FunctionDefinitionKind::Local(block, _) => find_type_in_block(&block, module.module_id, token_idx),
|
|
||||||
mir::FunctionDefinitionKind::Extern(_) => None,
|
|
||||||
mir::FunctionDefinitionKind::Intrinsic(_) => None,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_, function) in &module.associated_functions {
|
for (_, function) in &module.associated_functions {
|
||||||
if !(function.signature() + function.block_meta()).contains(token_idx) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for param in &function.parameters {
|
for param in &function.parameters {
|
||||||
if param.meta.contains(token_idx) {
|
set_tokens(
|
||||||
return Some(param.ty.clone());
|
&mut map,
|
||||||
}
|
¶m.meta,
|
||||||
|
SemanticAnalysis {
|
||||||
|
ty: Some(param.ty.clone()),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return match &function.kind {
|
match &function.kind {
|
||||||
mir::FunctionDefinitionKind::Local(block, _) => find_type_in_block(&block, module.module_id, token_idx),
|
mir::FunctionDefinitionKind::Local(block, _) => analyze_block(context, module, block, &mut map),
|
||||||
mir::FunctionDefinitionKind::Extern(_) => None,
|
mir::FunctionDefinitionKind::Extern(_) => {}
|
||||||
mir::FunctionDefinitionKind::Intrinsic(_) => None,
|
mir::FunctionDefinitionKind::Intrinsic(_) => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for function in &module.functions {
|
for function in &module.functions {
|
||||||
if !(function.signature() + function.block_meta()).contains(token_idx) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for param in &function.parameters {
|
for param in &function.parameters {
|
||||||
if param.meta.contains(token_idx) {
|
set_tokens(
|
||||||
return Some(param.ty.clone());
|
&mut map,
|
||||||
}
|
¶m.meta,
|
||||||
|
SemanticAnalysis {
|
||||||
|
ty: Some(param.ty.clone()),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return match &function.kind {
|
match &function.kind {
|
||||||
mir::FunctionDefinitionKind::Local(block, _) => find_type_in_block(&block, module.module_id, token_idx),
|
mir::FunctionDefinitionKind::Local(block, _) => analyze_block(context, module, block, &mut map),
|
||||||
mir::FunctionDefinitionKind::Extern(_) => None,
|
mir::FunctionDefinitionKind::Extern(_) => {}
|
||||||
mir::FunctionDefinitionKind::Intrinsic(_) => None,
|
mir::FunctionDefinitionKind::Intrinsic(_) => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
None
|
|
||||||
|
StaticAnalysis {
|
||||||
|
tokens: module.tokens.clone(),
|
||||||
|
token_analysis: map,
|
||||||
|
error,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_type_in_block(block: &mir::Block, module_id: SourceModuleId, token_idx: usize) -> Option<TypeKind> {
|
pub fn analyze_block(
|
||||||
if !block.meta.contains(token_idx) {
|
context: &mir::Context,
|
||||||
return Some(TypeKind::Bool);
|
source_module: &mir::Module,
|
||||||
}
|
block: &mir::Block,
|
||||||
|
map: &mut TokenAnalysisMap,
|
||||||
|
) {
|
||||||
for statement in &block.statements {
|
for statement in &block.statements {
|
||||||
if !statement.1.contains(token_idx) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match &statement.0 {
|
match &statement.0 {
|
||||||
mir::StmtKind::Let(named_variable_ref, _, expression) => {
|
mir::StmtKind::Let(named_variable_ref, _, expression) => {
|
||||||
if named_variable_ref.2.contains(token_idx) {
|
set_tokens(
|
||||||
return expression
|
map,
|
||||||
.return_type(&Default::default(), module_id)
|
&named_variable_ref.2,
|
||||||
.ok()
|
SemanticAnalysis {
|
||||||
.map(|(_, ty)| ty);
|
ty: expression
|
||||||
} else {
|
.return_type(&Default::default(), source_module.module_id)
|
||||||
return find_type_in_expr(&expression, module_id, token_idx);
|
.ok()
|
||||||
}
|
.map(|(_, ty)| ty),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// return analyze_in_expr(&expression, module_id, token_idx);
|
||||||
}
|
}
|
||||||
mir::StmtKind::Set(lhs, rhs) => {
|
mir::StmtKind::Set(lhs, rhs) => {
|
||||||
return find_type_in_expr(lhs, module_id, token_idx).or(find_type_in_expr(rhs, module_id, token_idx));
|
analyze_expr(context, source_module, lhs, map);
|
||||||
|
analyze_expr(context, source_module, rhs, map);
|
||||||
}
|
}
|
||||||
mir::StmtKind::Import(_) => {}
|
mir::StmtKind::Import(_) => {}
|
||||||
mir::StmtKind::Expression(expression) => return find_type_in_expr(expression, module_id, token_idx),
|
mir::StmtKind::Expression(expression) => {
|
||||||
|
analyze_expr(context, source_module, expression, map);
|
||||||
|
}
|
||||||
mir::StmtKind::While(WhileStatement { condition, block, .. }) => {
|
mir::StmtKind::While(WhileStatement { condition, block, .. }) => {
|
||||||
return find_type_in_expr(condition, module_id, token_idx)
|
analyze_expr(context, source_module, condition, map);
|
||||||
.or(find_type_in_block(block, module_id, token_idx));
|
analyze_block(context, source_module, block, map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_, Some(return_exp))) = &block.return_expression {
|
if let Some((_, Some(return_exp))) = &block.return_expression {
|
||||||
if let Some(ty) = find_type_in_expr(return_exp, module_id, token_idx) {
|
analyze_expr(context, source_module, return_exp, map)
|
||||||
return Some(ty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_type_in_expr(expr: &mir::Expression, module_id: SourceModuleId, token_idx: usize) -> Option<TypeKind> {
|
pub fn analyze_expr(
|
||||||
if !expr.1.contains(token_idx) {
|
context: &mir::Context,
|
||||||
return None;
|
source_module: &mir::Module,
|
||||||
}
|
expr: &mir::Expression,
|
||||||
|
map: &mut TokenAnalysisMap,
|
||||||
|
) {
|
||||||
|
set_tokens(
|
||||||
|
map,
|
||||||
|
&expr.1,
|
||||||
|
SemanticAnalysis {
|
||||||
|
ty: expr
|
||||||
|
.return_type(&Default::default(), source_module.module_id)
|
||||||
|
.ok()
|
||||||
|
.map(|(_, t)| t),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
match &expr.0 {
|
match &expr.0 {
|
||||||
mir::ExprKind::Variable(named_variable_ref) => Some(named_variable_ref.0.clone()),
|
mir::ExprKind::Variable(_) => {}
|
||||||
mir::ExprKind::Indexed(value, type_kind, index_expr) => Some(
|
mir::ExprKind::Indexed(value, _, index_expr) => {
|
||||||
find_type_in_expr(&value, module_id, token_idx)
|
analyze_expr(context, source_module, &value, map);
|
||||||
.or(find_type_in_expr(&index_expr, module_id, token_idx))
|
analyze_expr(context, source_module, &index_expr, map);
|
||||||
.unwrap_or(type_kind.clone()),
|
}
|
||||||
),
|
mir::ExprKind::Accessed(expression, ..) => {
|
||||||
mir::ExprKind::Accessed(expression, type_kind, _, meta) => {
|
analyze_expr(context, source_module, &expression, map);
|
||||||
if meta.contains(token_idx) {
|
|
||||||
Some(type_kind.clone())
|
|
||||||
} else {
|
|
||||||
find_type_in_expr(&expression, module_id, token_idx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mir::ExprKind::Array(expressions) => {
|
mir::ExprKind::Array(expressions) => {
|
||||||
for expr in expressions {
|
for expr in expressions {
|
||||||
if let Some(ty) = find_type_in_expr(expr, module_id, token_idx) {
|
analyze_expr(context, source_module, expr, map);
|
||||||
return Some(ty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
mir::ExprKind::Struct(name, items) => {
|
mir::ExprKind::Struct(_, items) => {
|
||||||
for (_, expr, meta) in items {
|
for (_, expr, _) in items {
|
||||||
if meta.contains(token_idx) {
|
analyze_expr(context, source_module, expr, map);
|
||||||
return expr.return_type(&Default::default(), module_id).map(|(_, t)| t).ok();
|
|
||||||
}
|
|
||||||
if let Some(ty) = find_type_in_expr(expr, module_id, token_idx) {
|
|
||||||
return Some(ty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(TypeKind::CustomType(mir::CustomTypeKey(name.clone(), module_id)))
|
|
||||||
}
|
}
|
||||||
mir::ExprKind::Literal(literal) => Some(literal.as_type()),
|
mir::ExprKind::Literal(_) => {}
|
||||||
mir::ExprKind::BinOp(_, lhs, rhs, type_kind) => {
|
mir::ExprKind::BinOp(_, lhs, rhs, _) => {
|
||||||
if let Some(ty) = find_type_in_expr(lhs, module_id, token_idx) {
|
analyze_expr(context, source_module, &lhs, map);
|
||||||
return Some(ty);
|
analyze_expr(context, source_module, &rhs, map);
|
||||||
}
|
|
||||||
if let Some(ty) = find_type_in_expr(rhs, module_id, token_idx) {
|
|
||||||
return Some(ty);
|
|
||||||
}
|
|
||||||
Some(type_kind.clone())
|
|
||||||
}
|
}
|
||||||
mir::ExprKind::FunctionCall(FunctionCall {
|
mir::ExprKind::FunctionCall(FunctionCall { parameters, .. }) => {
|
||||||
return_type,
|
|
||||||
parameters,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
for expr in parameters {
|
for expr in parameters {
|
||||||
if let Some(ty) = find_type_in_expr(expr, module_id, token_idx) {
|
analyze_expr(context, source_module, expr, map);
|
||||||
return Some(ty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(return_type.clone())
|
|
||||||
}
|
}
|
||||||
mir::ExprKind::AssociatedFunctionCall(
|
mir::ExprKind::AssociatedFunctionCall(_, FunctionCall { parameters, .. }) => {
|
||||||
_,
|
|
||||||
FunctionCall {
|
|
||||||
return_type,
|
|
||||||
parameters,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
for expr in parameters {
|
for expr in parameters {
|
||||||
if let Some(ty) = find_type_in_expr(expr, module_id, token_idx) {
|
analyze_expr(context, source_module, expr, map);
|
||||||
return Some(ty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(return_type.clone())
|
|
||||||
}
|
}
|
||||||
mir::ExprKind::If(IfExpression(cond, then_e, else_e)) => find_type_in_expr(&cond, module_id, token_idx)
|
mir::ExprKind::If(IfExpression(cond, then_e, else_e)) => {
|
||||||
.or(find_type_in_expr(&then_e, module_id, token_idx))
|
analyze_expr(context, source_module, &cond, map);
|
||||||
.or(else_e.clone().and_then(|e| find_type_in_expr(&e, module_id, token_idx))),
|
analyze_expr(context, source_module, &then_e, map);
|
||||||
mir::ExprKind::Block(block) => find_type_in_block(block, module_id, token_idx),
|
if let Some(else_e) = else_e.as_ref() {
|
||||||
mir::ExprKind::Borrow(expression, mutable) => {
|
analyze_expr(context, source_module, &else_e, map);
|
||||||
if let Some(ty) = find_type_in_expr(&expression, module_id, token_idx) {
|
|
||||||
return Some(ty);
|
|
||||||
}
|
|
||||||
if let Ok(inner) = expression.return_type(&Default::default(), module_id).map(|(_, ty)| ty) {
|
|
||||||
Some(TypeKind::Borrow(Box::new(inner.clone()), *mutable))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mir::ExprKind::Block(block) => analyze_block(context, source_module, block, map),
|
||||||
|
mir::ExprKind::Borrow(expression, _) => {
|
||||||
|
analyze_expr(context, source_module, &expression, map);
|
||||||
|
}
|
||||||
mir::ExprKind::Deref(expression) => {
|
mir::ExprKind::Deref(expression) => {
|
||||||
if let Some(ty) = find_type_in_expr(&expression, module_id, token_idx) {
|
analyze_expr(context, source_module, &expression, map);
|
||||||
return Some(ty);
|
|
||||||
}
|
|
||||||
if let Ok(TypeKind::Borrow(inner, _)) =
|
|
||||||
expression.return_type(&Default::default(), module_id).map(|(_, ty)| ty)
|
|
||||||
{
|
|
||||||
Some(*inner.clone())
|
|
||||||
} else {
|
|
||||||
Some(TypeKind::CustomType(mir::CustomTypeKey(
|
|
||||||
"ä".to_owned(),
|
|
||||||
SourceModuleId(1),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mir::ExprKind::CastTo(expression, type_kind) => {
|
mir::ExprKind::CastTo(expression, _) => {
|
||||||
Some(find_type_in_expr(&expression, module_id, token_idx).unwrap_or(type_kind.clone()))
|
analyze_expr(context, source_module, &expression, map);
|
||||||
}
|
}
|
||||||
mir::ExprKind::GlobalRef(_, type_kind) => Some(type_kind.clone()),
|
mir::ExprKind::GlobalRef(_, _) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ mod analysis;
|
|||||||
struct Backend {
|
struct Backend {
|
||||||
client: Client,
|
client: Client,
|
||||||
analysis: DashMap<String, StaticAnalysis>,
|
analysis: DashMap<String, StaticAnalysis>,
|
||||||
ast: DashMap<String, reid::ast::Module>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tower_lsp::async_trait]
|
#[tower_lsp::async_trait]
|
||||||
@ -91,7 +90,7 @@ impl LanguageServer for Backend {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (range, ty) = if let Some((idx, token)) = token {
|
let (range, ty) = if let Some((idx, token)) = token {
|
||||||
if let Some(possible_ty) = self.analysis.get(&file_name).unwrap().token_analysis.get(&idx) {
|
if let Some(analysis) = self.analysis.get(&file_name).unwrap().token_analysis.get(&idx) {
|
||||||
let start = token.position;
|
let start = token.position;
|
||||||
let end = token.position.add(token.token.len() as u32);
|
let end = token.position.add(token.token.len() as u32);
|
||||||
let range = Range {
|
let range = Range {
|
||||||
@ -104,7 +103,7 @@ impl LanguageServer for Backend {
|
|||||||
character: (end.0 as i32 - 1).max(0) as u32,
|
character: (end.0 as i32 - 1).max(0) as u32,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if let Some(ty) = possible_ty.ty.clone() {
|
if let Some(ty) = analysis.ty.clone() {
|
||||||
(Some(range), format!("{}", ty))
|
(Some(range), format!("{}", ty))
|
||||||
} else {
|
} else {
|
||||||
(Some(range), String::from("None type"))
|
(Some(range), String::from("None type"))
|
||||||
@ -220,11 +219,6 @@ fn reid_error_into_diagnostic(error: &error_raporting::ErrorKind, tokens: &Vec<F
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CompileResult {
|
|
||||||
tokens: Vec<FullToken>,
|
|
||||||
types: DashMap<FullToken, Option<TypeKind>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(source: &str, path: PathBuf, map: &mut ErrorModules) -> Result<(SourceModuleId, Vec<FullToken>), ReidError> {
|
fn parse(source: &str, path: PathBuf, map: &mut ErrorModules) -> Result<(SourceModuleId, Vec<FullToken>), ReidError> {
|
||||||
let file_name = path.file_name().unwrap().to_str().unwrap().to_owned();
|
let file_name = path.file_name().unwrap().to_str().unwrap().to_owned();
|
||||||
|
|
||||||
@ -238,7 +232,6 @@ async fn main() {
|
|||||||
|
|
||||||
let (service, socket) = LspService::new(|client| Backend {
|
let (service, socket) = LspService::new(|client| Backend {
|
||||||
client,
|
client,
|
||||||
ast: DashMap::new(),
|
|
||||||
analysis: DashMap::new(),
|
analysis: DashMap::new(),
|
||||||
});
|
});
|
||||||
Server::new(stdin, stdout, socket).serve(service).await;
|
Server::new(stdin, stdout, socket).serve(service).await;
|
||||||
|
Loading…
Reference in New Issue
Block a user