Getting started

Explicit-only metadata import

.NET compilation preset

CompilationOptions.DotNet returns fresh immutable defaults for a .NET target whose semantic inputs are explicitly supplied references. Framework/package resolution remains with the caller or project tooling:

var options = CompilationOptions.DotNet
    .WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
var compilation = Compilation.Create("Example", syntaxTrees, references, options);

The preset uses new MetadataImportOptions(): discover the core defining System.Object from the supplied references, without adding host fallback paths. The .NET target uses that discovered core identity for emission as well. No framework version, installed SDK reference pack, or package is selected by this preset. Existing With... copies retain this policy. Host execution services are still separate and .NET-specific; this does not remove all host reflection from the emitter.

new MetadataImportOptions("Core.Name") retains the older named-core import mode, with independent emission selection. new CompilationOptions() retains host-assisted import. A null MetadataImportOptions still means host-assisted; a non-null options object with a null CoreAssemblyName means explicit-only core discovery. This is a transitional representation using the current import options, pending the agreed TargetPlatform/Contract API.

For discovered-core mode, an explicit TargetCoreAssemblyName, when supplied, must match the discovered core's simple name. Conflicting explicit EmitOptions core identities report RAVT003 before output is written. Missing-core setup is reported as RAVT004 by compilation diagnostic collection and emission; see the failure behavior below. An empty reference list never gains implicit host references through this preset.

Host runtime implementation paths

Targets.DotNetRuntimeAssemblyPathResolver owns the existing reference-to-runtime path heuristics used by host assembly registration, called through the .NET target. It handles NuGet ref/lib layouts, SDK packs/shared layouts, and recognized framework reference packages. This is host implementation lookup, not semantic reference resolution: its results are not added to an explicit-only target reference set.

Preserved policies include preferring a matching NuGet lib path, then descending path order among available package lib candidates; SDK-pack fallback uses the matching shared-framework version. Recognized framework reference packages first look for an installed exact version, then prefer stable numeric versions within the requested major, before falling back to package lib lookup. These are existing host lookup heuristics, not a new framework compatibility guarantee or cross-compilation policy.

The resolver accepts an internal shared-framework root override so path selection can be tested against temporary layouts without depending on the machine's installed frameworks. Production derives that root from the compiler host's runtime as before. DotNetHostRuntime owns host assembly loading and caches separately from this path policy. Reflection-to-semantic-symbol projection still has dependencies in Compilation.

Host assembly service ownership

Each DotNetCompilationTarget owns one DotNetHostRuntime. The service holds per-compilation metadata-to-runtime assembly associations, local path registrations, and local runtime assembly caches. Existing process-wide path/runtime caches and trusted-platform discovery move with it, retaining their current lifetime, initialization and lookup order. This extraction does not introduce deterministic unloading or change default AssemblyLoadContext behavior.

The service performs runtime assembly registration/loading, host type lookup and host emit-core discovery. The target injects this service directly into its loader; metadata import no longer calls back through Compilation for path or runtime assembly registration. Compilation's remaining runtime-type lookup adapters retain setup and argument checks. The service does not retain Compilation or semantic symbols; symbols supplied for host lookup are only used during that call. Compatible metadata sessions may still be shared independently, while each new compilation gets its own host service and symbol caches.

Host fallback paths remain available only under existing host-assisted import policy. Explicit-reference target definitions are not replaced by runtime implementations, even when those implementations are already in shared host caches. The compiler still exposes existing reflection core handles; replacing the target remains incomplete. ReflectionTypeLoader is now owned by the per-compilation .NET target as described below.

Metadata session and core ownership

DotNetCompilationTarget holds the selected metadata core, executable host core, and host emit core alongside its metadata session. Shared setup requests a semantic loader from the target after checking reference fingerprints; it no longer handles a .NET session directly. Existing Compilation core-assembly properties forward to the target without triggering setup. Target operations use their owning compilation for contract validation and emission.

Host handles are seeded before setup can reenter reflection services. Metadata initialization then selects the metadata core and registers its host mapping before importing symbols. This preserves setup ordering, initialization diagnostics and collection-based session lifetime. These remaining reflection-facing adapters still need attention before a second target can be selected.

Reflection-to-symbol projection ownership

Each DotNetCompilationTarget owns one lazy ReflectionTypeLoader, including its reflection type, metadata identity and method/type-parameter projection caches. The target passes that projector explicitly to DotNetSemanticDataLoader, which uses it for imported PE modules. Compilation's reflection entry points forward to the same projector; they no longer create or own an independent instance.

Projector allocation does not initialize the compilation or load references. This permits reflection queries before setup and allows metadata import to reuse the projector during same-thread setup reentrancy. Lazy initialization publishes one projector even when multiple callers request it concurrently. This does not introduce a broader concurrency guarantee for every projection operation.

The projector remains compilation-bound and is never stored in shared metadata sessions or host caches. Compatible snapshots may share a metadata session but retain distinct projectors and projected symbols. This is an ownership extraction; projection algorithms, nullability, generic substitution and the existing public Compilation.GetType(Type) surface remain unchanged. The .NET-specific projector is not added to the neutral semantic-data-loader interface.

Target initialization failures

Known option contradictions are checked first and reported as RAVT003, before reference loading. See configuration validation. A consistent configuration can still fail to establish its core as described below.

Compilation-wide, tree-scoped, and document-scoped diagnostic collection report RAVT004 when the .NET loader cannot establish its metadata core. This includes explicit-only discovery with no supplied core defining System.Object, and known I/O, access, invalid-image or type-load failures while opening the metadata core session. The diagnostic retains the configured core identity and underlying failure detail when available. It does not substitute host definitions.

Emission returns an unsuccessful EmitResult before declaration binding or writing to either PE or PDB output, even if the caller supplied precomputed diagnostics. RAVT004 remains a fatal error regardless of diagnostic suppression/severity settings: there is no initialized target against which compilation can continue. Diagnostic collection stops at this failure; it does not claim a complete set of semantic diagnostics. Syntax-only diagnostics require no target initialization.

Failed setup is not marked complete or reused by another snapshot. A new compilation with corrected references can initialize normally; repeated or concurrent diagnostic requests on the invalid compilation remain failures. Failures are not cached as successful sessions. Cancellation and unrelated compiler exceptions are not translated into RAVT004.

This boundary covers core-session establishment, not every possible later import failure. Direct semantic queries still require a valid target and can throw on initialization failure. No partial semantic environment or error-symbol core is introduced. Further loader failures and earlier option validation remain follow-up work.

Implementation boundary

The .NET implementation materializes resolver inputs in an immutable Metadata.DotNetMetadataReferenceSet. It owns path normalization, identity deduplication, and ordered candidate selection. Entries contain identity strings and paths, not mutable reflection identities or a snapshot of assembly bytes. DotNetMetadataContextFactory owns the stream-backed resolver, context construction, and portable assembly identity reads. DotNetMetadataSession owns the resulting context and path/identity loading, including the existing path-to-identity fallback. DotNetSemanticDataLoader selects the reference paths and metadata core and creates fresh sessions; DotNetCompilationTarget supplies explicit reference and import-option inputs to session setup and owns the resulting session and any reuse candidate. Compilation gates incremental session reuse using import options and portable-reference fingerprints. The target adopts only the previous session, never the previous compilation, target, projector or host service. Reference import passes through ISemanticDataLoader; DotNetSemanticDataLoader owns loaded-assembly and assembly-symbol caches, dependency traversal, and construction of PE assembly/module symbols for each compilation. Its dependencies are the metadata session, reflection projector and host service. The projector still binds it indirectly to the owning compilation; this does not make the loader reusable across snapshots. Compilation-to-compilation references remain in the shared compilation layer. The .NET loader explicitly registers selected paths through the .NET host service before creating a new session. Metadata construction no longer accepts a host-registration callback. The resolver keeps the first sorted simple-name candidate; host registration still visits all selected entries in order and retains the last path for that name. These remain separate compatibility policies.

These are incremental extractions described in the target boundary plan. The factory is .NET-specific and returns MetadataLoadContext; it is not yet a platform-neutral provider. Public APIs, reference precedence, fallback policy, configuration errors, and context lifetime are unchanged. Compatible snapshots share a session without retaining the previous compilation. A session keeps no compilation, symbol cache, or registration callback. Individual compilations do not dispose its context because other snapshots can still use it; the existing collection-based lifetime is retained. Deterministic shared-session disposal is not introduced by this extraction.

The loader interface returns semantic symbols rather than reflection assemblies. It currently uses the existing MetadataReference/IAssemblySymbol reference surface; these shapes can be redesigned for neoCLR as needed. Selection is still fixed to .NET through the internal DotNetCompilationTarget, which composes the loader with its runtime contract and existing code generator. Core-library discovery is loader-owned, but compilation still holds reflection core handles for its existing runtime/emission services. Reflection type projection and host runtime registration also retain .NET dependencies. This is an import boundary, not a claim that another target can already replace all semantic data loading. The goal is to select loader, platform/runtime contract, and codegen together for .NET and later neoCLR; independently mixed targets and cross-compilation are out of current scope.

Imported assembly discovery

Shared assembly discovery in CompilationSymbolLookup uses the internal IImportedAssemblySymbol contract for simple-name/arity lookup and extension conversion container discovery. The .NET implementation remains PEAssemblySymbol; its reflection metadata index is private to that implementation. The contract returns Raven symbols and exposes no reflection objects or cache-control APIs.

Simple-name lookup still prefers matching source declarations, then the first matching imported assembly/type. Nested types participate by their own name and arity. Extension conversion discovery returns candidate containers; binding continues to decide applicability. Public IAssemblySymbol and semantic-model APIs are unchanged in this slice, but they can be redesigned under ADR-0003. Symbol creation and emitter reflection access remain boundaries to extract.

Namespace extension discovery uses INamespaceExtensionLookup, a capability implemented by PE namespaces and composed by merged namespaces. It does not classify a namespace as purely imported: merged namespaces can include source declarations and multiple providers. Shared lookup and merged discovery no longer dispatch on concrete PE namespace types. General namespace/type traversal remains available for namespaces without the capability; source extension traversal and receiver applicability are still separate compiler responsibilities.

These contracts are intermediate steps toward target-owned metadata and symbols, not a fixed plugin ABI. The runtime/platform contract governs semantic rules, the type environment, representations, and supported features. One or more symbol sources supply that environment; one or more compatible code generators implement it. Sources need not use metadata files or CLI assemblies. Changing contract can require rebuilding imported symbols and rebinding; unsupported features or incompatible source/backend combinations require diagnostics. These selection and validation APIs remain future work.

Existing resolver compatibility rules

The .NET resolver keeps the first supplied path for each full assembly identity, then sorts the remaining paths case-insensitively. Resolution prefers an exact identity match and otherwise uses the first simple-name match in that sorted set. This fallback is not strict version admission. Invalid, missing, or non-managed candidates are skipped while constructing the resolver; this does not guarantee that directly importing those references will succeed.

Session path loading first reads the requested file. If it fails and a fallback identity was supplied, the session attempts identity resolution. Without that identity, missing-file and invalid-image failures propagate. The resolver itself does not discover host assemblies outside its supplied paths; host-assisted discovery is performed by the .NET loader through the target host service before constructing the session.

DotNetMetadataResolutionTests characterizes these compatibility rules using isolated CLI fixtures. Future strict target admission needs a separate contract and diagnostics rather than an incidental change to these fallback rules.

Configuration

CompilationOptions.WithMetadataImportOptions(new MetadataImportOptions("System.Runtime")) selects a metadata core assembly and imports dependencies only from the compilation's supplied PE references. Include the core and all required transitive dependencies. For example, when supplying a .NET reference pack, System.Runtime contains the core metadata definitions. Omitting Console then makes Console unavailable even if it exists in the compiler host. A missing/unusable core cannot initialize MetadataLoadContext and is a configuration error; this option does not synthesize a substitute core library.

The default is null, preserving host-assisted .NET reference discovery. Set the option to null to return to that mode. Optional-copy methods preserve the policy, and changing it prevents incremental metadata/semantic state transfer from the prior compilation.

This is a compiler API building block for restricted targets, not a complete target framework, CLI switch, or sandbox for executing compiler extensions. It does not change host execution, framework projections, runtime reflection used by the emitter, or EmitOptions.TargetCoreLibraryIdentity. Metadata assembly lookup retains Raven's existing identity/simple-name matching within the explicit input set; exact identity admission and full dependency validation belong to a target profile.

.NET's MetadataLoadContext uses a supplied resolver rather than imposing host lookup: Microsoft documentation. Raven's default seeds that resolver with host assemblies for compatibility. Explicit-only mode omits this seeding and names the metadata core separately from the host core. This uses the existing .NET mechanism; it costs callers an explicit complete reference set. Host-side emitter services remain unchanged so this slice does not promise independent core-library emission or execution on another runtime.

Compiler API consumers must rebuild: the optional constructor argument preserves existing source calls, but changes the constructor's binary signature in this preview.

Target-only types during emission

When EmitOptions.TargetCoreLibraryIdentity is specified, persisted emission can use named metadata types and closed constructions of metadata types directly. Target-only reference assemblies need not be loaded as executable assemblies into the compiler host. Generic member references retain the definition's generic parameters even when the declaring type is constructed; metadata proxies retain ref/out/in addressing. Retargeted constructors use the same temporary-token approach as methods: the final PE references the target constructor and retains its definition signature. Temporary constructor types are removed before writing the artifact. This also avoids asking Reflection.Emit to encode modified generic types from MetadataLoadContext directly. Default emission and the separate metadata-import policy remain unchanged.

This does not claim complete cross-target emission: mixed source/metadata generic constructions, generic methods, and the entire framework surface need further coverage. A successful emit is not runtime validation. Consumers should check dependency closure and execute against their actual target. No language syntax or editor API changed.

Project-backed editor imports

RavenMetadataCoreAssemblyName opts an MSBuild Raven project into the same explicit-only metadata policy. Supply the named core and its dependencies as project references:

<PropertyGroup>
  <RavenMetadataCoreAssemblyName>Target.Core</RavenMetadataCoreAssemblyName>
  <ImplicitImports>disable</ImplicitImports>
  <RavenFrameworkProjections>None</RavenFrameworkProjections>
</PropertyGroup>
<ItemGroup>
  <Reference Include="Target.Core"><HintPath>refs/Target.Core.dll</HintPath></Reference>
</ItemGroup>

The project evaluator disables automatic host framework references when this property is set, even if RavenUseHostFrameworkReferences is true. Explicit reference/package items still belong to the project's supplied input set; callers must supply the correct artifacts. The language server also stops automatically adding host Raven.Core, Raven.Macros and their support references. Missing target references remain missing; there is no silent fallback to the host library. Leaving the property unset preserves existing project behavior.

This carries the existing compiler policy across project loading and the language server. It is not an SDK target, a retargeted emit setting, or a new build pipeline. In particular, naming a neoCLR core does not make ordinary dotnet build execute on neoCLR. A target-specific emitter/importer is still required. The configured project provides target-aware completion using the same semantic APIs as other Raven projects.

Explicit metadata-core projects also omit the automatically generated .NET TargetFrameworkAttribute source. Their host/tooling TFM does not establish a guest framework identity or guarantee that System.Runtime.Versioning exists. Target authors may supply their own assembly attributes when supported by their reference surface.

Project-owned emission core

CompilationOptions.WithTargetCoreAssemblyName("Target.Core") selects the emission core from the assembly already resolved by explicit metadata import. Configure both from an evaluated project (or an imported target-pack .props file):

<PropertyGroup>
  <RavenMetadataCoreAssemblyName>Target.Core</RavenMetadataCoreAssemblyName>
  <RavenTargetCoreAssemblyName>Target.Core</RavenTargetCoreAssemblyName>
</PropertyGroup>

With this opt-in, ordinary Compilation.Emit and rvnc project.rvnproj use that core identity without an external runner constructing EmitOptions. RAVT003 rejects inconsistent import/emission selection and conflicting explicit EmitOptions before writing the assembly. Missing or unusable core references report RAVT004 when core-session establishment fails during diagnostic collection or emission. Named-core import-only callers retain the old behavior when the emission setting is absent. Discovered-core mode, including CompilationOptions.DotNet, selects emission automatically. Legacy default .NET emission and explicit --target-core-library use without a project selection remain available.

Optional copies preserve the selection; incremental semantic reuse accounts for changes. The existing public CompilationOptions constructor signature is retained. Editor and compiler configuration diagnostics come from the same compilation rather than an LSP special case. A new compiler is required to interpret the new project property.

Explicit metadata projects also take precedence over a project-system service's host framework override. Automatic compiler-support references are omitted; project/package references remain explicit inputs. The command-line driver does not add host frameworks, Raven.Core, Raven.Macros or Raven.CodeAnalysis back after project evaluation. It keeps the project's embedded core-shim and runtime-async defaults instead of deriving them from the compiler host. Explicit command-line reference and runtime-async selections remain explicit inputs; this is not a sandbox for compiler plugins.

This closes a CLI/editor inconsistency while retaining .NET's separation between reference metadata and executable code. It does not implement a complete target-pack schema, binary loading in another runtime, or removal of every downstream adapter. The neoCLR experiment independently verifies emitted dependency closure and execution; Raven retains normal metadata/CIL emission rather than a neoCLR-specific backend.

Assembly ownership and qualified expressions (2026-10-08)

IAssemblySymbol.GetTypeByMetadataName on a source assembly now searches that assembly's declarations only. It no longer returns a referenced CLI type as if it were source-owned. Call Compilation.GetTypeByMetadataName when reference lookup is intended. This is the same ownership distinction exposed by .NET/Roslyn's assembly symbol API and compilation symbol APIs; no metadata representation changes. Closure frames resolve their Object base through the owning compilation's special-type contract, so they retain the selected framework or native Object provider.

Namespace-qualified expression types use the compilation's existing source-first, metadata-affinity and explicit target-provider selection instead of the first matching member of an unordered namespace fragment. Type arity remains part of the metadata name. This fixes inconsistent qualified calls when references contain competing declarations, without introducing a neoCLR-specific preference into ordinary .NET lookup. The cost is a compilation-level lookup, with its existing cache; this change makes no performance claim. Reference ambiguity policy remains Raven's existing policy, not a new claim of complete Roslyn parity.

Regression coverage uses two ordinary CLI libraries with competing Shared.Api declarations and both reference orders. It checks source-assembly ownership, compilation selection, diagnostics and semantic identity for qualified calls.

Namespace import lookup applies the same selection to named type candidates before member binding and deduplication. import Shared.* therefore agrees with qualified Shared.Api and explicit import Shared.Api calls when metadata declarations compete. Namespace scopes, aliases, type-scope static members and accessibility checks retain their existing roles. A missing configured provider does not fall back to the original namespace candidate. The regression covers all three forms and both reference orders.