Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for module initializers #992

Draft
wants to merge 5 commits into
base: draft-v9
Choose a base branch
from
Draft
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
17 changes: 17 additions & 0 deletions standard/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@
- `System.ObsoleteAttribute` ([§22.5.4](attributes.md#2254-the-obsolete-attribute)), which is used to mark a member as obsolete.
- `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` ([§22.5.5](attributes.md#2255-the-asyncmethodbuilder-attribute)), which is used to establish a task builder for an async method.
- `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§22.5.6.2](attributes.md#22562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§22.5.6.3](attributes.md#22563-the-callerfilepath-attribute)), and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§22.5.6.4](attributes.md#22564-the-callermembername-attribute)), which are used to supply information about the calling context to optional parameters.
- `System.Runtime.CompilerServices.ModuleInitializer` (§module-init-attr), which is used to mark a method as a module initializer.

The Nullable static analysis attributes ([§22.5.7](attributes.md#2257-code-analysis-attributes)) can improve the correctness of warnings generated for nullabilities and null states ([§8.9.5](types.md#895-nullabilities-and-null-states)).

Expand Down Expand Up @@ -860,6 +861,22 @@

For invocations that occur within declarations of instance constructors, static constructors, finalizers and operators the member name used is implementation-dependent.

### §module-init-attr The ModuleInitializer attribute

The attribute `ModuleInitializer` is used to mark a method as a ***module initializer***. Such a method is called during initialization of the containing module. A module may have multiple initializers, which are called in an implementation-defined order.
Copy link
Contributor

@jnm2 jnm2 Feb 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how much you want to repeat, but from https://github.com/dotnet/runtime/blob/main/docs/design/specs/Ecma-335-Augments.md#module-initializer:

Module Initializer

All modules may have a module initializer. A module initializer is defined as the type initializer (§ II.10.5.3) of the <Module> type (§ II.10.8).

There are no limitations on what code is permitted in a module initializer. Module initializers are permitted to run and call both managed and unmanaged code.

Module Initialization Guarantees

In addition to the guarantees that apply to all type initializers, the CLI shall provide the following guarantees for module initializers:

  1. A module initializer is executed at, or sometime before, first access to any static field or first invocation of any method defined in the module.

  2. A module initializer shall run exactly once for any given module unless explicitly called by user code.

  3. No method other than those called directly or indirectly from the module initializer will be able to access the types, methods, or data in a module before its initializer completes execution.

Copy link
Contributor

@jnm2 jnm2 Feb 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part specifically is interesting:

A module initializer is executed at, or sometime before, first access to any static field or first invocation of any method defined in the module.

Because there is no guarantee that the runtime will execute the initializer before using reflection to access types in the module, and in fact it does not, and this means that libraries can't use module initializers to hook up to AppDomain.AssemblyResolve in time to load assemblies required by such reflection. (Examples of wanting to do this is when assemblies are stored in nonstandard locations such as embedded resources or other places on disk, or when you need to handle assembly versioning differences and you can't use binding redirects. What all of these have in common are scenarios where a .NET library is a plugin to a third-party application.)


There are no limitations on what code is permitted in a module initializer.

A module initializer shall have the following characteristics:

- The *method_modifier* `static`.
- No *parameter_list*.
- A *return_type* of `void`.
- No *type_parameter_list*.
- Not be declared inside a *class_declaration* having a *type_parameter_list*.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not within a struct_declaration having a type_parameter_list, either.

I think it's better to specify this as a semantic constraint "Not be a member of a generic type" than as a grammar constraint.

- Be accessible from the containing module (that is, have an access modifier `internal` or `public`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that allow protected internal?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does. The rules were chosen to describe "callable from <Module>..cctor while following .NET accessibility rules." You can simulate it by asking whether the method would be accessible from a regular C# or .NET IL from a regular top-level class's static constructor.

Copy link
Contributor

@KalleOlaviNiemitalo KalleOlaviNiemitalo Feb 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, protected internal is not an access modifier; it is a sequence of two access modifiers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how about:

public class C {
    private class D {
        [System.Runtime.CompilerServices.ModuleInitializer]
        public static void M() {
        }
    }
}

The declaration of C.D.M() has the public access modifier but SharpLab.io tells me "error CS8814: Module initializer method 'M' must be accessible at the module level". So the wording in this PR does not seem quite accurate.

The wording might have to be changed again in the future when C# 11 file-local types are specified.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a really good point.

- Not be a local function.
Comment on lines +870 to +878
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In C# 11, this spec will also have to disallow virtual and abstract, which otherwise become allowed in static methods of interfaces.


Check failure on line 879 in standard/attributes.md

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces [Expected: 0 or 2; Actual: 42]
### 22.5.7 Code analysis attributes

#### 22.5.7.1 General
Expand Down
2 changes: 2 additions & 0 deletions standard/portability-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ A conforming implementation is required to document its choice of behavior in ea
1. The impact of thread termination when a thread has no handler for an exception, and the thread is itself terminated. ([§13.10.6](statements.md#13106-the-throw-statement))
1. The mechanism by which linkage to an external method is achieved. ([§15.6.8](classes.md#1568-external-methods))
1. The impact of thread termination when no matching `catch` clause is found for an exception and the code that initially started that thread is reached. ([§21.4](exceptions.md#214-how-exceptions-are-handled)).
1. The order of execution of module initializers in a module (§module-init-attr).
1. An execution environment may provide additional attributes that affect the execution of a C# program. ([§22.5.1](attributes.md#2251-general))
1. The mappings between pointers and integers. ([§23.5.1](unsafe-code.md#2351-general))
1. The effect of applying the unary `*` operator to a `null` pointer. ([§23.6.2](unsafe-code.md#2362-pointer-indirection))
1. The behavior when pointer arithmetic overflows the domain of the pointer type. ([§23.6.6](unsafe-code.md#2366-pointer-increment-and-decrement), [§23.6.7](unsafe-code.md#2367-pointer-arithmetic))
1. The result of the `sizeof` operator for non-pre-defined value types. ([§23.6.9](unsafe-code.md#2369-the-sizeof-operator))
1. The behavior of the `fixed` statement if the array expression is `null` or if the array has zero elements. ([§23.7](unsafe-code.md#237-the-fixed-statement))
1. The behavior of the `fixed` statement if the string expression is `null`. ([§23.7](unsafe-code.md#237-the-fixed-statement))
1. The value returned when a stack allocation of size zero is made ([§12.8.21](expressions.md#12821-stack-allocation)).

## B.4 Unspecified behavior

Expand Down
6 changes: 6 additions & 0 deletions standard/standard-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,12 @@ namespace System.Runtime.CompilerServices
void OnCompleted(Action continuation);
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ModuleInitializerAttribute : Attribute
{
public ModuleInitializerAttribute() { }
}

public readonly struct TaskAwaiter : ICriticalNotifyCompletion,
INotifyCompletion
{
Expand Down
Loading