Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/guide/declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,35 @@ Bar.baz = () => {};

:::

## Overloaded Methods

JavaScript does not have function overloads, so Bootsharp automatically disambiguates them when projecting overloaded C# methods. The overload with the fewest parameters keeps the original name; the rest are suffixed with `With...` derived from the extra parameter names (or, when that is still ambiguous, from the full parameter names or parameter types).

::: code-group

```csharp [Bar.cs]
namespace Foo;

public class Bar
{
[Export] public static void Start (string title) {}
[Export] public static void Start (string title, string info) {}
[Export] public static void Start (string title, double progress) {}
[Export] public static void Start (string title, string info, double progress) {}
}
```

```ts [foo.g.d.mts]
export namespace Bar {
export function start(title: string): void;
export function startWithInfo(title: string, info: string): void;
export function startWithProgress(title: string, progress: number): void;
export function startWithInfoAndProgress(title: string, info: string, progress: number): void;
}
```

:::

## Property Declarations

Exported properties are emitted as variables under the declaring class's TS namespace:
Expand Down
2 changes: 1 addition & 1 deletion samples/bench/bootsharp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "module",
"exports": {
".": "./bin/bootsharp/index.mjs",
"./*": "./bin/bootsharp/generated/*.g.mjs"
"./*": "./bin/bootsharp/generated/modules/*.g.mjs"
},
"browser": {
"node:fs": false,
Expand Down
61 changes: 61 additions & 0 deletions src/cs/Bootsharp.Publish.Test/GenerateJS/JSModuleTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,67 @@ public void DoesNotEmitModulesForBclTypes ()
DoesNotContain("../imports.g.mjs", "system");
}

[Fact]
public void OverloadedMethodsAreDisambiguated ()
{
AddAssembly(With(
"""
public class Class
{
[Export] public static void Foo (int a) {}
[Export] public static void Foo (string a) {}
[Export] public static void Foo (double b, double a) {}
[Export] public static void Bar (int x) {}
[Export] public static void Bar (int x, string name) {}
[Export] public static void Baz (int x, int y) {}
[Export] public static void Baz (int x, string y) {}
[Export] public static void Qux (int a) {}
[Export] public static void Qux (int a, int b) {}
[Export] public static void Qux (int a, int b, int c) {}
[Export] public static void X (int x, int y) {}
[Export] public static void X (string x, int y) {}
[Export] public static void X (int x, string y) {}
[Export] public static void Bob (int x, int y, string z) {}
[Export] public static void Bob (int x, string y, int q) {}
[Export] public static void Change (double progress) {}
[Export] public static void Change (string info) {}
[Export] public static void Change (double progress, string info) {}
[Export] public static void Start (string title) {}
[Export] public static void Start (string title, string info) {}
[Export] public static void Start (string title, double progress) {}
[Export] public static void Start (string title, string info, double progress) {}
}
"""));
Execute();
Contains(
"""
export const Class = {
foo: (a) => exports.Class_Foo(a),
fooWithA: (a) => exports.Class_Foo(a),
fooWithB: (b, a) => exports.Class_Foo(b, a),
bar: (x) => exports.Class_Bar(x),
barWithName: (x, name) => exports.Class_Bar(x, name),
baz: (x, y) => exports.Class_Baz(x, y),
bazWithXAndY: (x, y) => exports.Class_Baz(x, y),
qux: (a) => exports.Class_Qux(a),
quxWithB: (a, b) => exports.Class_Qux(a, b),
quxWithBAndC: (a, b, c) => exports.Class_Qux(a, b, c),
x: (x, y) => exports.Class_X(x, y),
xWithStringAndInt32: (x, y) => exports.Class_X(x, y),
xWithInt32AndString: (x, y) => exports.Class_X(x, y),
bob: (x, y, z) => exports.Class_Bob(x, y, z),
bobWithQ: (x, y, q) => exports.Class_Bob(x, y, q),
change: (progress) => exports.Class_Change(progress),
changeWithInfo: (info) => exports.Class_Change(info),
changeWithProgressAndInfo: (progress, info) => exports.Class_Change(progress, info),
start: (title) => exports.Class_Start(title),
startWithInfo: (title, info) => exports.Class_Start(title, info),
startWithProgress: (title, progress) => exports.Class_Start(title, progress),
startWithInfoAndProgress: (title, info, progress) => exports.Class_Start(title, info, progress)
};
""");
}

[Fact]
public void RespectsPrefsInStatics ()
{
Expand Down
6 changes: 6 additions & 0 deletions src/cs/Bootsharp.Publish/Common/Global/GlobalText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public static string ToFirstLower (string value)
return char.ToLowerInvariant(value[0]) + value[1..];
}

public static string ToFirstUpper (string value)
{
if (value.Length == 1) return value.ToUpperInvariant();
return char.ToUpperInvariant(value[0]) + value[1..];
}

public static string Slugify (string value)
{
var bld = new StringBuilder(value.Length + 4);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace Bootsharp.Publish;

internal static class OverloadDisambiguator
{
public static void Disambiguate (IEnumerable<SurfaceMeta> surfaces)
{
foreach (var surface in surfaces)
foreach (var overloaded in surface.Members.OfType<MethodMeta>()
.GroupBy(m => m.JSName).Where(g => g.Count() > 1))
Disambiguate(surface, overloaded.ToArray());
}

private static void Disambiguate (SurfaceMeta srf, IReadOnlyList<MethodMeta> overloaded)
{
var argsByOverload = MapArgs(overloaded); // attempt to disambiguate only with extra arg names
foreach (var method in FindAmbiguous(argsByOverload)) // if ambiguous, try with all arg names
argsByOverload[method] = GetArgNames(method);
foreach (var method in FindAmbiguous(argsByOverload)) // if still ambiguous, use arg type names
argsByOverload[method] = GetArgTypes(method);
foreach (var (method, args) in argsByOverload)
Rename(srf, method, string.Join("And", args));
}

private static Dictionary<MethodMeta, string[]> MapArgs (IReadOnlyList<MethodMeta> overloaded)
{
var baseline = ResolveBaseline(overloaded); // baseline is the method that won't be renamed
var baselineArgs = baseline.Args.Select(a => a.Name).ToHashSet();
return overloaded.Where(m => m != baseline).ToDictionary(m => m, m => m.Args
.Where(a => !baselineArgs.Contains(a.Name))
.Select(a => ToFirstUpper(a.Name))
// if an overload has extra args — use their names as discriminator
.ToArray() is { Length: > 0 } extra ? extra : GetArgNames(m));
}

private static IEnumerable<MethodMeta> FindAmbiguous (Dictionary<MethodMeta, string[]> args) => args
.GroupBy(kv => string.Join("|", kv.Value))
.Where(g => g.Count() > 1)
.SelectMany(g => g.Select(kv => kv.Key));

private static string[] GetArgNames (MethodMeta method) => method.Args
.Select(a => ToFirstUpper(a.Name)).ToArray();

private static string[] GetArgTypes (MethodMeta method) => method.Args
.Select(a => a.Value.Type.Clr.Name).ToArray();

private static MethodMeta ResolveBaseline (IReadOnlyList<MethodMeta> overloaded) => overloaded
.OrderBy(m => m.Args.Count)
.ThenBy(m => string.Join("|", m.Args.Select(a => a.Value.Type.Clr.FullName)))
.First();

private static void Rename (SurfaceMeta srf, MethodMeta method, string discriminator)
{
var members = (IList<MemberMeta>)srf.Members;
members[members.IndexOf(method)] = method with { JSName = $"{method.JSName}With{discriminator}" };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public void Inspect (Assembly assembly)

public IReadOnlyCollection<TypeMeta> Collect ()
{
OverloadDisambiguator.Disambiguate([..surfaces, ..its.Values]);
TypeMeta[] specialized = [..surfaces, ..its.Values, ..srd.Collect()];
var clrs = specialized.Select(t => t.Clr).ToHashSet();
return [..specialized, ..crawled.Values.Where(c => !clrs.Contains(c.Clr))];
Expand Down
2 changes: 1 addition & 1 deletion src/cs/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>

<PropertyGroup>
<Version>0.8.0-alpha.309</Version>
<Version>0.8.0-alpha.324</Version>
<Authors>Elringus</Authors>
<PackageTags>javascript typescript ts js wasm node deno bun interop codegen</PackageTags>
<PackageProjectUrl>https://bootsharp.com</PackageProjectUrl>
Expand Down
Loading