Skip to content

Make file-based apps multi-language #55716

Description

@T-Gro

dotnet run file.cs runs a single C# file with no project. This asks to make the file-based app pipeline language-agnostic:

  • dotnet run app.fs runs a single F# file, the same way dotnet run app.cs runs a C# one.
  • #:ref and #:project can cross languages, in both directions.

The language side is already done: C# ignores #: (C# 14), and F# just merged the same in dotnet/fsharp#20212 (RFC-1337). VB and other languages can opt in later, and the design must not block them.

Motivation

A big ask from the F# community is C# source generators. Today F# users build an intermediate C# project only to run a generator, for example [GeneratedRegex]. A web of single-file components removes that extra project and the need to maintain it.

Other technologies and techniques fit one language better than another, for example low-level programming in C# and domain modelling with types in F#. Needing nothing more than a new file would greatly improve the multi-language experience of .NET.

This proposal of multi-language file graphs supports a smooth experience: pick the best language for each task, while staying on a COMMON language runtime and SDK.

Core requirement: a language-aware #:ref graph

Today #:ref builds one virtual project per referenced file, follows transitive references, and already rejects cycles. Keep that model and make it language-aware:

  • each file's virtual project uses the language of its extension,
  • #:ref and #:project resolve across languages, in both directions,
  • a reference cycle stays an error.

One project per file is already single-language, so no compaction is needed for correctness.

Example: a web of .cs and .fs

TopLevelEntryPoint.cs     (C#) -> #:ref BusinessLogic.fs
BusinessLogic.fs          (F#) -> #:ref DomainTypes.fs, SourceGeneratedRegexes.cs
SourceGeneratedRegexes.cs (C#)                          // uses [GeneratedRegex], C#-only
DomainTypes.fs            (F#)

BusinessLogic.fs needs a source-generated regex, which only C# offers, so it references SourceGeneratedRegexes.cs. The entry point is C# and uses BusinessLogic.fs. Each file becomes its own single-language virtual project.

Requested scope

  • SDK/CLI: pick the language from the file extension for both the entry point and each referenced file. Build the language-aware #:ref graph.
  • Editor (Roslyn LSP + F# tooling): a file-based app resolves to a set of cross-language virtual projects, so IntelliSense, go-to-definition, and diagnostics work across a cross-language #:ref.
  • Debugger: attach and debug a file-based app whose graph spans languages.

Out of scope

A VB language change (later).

Open questions

  • How a reference cycle is reported to the CLI and the editor.
Bonus: potentially compacting the graph

One project per file works but multiplies projects and builds. As an optional optimization, the SDK could emit fewer projects, as long as each stays single-language and the reference graph stays acyclic. This is not "one project per language".

Take the example above. The fewest projects is 3, because the two C# files cannot share one:

Project Lang Files Depends on
A C# SourceGeneratedRegexes.cs
B F# DomainTypes.fs, BusinessLogic.fs A
C C# TopLevelEntryPoint.cs B

SourceGeneratedRegexes.cs and TopLevelEntryPoint.cs are both C#, but BusinessLogic.fs (F#) sits between them (C → B → A). Merging the two C# files would form a cycle with the F# project. So C# splits into two projects, while the two F# files merge into one. The algorithm, its minimality target, and a deterministic tie-break stay open.

Remark: replace hard-coded C# rules with pluggable components

The SDK should stop hard-coding C#. Abstract the C#-specific rules behind separated interfaces, with one language-specific handler per language, for (a) entry-point/run selection, (b) virtual-project construction, and (c) directive parsing/translation. The core stays language-neutral and drives the graph, the compaction, and the build, so new languages need no changes to shared code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-run-fileItems related to the "dotnet run <file>" effort

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions