// Copyright (c) 2026 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.BamlDecompiler
{
///
/// An artificial stand-in for one of the well-known BAML assemblies (e.g. PresentationFramework)
/// when the real assembly cannot be resolved - for instance when a WPF binary is inspected on a
/// machine that has no WPF installed (Linux/macOS).
///
/// It mirrors : it materializes type definitions on request but carries
/// no members. Only the types the BAML decompiler explicitly seeds via
/// are materialized; every other lookup returns null, so references to non-well-known types
/// keep decomposing to UnknownType exactly as they do when the assembly is entirely absent.
///
///
sealed class SyntheticWpfModule : IModule
{
///
/// Creates a module reference that resolves to a standing in
/// for . When is
/// non-null, the synthesized assembly exposes an XmlnsDefinitionAttribute mapping every
/// seeded CLR namespace to that XML namespace, so known WPF types serialize with the clean
/// presentation xmlns instead of a clr-namespace fallback.
///
public static IModuleReference CreateReference(IAssemblyReference assemblyName, string presentationXmlnsNamespace)
{
return new SyntheticModuleReference(assemblyName, presentationXmlnsNamespace);
}
public ICompilation Compilation { get; }
readonly IAssemblyReference assemblyName;
readonly string presentationXmlnsNamespace;
readonly Dictionary typeDefinitions = new();
readonly SyntheticNamespace rootNamespace;
IReadOnlyList assemblyAttributes;
SyntheticWpfModule(ICompilation compilation, IAssemblyReference assemblyName, string presentationXmlnsNamespace)
{
this.Compilation = compilation;
this.assemblyName = assemblyName;
this.presentationXmlnsNamespace = presentationXmlnsNamespace;
this.rootNamespace = new SyntheticNamespace(this, null, string.Empty, string.Empty);
}
///
/// Seeds this module with a synthetic definition for the given type and returns it.
/// Repeated calls for the same type return the cached instance. This is the only way a type
/// becomes visible on the module; plain
/// lookups never create new types.
///
public ITypeDefinition RegisterType(string ns, string name)
{
var typeName = new TopLevelTypeName(ns, name);
if (!typeDefinitions.TryGetValue(typeName, out var typeDef))
{
typeDef = new SyntheticTypeDefinition(this, typeName);
typeDefinitions.Add(typeName, typeDef);
// Invalidate the cached xmlns attributes; a namespace may have appeared.
assemblyAttributes = null;
}
return typeDef;
}
bool IModule.IsMainModule => Compilation.MainModule == this;
string IModule.AssemblyName => assemblyName.Name;
Version IModule.AssemblyVersion => assemblyName.Version;
string IModule.FullAssemblyName => assemblyName.FullName;
string ISymbol.Name => assemblyName.Name;
SymbolKind ISymbol.SymbolKind => SymbolKind.Module;
ICSharpCode.Decompiler.Metadata.MetadataFile IModule.MetadataFile => null;
INamespace IModule.RootNamespace => rootNamespace;
public IEnumerable TopLevelTypeDefinitions => typeDefinitions.Values;
public IEnumerable TypeDefinitions => typeDefinitions.Values;
public ITypeDefinition GetTypeDefinition(TopLevelTypeName topLevelTypeName)
{
return typeDefinitions.TryGetValue(topLevelTypeName, out var typeDef) ? typeDef : null;
}
IEnumerable IModule.GetAssemblyAttributes() => GetAssemblyAttributes();
IEnumerable IModule.GetModuleAttributes() => EmptyList.Instance;
bool IModule.InternalsVisibleTo(IModule module) => module == this;
IReadOnlyList GetAssemblyAttributes()
{
if (presentationXmlnsNamespace == null)
return EmptyList.Instance;
var attributes = assemblyAttributes;
if (attributes != null)
return attributes;
// Reconstruct the XmlnsDefinitionAttribute set that the real WPF assembly would carry:
// one entry per seeded CLR namespace, all mapping to the presentation XML namespace.
var attributeType = new SyntheticTypeDefinition(this,
new TopLevelTypeName("System.Windows.Markup", "XmlnsDefinitionAttribute"));
var stringType = Compilation.FindType(KnownTypeCode.String);
var namespaces = typeDefinitions.Keys.Select(t => t.Namespace).Distinct();
var list = new List();
foreach (var ns in namespaces)
{
var fixedArguments = ImmutableArray.Create(
new CustomAttributeTypedArgument(stringType, presentationXmlnsNamespace),
new CustomAttributeTypedArgument(stringType, ns));
list.Add(new DefaultAttribute(attributeType, fixedArguments,
ImmutableArray>.Empty));
}
attributes = list;
assemblyAttributes = attributes;
return attributes;
}
sealed class SyntheticModuleReference : IModuleReference
{
readonly IAssemblyReference assemblyName;
readonly string presentationXmlnsNamespace;
public SyntheticModuleReference(IAssemblyReference assemblyName, string presentationXmlnsNamespace)
{
this.assemblyName = assemblyName;
this.presentationXmlnsNamespace = presentationXmlnsNamespace;
}
IModule IModuleReference.Resolve(ITypeResolveContext context)
{
return new SyntheticWpfModule(context.Compilation, assemblyName, presentationXmlnsNamespace);
}
}
sealed class SyntheticNamespace : INamespace
{
readonly SyntheticWpfModule module;
public INamespace ParentNamespace { get; }
public string FullName { get; }
public string Name { get; }
public SyntheticNamespace(SyntheticWpfModule module, INamespace parentNamespace, string fullName, string name)
{
this.module = module;
this.ParentNamespace = parentNamespace;
this.FullName = fullName;
this.Name = name;
}
string INamespace.ExternAlias => string.Empty;
IEnumerable INamespace.ChildNamespaces => EmptyList.Instance;
IEnumerable INamespace.Types => module.TopLevelTypeDefinitions.Where(td => td.Namespace == FullName);
IEnumerable INamespace.ContributingModules => new[] { module };
SymbolKind ISymbol.SymbolKind => SymbolKind.Namespace;
ICompilation ICompilationProvider.Compilation => module.Compilation;
INamespace INamespace.GetChildNamespace(string name) => null;
ITypeDefinition INamespace.GetTypeDefinition(string name, int typeParameterCount)
{
if (typeParameterCount != 0)
return null;
return module.GetTypeDefinition(new TopLevelTypeName(FullName, name));
}
}
sealed class SyntheticTypeDefinition : ITypeDefinition
{
readonly SyntheticWpfModule module;
readonly TopLevelTypeName typeName;
public SyntheticTypeDefinition(SyntheticWpfModule module, TopLevelTypeName typeName)
{
this.module = module;
this.typeName = typeName;
}
IReadOnlyList ITypeDefinition.NestedTypes => EmptyList.Instance;
IReadOnlyList ITypeDefinition.Members => EmptyList.Instance;
IEnumerable ITypeDefinition.Fields => EmptyList.Instance;
IEnumerable ITypeDefinition.Methods => EmptyList.Instance;
IEnumerable ITypeDefinition.Properties => EmptyList.Instance;
IEnumerable ITypeDefinition.Events => EmptyList.Instance;
KnownTypeCode ITypeDefinition.KnownTypeCode => KnownTypeCode.None;
IType ITypeDefinition.EnumUnderlyingType => SpecialType.UnknownType;
public FullTypeName FullTypeName => typeName;
public string MetadataName => typeName.Name;
ITypeDefinition IEntity.DeclaringTypeDefinition => null;
IType ITypeDefinition.DeclaringType => null;
IType IType.DeclaringType => null;
IType IEntity.DeclaringType => null;
bool ITypeDefinition.HasExtensions => false;
ExtensionInfo ITypeDefinition.ExtensionInfo => null;
bool ITypeDefinition.IsReadOnly => false;
// Materialized WPF types carry no metadata, so treat them all as (reference-type) classes.
// The BAML decompiler only reads their identity and null-guards everything else.
TypeKind IType.Kind => TypeKind.Class;
bool? IType.IsReferenceType => true;
bool IType.IsByRefLike => false;
Nullability IType.Nullability => Nullability.Oblivious;
Nullability ITypeDefinition.NullableContext => Nullability.Oblivious;
// Synthetic WPF types are always nullability-oblivious and BAML never annotates them.
IType IType.ChangeNullability(Nullability nullability) => this;
int IType.TypeParameterCount => 0;
IReadOnlyList IType.TypeParameters => EmptyList.Instance;
IReadOnlyList IType.TypeArguments => EmptyList.Instance;
IEnumerable IType.DirectBaseTypes => EmptyList.Instance;
EntityHandle IEntity.MetadataToken => MetadataTokens.TypeDefinitionHandle(0);
public string Name => typeName.Name;
IModule IEntity.ParentModule => module;
Accessibility IEntity.Accessibility => Accessibility.Public;
bool IEntity.IsStatic => false;
bool IEntity.IsAbstract => false;
bool IEntity.IsSealed => false;
SymbolKind ISymbol.SymbolKind => SymbolKind.TypeDefinition;
ICompilation ICompilationProvider.Compilation => module.Compilation;
string INamedElement.FullName => typeName.ReflectionName;
string INamedElement.ReflectionName => typeName.ReflectionName;
string INamedElement.Namespace => typeName.Namespace;
bool IEquatable.Equals(IType other) => this == other;
IEnumerable IType.GetAccessors(Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IEntity.GetAttributes() => EmptyList.Instance;
bool IEntity.HasAttribute(KnownAttribute attribute) => false;
IAttribute IEntity.GetAttribute(KnownAttribute attribute) => null;
IEnumerable IType.GetConstructors(Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IType.GetEvents(Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IType.GetFields(Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IType.GetMembers(Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IType.GetMethods(Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IType.GetMethods(IReadOnlyList typeArguments, Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IType.GetNestedTypes(Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IType.GetNestedTypes(IReadOnlyList typeArguments, Predicate filter, GetMemberOptions options) => EmptyList.Instance;
IEnumerable IType.GetProperties(Predicate filter, GetMemberOptions options) => EmptyList.Instance;
bool ITypeDefinition.IsRecord => false;
ITypeDefinition IType.GetDefinition() => this;
ITypeDefinitionOrUnknown IType.GetDefinitionOrUnknown() => this;
TypeParameterSubstitution IType.GetSubstitution() => TypeParameterSubstitution.Identity;
IType IType.AcceptVisitor(TypeVisitor visitor) => visitor.VisitTypeDefinition(this);
IType IType.VisitChildren(TypeVisitor visitor) => this;
public override string ToString() => $"[SyntheticWpfType {typeName.ReflectionName}]";
}
}
}