Skip to content

Commit b7ef957

Browse files
committed
Switch import syntax to path-as-alias and align LSP alias display
1 parent bb442e3 commit b7ef957

19 files changed

Lines changed: 549 additions & 150 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ All notable changes to FScript are documented in this file.
55
## [Unreleased]
66

77
- Fixed LSP completion insertion for dotted prefixes so selecting `Option.map` after `Option.` no longer duplicates the qualifier.
8+
- Switched import syntax to `import "path.fss" as Alias` and removed `from` import grammar.
9+
- Updated LSP type display/navigation to hide internal import prefixes and use source aliases (for example `Common.ProjectInfo`).
10+
- Renamed sample `includes-and-exports.fss` to `imports-and-exports.fss` and updated docs links.
811

912
## [0.34.0]
1013

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Useful samples:
8888
- [`samples/patterns-and-collections.fss`](samples/patterns-and-collections.fss)
8989
- [`samples/tree.fss`](samples/tree.fss)
9090
- [`samples/mutual-recursion.fss`](samples/mutual-recursion.fss)
91-
- [`samples/includes-and-exports.fss`](samples/includes-and-exports.fss)
91+
- [`samples/imports-and-exports.fss`](samples/imports-and-exports.fss)
9292

9393
## Interpreter Architecture
9494

docs/guides/getting-started-tutorial.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ You can split scripts using `import`.
330330
`main.fss`:
331331

332332
```fsharp
333-
import "shared/math.fss"
334-
print $"{math.sum 20 22}"
333+
import "shared/math.fss" as Math
334+
print $"{Math.sum 20 22}"
335335
```
336336

337337
`shared/math.fss`:
@@ -343,7 +343,7 @@ let sum a b = a + b
343343
Notes:
344344
- imported files are `.fss`,
345345
- import cycles are fatal,
346-
- imported files are namespaced by filename stem (`math.sum`, `common.join`, ...).
346+
- imported symbols must be accessed through the explicit alias (`Math.sum`, `Common.join`, ...).
347347

348348
## 11. Hosting, exports, and sandboxing (advanced)
349349
FScript is designed to be embedded.
@@ -379,7 +379,7 @@ When embedding, keep this mindset:
379379
- [`samples/types-showcase.fss`](../../samples/types-showcase.fss)
380380
- [`samples/patterns-and-collections.fss`](../../samples/patterns-and-collections.fss)
381381
- [`samples/map-matching.fss`](../../samples/map-matching.fss)
382-
- [`samples/includes-and-exports.fss`](../../samples/includes-and-exports.fss)
382+
- [`samples/imports-and-exports.fss`](../../samples/imports-and-exports.fss)
383383
- Specifications index: [`docs/specs/README.md`](../specs/README.md)
384384
- Architecture index: [`docs/architecture/README.md`](../architecture/README.md)
385385
- [`samples/`](../../samples/)

docs/specs/syntax-and-indentation.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This document describes the concrete syntax accepted by the interpreter and the
66
## File and program structure
77
- A program is a sequence of top-level statements.
88
- Top-level statements are:
9-
- `import "relative/path/file.fss"` directives
9+
- `import "relative/path/file.fss" as Alias` directives
1010
- `type` / `type rec` declarations
1111
- `let` / `let rec` bindings
1212
- `[<export>] let` / `[<export>] let rec` bindings
@@ -206,16 +206,16 @@ All of the following map literal layouts are valid:
206206
- Inline record annotation fields are `;`-separated in single-line form.
207207

208208
## Import directive
209-
- Import uses preprocessor-style syntax:
210-
- `import "shared/helpers.fss"`
209+
- Import uses explicit alias syntax:
210+
- `import "shared/helpers.fss" as Helpers`
211211
- Imports are top-level only.
212-
- Imported files are wrapped in an implicit module derived from the imported file name.
213-
- `import "shared/helpers.fss"` exposes symbols under `helpers.*`.
212+
- Imported symbols are available only through the alias selected by the importer.
213+
- `import "shared/helpers.fss" as Helpers` exposes symbols under `Helpers.*`.
214214
- Import loading is recursive.
215215
- Cycles are fatal and reported as parse errors.
216216
- File paths in imports must be `.fss`.
217217
- Import resolution is file-relative:
218-
- `import "x.fss"` resolves from the directory of the file containing the directive.
218+
- `import "x.fss" as X` resolves from the directory of the file containing the directive.
219219
- Paths are normalized before loading:
220220
- relative segments like `.` and `..` are collapsed.
221221
- Resolved files must stay inside the configured sandbox root (`RootDirectory`).

samples/imports-and-exports.fss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import "includes/common.fss" as Common
2+
3+
[<export>] let extension_name = "demo"
4+
5+
[<export>] let summary (project: Common.ProjectInfo) =
6+
let steps = [ "build"; "test"; "publish" ] |> List.map Common.describe_command
7+
$"{Common.describe_project project}: {Common.join_with_comma steps}"
8+
9+
let main =
10+
let project = { Name = "Terrabuild"; Language = "F#" }
11+
print (summary project)
12+
print $"Extension: {extension_name}"
13+
14+
main

samples/includes-and-exports.fss

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/FScript.CSharpInterop/LanguageServer/LspHandlers.fs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ module LspHandlers =
266266
else
267267
typeText
268268

269+
let private displayTypeName (doc: DocumentState) (name: string) =
270+
if String.IsNullOrWhiteSpace(name) then name
271+
else
272+
let parts = name.Split('.') |> Array.toList
273+
match parts with
274+
| head :: tail when doc.ImportInternalToAlias.ContainsKey(head) ->
275+
let alias = doc.ImportInternalToAlias[head]
276+
match tail with
277+
| [] -> alias
278+
| _ -> alias + "." + (String.concat "." tail)
279+
| _ -> name
280+
269281
let private formatFunctionSignature (doc: DocumentState) (sym: TopLevelSymbol) =
270282
let paramNames = doc.FunctionParameters |> Map.tryFind sym.Name |> Option.defaultValue []
271283

@@ -276,7 +288,7 @@ module LspHandlers =
276288
let effectiveParts =
277289
match sym.TypeTargetName with
278290
| Some returnName when parts.Length > 0 ->
279-
(parts |> List.take (parts.Length - 1)) @ [ returnName ]
291+
(parts |> List.take (parts.Length - 1)) @ [ displayTypeName doc returnName ]
280292
| _ -> parts
281293
let arrowText =
282294
if effectiveParts.Length = (paramNames.Length + 1) then
@@ -298,6 +310,7 @@ module LspHandlers =
298310
doc.FunctionDeclaredReturnTargets
299311
|> Map.tryFind sym.Name
300312
|> Option.defaultValue "unknown"
313+
|> displayTypeName doc
301314
let parts = annotated @ [ returnType ]
302315
let arrowText =
303316
if parts.Length = (paramNames.Length + 1) then

src/FScript.CSharpInterop/LanguageServer/LspModel.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ module LspModel =
7474
InjectedFunctionSignatures: Map<string, string>
7575
InjectedFunctionParameterNames: Map<string, string list>
7676
InjectedFunctionDefinitions: Map<string, (string * Span)>
77+
ImportAliasToInternal: Map<string, string>
78+
ImportInternalToAlias: Map<string, string>
7779
// Variable occurrences keyed by identifier, sourced from AST spans.
7880
// This avoids text-based false positives (for example record field labels).
7981
VariableOccurrences: Map<string, Span list> }

src/FScript.CSharpInterop/LanguageServer/LspSymbols.fs

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace FScript.LanguageServer
33
open System
44
open System.Collections.Generic
55
open System.IO
6+
open System.Text.RegularExpressions
67
open System.Text.Json.Nodes
78
open FScript.Language
89
open FScript.CSharpInterop
@@ -146,6 +147,71 @@ module LspSymbols =
146147
|> String.concat "; "
147148
|> sprintf "{| %s |}"
148149

150+
let private tryBuildImportAliasMaps (sourcePath: string) (text: string) : Map<string, string> * Map<string, string> =
151+
try
152+
let parsed = Parser.parseProgramWithSourceName (Some sourcePath) text
153+
let imports =
154+
parsed
155+
|> List.takeWhile (function | SImport _ -> true | _ -> false)
156+
|> List.choose (function | SImport(alias, importPath, _) -> Some (alias, importPath) | _ -> None)
157+
158+
let sourceDirectory =
159+
try
160+
match Path.GetDirectoryName(sourcePath) with
161+
| null
162+
| "" -> Directory.GetCurrentDirectory()
163+
| dir -> dir
164+
with _ ->
165+
Directory.GetCurrentDirectory()
166+
167+
let pathToPrefix = Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
168+
let mutable prefixCounter = 0
169+
let mutable aliasToInternal = Map.empty<string, string>
170+
let mutable internalToAlias = Map.empty<string, string>
171+
172+
for (alias, importPath) in imports do
173+
let resolvedPath =
174+
if Path.IsPathRooted(importPath) then
175+
Path.GetFullPath(importPath)
176+
else
177+
Path.GetFullPath(Path.Combine(sourceDirectory, importPath))
178+
179+
let internalPrefix =
180+
match pathToPrefix.TryGetValue(resolvedPath) with
181+
| true, existing -> existing
182+
| false, _ ->
183+
prefixCounter <- prefixCounter + 1
184+
let created = $"__imp{prefixCounter}"
185+
pathToPrefix[resolvedPath] <- created
186+
created
187+
188+
aliasToInternal <- aliasToInternal |> Map.add alias internalPrefix
189+
if not (internalToAlias.ContainsKey(internalPrefix)) then
190+
internalToAlias <- internalToAlias |> Map.add internalPrefix alias
191+
192+
aliasToInternal, internalToAlias
193+
with _ ->
194+
Map.empty, Map.empty
195+
196+
let private mapInternalQualifiedName (internalToAlias: Map<string, string>) (name: string) =
197+
if String.IsNullOrWhiteSpace(name) then name
198+
else
199+
match name.Split('.') |> Array.toList with
200+
| head :: tail when internalToAlias.ContainsKey(head) ->
201+
let alias = internalToAlias[head]
202+
match tail with
203+
| [] -> alias
204+
| _ -> alias + "." + (String.concat "." tail)
205+
| _ -> name
206+
207+
let private mapTypeTextForDisplay (internalToAlias: Map<string, string>) (typeText: string) =
208+
if Map.isEmpty internalToAlias then typeText
209+
else
210+
internalToAlias
211+
|> Map.fold (fun current internalPrefix alias ->
212+
let pattern = @"\b" + Regex.Escape(internalPrefix) + @"\b"
213+
Regex.Replace(current, pattern, alias)) typeText
214+
149215
let private canonicalRecordSignatureFromFields (fields: (string * string) list) =
150216
fields
151217
|> List.sortBy fst
@@ -1395,6 +1461,11 @@ module LspSymbols =
13951461
Uri(uri).LocalPath
13961462
else
13971463
uri
1464+
let importAliasToInternal, importInternalToAlias =
1465+
if uri.StartsWith("file://", StringComparison.OrdinalIgnoreCase) then
1466+
tryBuildImportAliasMaps sourceName text
1467+
else
1468+
Map.empty, Map.empty
13981469
let runtimeExterns = LspRuntimeExterns.forSourcePath sourceName
13991470

14001471
let diagnostics = ResizeArray<JsonNode>()
@@ -1511,6 +1582,39 @@ module LspSymbols =
15111582
| ParseException err ->
15121583
diagnostics.Add(diagnostic 1 "parse" err.Span err.Message)
15131584

1585+
let displayTypeText (typeText: string) = mapTypeTextForDisplay importInternalToAlias typeText
1586+
1587+
let symbols =
1588+
symbols
1589+
|> List.map (fun sym ->
1590+
{ sym with
1591+
TypeText = sym.TypeText |> Option.map displayTypeText })
1592+
1593+
let recordParamFields =
1594+
recordParamFields
1595+
|> Map.map (fun _ fields ->
1596+
fields |> List.map (fun (fieldName, fieldType) -> fieldName, displayTypeText fieldType))
1597+
1598+
let functionAnnotationTypes =
1599+
functionAnnotationTypes
1600+
|> Map.map (fun _ typeTexts -> typeTexts |> List.map displayTypeText)
1601+
1602+
let parameterTypeHints =
1603+
parameterTypeHints
1604+
|> List.map (fun (span, label) -> span, displayTypeText label)
1605+
1606+
let functionReturnTypeHints =
1607+
functionReturnTypeHints
1608+
|> List.map (fun (span, label) -> span, displayTypeText label)
1609+
1610+
let patternTypeHints =
1611+
patternTypeHints
1612+
|> List.map (fun (span, label) -> span, displayTypeText label)
1613+
1614+
let localVariableTypeHints =
1615+
localVariableTypeHints
1616+
|> List.map (fun (span, name, typeText) -> span, name, displayTypeText typeText)
1617+
15141618
documents[uri] <-
15151619
{ SourcePath = sourceName
15161620
Text = text
@@ -1529,6 +1633,8 @@ module LspSymbols =
15291633
InjectedFunctionSignatures = injectedFunctionSignatures
15301634
InjectedFunctionParameterNames = injectedFunctionParameterNames
15311635
InjectedFunctionDefinitions = injectedFunctionDefinitions
1636+
ImportAliasToInternal = importAliasToInternal
1637+
ImportInternalToAlias = importInternalToAlias
15321638
VariableOccurrences = occurrences }
15331639
publishDiagnostics uri (diagnostics |> Seq.toList)
15341640

@@ -1853,7 +1959,12 @@ module LspSymbols =
18531959
let idx = word.LastIndexOf('.')
18541960
if idx > 0 then
18551961
let qualifier = word.Substring(0, idx)
1856-
tryResolveTypeNameForQualifier doc qualifier
1962+
let memberName = word.Substring(idx + 1)
1963+
match doc.ImportAliasToInternal |> Map.tryFind qualifier with
1964+
| Some internalPrefix when not (String.IsNullOrWhiteSpace(memberName)) ->
1965+
Some $"{internalPrefix}.{memberName}"
1966+
| _ ->
1967+
tryResolveTypeNameForQualifier doc qualifier
18571968
else
18581969
None
18591970
| Some word ->

src/FScript.Language/Ast.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ and MapEntry =
8282

8383
and Stmt =
8484
| SType of TypeDef
85-
| SImport of string * Span
85+
| SImport of string * string * Span
8686
| SLet of string * Param list * Expr * bool * bool * Span
8787
| SLetRecGroup of (string * Param list * Expr * Span) list * bool * Span
8888
| SExpr of Expr

0 commit comments

Comments
 (0)