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

Start proposal for more constant types #8257

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
79 changes: 79 additions & 0 deletions proposals/more-const-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Unmanaged constant types
Copy link
Member

Choose a reason for hiding this comment

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

I have a similar proposal, from 2017, that goes over a different approach to supporting this functionality here: #688


* [x] Proposed
* [ ] Prototype: [Complete](https://github.com/PROTOTYPE_OWNER/roslyn/BRANCH_NAME)
* [ ] Implementation: [In Progress](https://github.com/dotnet/roslyn/BRANCH_NAME)
* [ ] Specification: [Not Started](pr/1)

## Summary
[summary]: #summary

Expand the allowable types for compile-time constants (`const` declarations) to any value type that is fully blittable.
Copy link
Member

Choose a reason for hiding this comment

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

C# notably doesn't have a concept of blittable, only unmanaged. blittable has a specific meaning, as it pertains to interop, and types like bool aren't considered blittable unless the user opts-in via DisableRuntimeMarshalling


The current specification [§12.23](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1223-constant-expressions) limits constant expressions to an enumerated set of types, or default reference expressions (`default`, or `null`).

This proposal expands the types allowed. The allowed types should include any unmanaged type ([§8.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#88-unmanaged-types))
Copy link
Member

Choose a reason for hiding this comment

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

There's potential issues with regards to variable length types such as nint or nuint where today the underlying primitive constants are restricted to their 32-bit domain


It could be expanded to include any `readonly struct` type.

We should consider if a `ref struct` type, such as `ReadOnlySpan<T>` could be a compile-time constant.

## Motivation
[motivation]: #motivation

Only constant expressions can be used in pattern matching expressions. This expands the types that can be used in patterns. For example, it's common to use GUIDs as identifiers. This feature enables matching on GUID values. Currently, the only way to do that is to convert a GUID to a ReadOnlySpan<byte> and use a list pattern on the list of bytes.
Copy link
Member

Choose a reason for hiding this comment

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

Expanding the notion of const in the language is a decent sized feature as it ends up touching on a number of areas. If the main motivation is to expand the set of types usable in patterns should we just approach that more directly?


Similarly, any other type that holds multiple values, like record struct types, could be used to match patterns. For example, a `Point` type could be matched agains a `const` for the origin.

Other uses for constant expressions include:

- default argument values.
- Attribute parameters

## Detailed design
[design]: #detailed-design

This is the bulk of the proposal. Explain the design in enough detail for somebody familiar with the language to understand, and for somebody familiar with the compiler to implement, and include examples of how the feature is used. This section can start out light before the prototyping phase but should get into specifics and corner-cases as the feature is iteratively designed and implemented.

## Drawbacks
[drawbacks]: #drawbacks

Why should we *not* do this?

## Alternatives
[alternatives]: #alternatives

What other designs have been considered? What is the impact of not doing this?

## Unresolved questions
[unresolved]: #unresolved-questions

What parts of the design are still undecided?

- What types should be allowed?
- What are the implications of allowing constant ref structs? Are they even useful?
- How will down-level scenarios work? Can these constants be accessed by code using previous compilers? What about using Reflection to access fields?
- Does a type need to "opt in" to all constant definitions? (Otherwise, changes later could introduce a field that disallows all existing `const` declarations):

```csharp
public struct Point
{
public int X;
public int Y;
}

// V2:

public struct Point
{
public int X;
public int Y;
public object SomeReference;
}
```

Copy link

Choose a reason for hiding this comment

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

How do we reconcile this with the runtime concept of field constant?

Currently C# constants match the runtime's version of field constants. For example, a field can be declared that is a constant which is different than a static field with a value. The constant value is encoded in the metadata which the compiler uses to emit as load instructions at the use site instead of referencing the field at runtime, because no field exists for it at runtime. The kinds of values that can be field constants is constrained to those that can be encoded in metadata. I'm not sure what those are, but it probably does not include arbitrary types even if they are blittable.

Of course, C# could deviate from the runtime and allow other types to be constant but would have to change to use static fields for these instead of field constants.

Copy link
Member

@333fred 333fred Jul 3, 2024

Choose a reason for hiding this comment

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

C# already diverges from the runtime's concept of constants. Consider decimal:

const decimal d = 1.0M;

Results in:

    // Fields
    .field public static initonly valuetype [System.Runtime]System.Decimal d
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.DecimalConstantAttribute::.ctor(uint8, uint8, uint32, uint32, uint32) = (
        01 00 01 00 00 00 00 00 00 00 00 00 0a 00 00 00
        00 00
    )

Plus a static ctor to assign that value.

## Design meetings

Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to.