러스트 LSP 구축의 기술적 난제와 아키텍처
Why building a Rust LSP is hard
Hacker NewsLSP는 컴파일러와 달리, 프로젝트의 불완전한 상태에서도 사용자에게 즉각적이고 유용한 피드백을 제공해야 하는 복잡한 시스템입니다.
- 단순 구문 분석을 넘어 코드의 의미 구조를 파악하는 '시맨틱 분석'이 필수이며, 이를 위해 가상 파일 시스템(VFS)과 비동기 상태 관리가 요구됩니다.
- 자동 완성이나 참조 찾기와 같은 IDE 기능 뒤에는 코드를 읽는 것을 넘어 문맥과 관계를 실시간으로 추론하고 관리하는 고도의 기술이 숨어있습니다.
- LSP 구현은 상태 변화에 따른 동시성 문제와 데이터 일관성을 유지해야 하는 난제들로, 복잡한 아키텍처 설계가 핵심입니다.
LSP는 컴파일러와 달리, 프로젝트 전체에 대한 완전한 정보가 없더라도 사용자에게 가능한 한 즉각적으로 유용한 결과를 제공해야 하는 특성이 있습니다. 이 때문에 개발자는 모든 쿼리에 대해 완벽하게 준비될 때까지 기다릴 수 없습니다. 따라서 LSP 서버는 최소한의 유효한 작업만 수행하여 쿼리 처리를 가능하게 해야 하며, 초기에는 단순히 제공된 설정을 검증하고 응답하는 방식으로 시작합니다.
LSP가 직면하는 가장 큰 기술적 난제 중 하나는 상태 관리입니다. 프로토콜 자체는 파일 시스템을 다루지 않지만, 실제 분석 과정에서는 파일 시스템 접근이 필요하며 이를 동기화해야 합니다. 에디터 외부에서 발생할 수 있는 편집 이벤트까지 고려하면 프로젝트 소스를 메모리에 로드하여 가상 파일 시스템(VFS)으로 선언하고, 모든 변경 사항에 따라 일관된 내부 상태를 유지하는 것이 필수적입니다.
단순한 구문 분석을 넘어선 심층적인 기능들은 고도의 의미론적 분석 능력을 요구합니다. 예를 들어, 커서 위치의 타입을 파악하기 위해서는 단순히 코드를 읽는 것을 넘어 해당 항목이 어디에서 왔는지 추적하는 정의 맵(definition maps)과 아이템 트리를 구축해야 합니다. 특히 워크스페이스 전체에서 특정 함수가 사용된 모든 곳을 찾는 참조 기능은 이러한 복잡성을 극대화합니다.
이러한 복잡성 때문에 LSP 구현체들은 고도화된 아키텍처 패턴을 채택합니다. rust-analyzer와 같은 도구는 salsa라는 증분 데이터베이스를 활용하여, 입력과 출력 간의 관계를 정의하고 출력을 지연 계산 및 메모이제이션합니다. 이를 통해 전체 인덱싱 과정 없이도 상태 변화에 따라 필요한 부분만 재계산하는 방식으로 일관성을 유지할 수 있습니다.