The boundaries list
Forms that compile (10)
Section titled “Forms that compile (10)”| Syntax | Description |
|---|---|
class MyError : System.Exception { } | Declaring an exception type |
public delegate int Op(int a); | Declaring a delegate |
private Op F; | A delegate-typed field |
/// <summary>…</summary> | Doc comments |
#nullable enable / string? s | Nullable annotation |
[NotNull] | Null-analysis attribute |
[NotNullWhen(true)] out string s | Not-null-when-true specification |
[return: MaybeNull] | Maybe-null return specification |
[AllowNull] | Allow-null specification |
#nullable disable | Disabling the check |
Forms that don’t compile (11)
Section titled “Forms that don’t compile (11)”| Syntax | Description | Error | Reason | Instead |
|---|---|---|---|---|
throw; | Rethrow a caught exception | TUKI0001 | runtime | Represent failure with a return value or state |
catch { … } | Catch without writing a type | TUKI0001 | runtime | Same as above |
yield return / yield break | Return values one at a time | TUKI0001, TUKI0099 | runtime | Pack them into an array and return it |
IEnumerator<int> Nums() | Return an iterator | TUKI0001, TUKI0099 | runtime | Same as above |
async Task Run() | Write an async method | TUKI0001, TUKI0101 | runtime | Split it into events |
async void Run() | An async method with no return value | TUKI0001, TUKI0101 | runtime | Same as above |
await … | Wait for completion | TUKI0001 | runtime | Same as above |
System.Func<int,int> f = x => x + 1; | Store a lambda expression in a variable | TUKI0108, TUKI0001 | runtime | Call a method by name |
() => n | A lambda expression that captures an outer variable | TUKI0108, TUKI0001 | runtime | Pass the value you want to capture as an argument, and call a method by name |
[DllImport("x")] | Call an external library | TUKI0001 | runtime | Use a runtime method |
dynamic d = 1; d.ToString() | Resolve a member by name at runtime | TUKI0102, TUKI0099 | runtime | Write the type |
| A declaration compiling doesn’t mean you can use it. For both exception types and delegates, the declaration passes the compiler, and you’re rejected only once you write the line that uses it. |
LINQ is rejected not because of iterators or delegates, but because the namespace doesn’t exist. Just the line using System.Linq; produces CS0234, and the compiler says it can’t find System.Linq.
Forms that pass through silently are confusing precisely because they aren’t rejected. You can write doc comments and null annotations, but the runtime meaning doesn’t change.
Forms where only the declaration compiles
Section titled “Forms where only the declaration compiles”Declaring an exception type
Section titled “Declaring an exception type”You can declare an exception type, but you can’t throw or catch it.
using UnityEngine;using Tsukimi;
public class MyError : System.Exception { }public class ExCustom : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }}Declaring a delegate
Section titled “Declaring a delegate”You can declare a delegate type, but you can’t create a value of that type.
using UnityEngine;using Tsukimi;
public delegate int Op(int a);public class DlDeclared : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }}A delegate-typed field
Section titled “A delegate-typed field”You can declare the field, but you can’t create a value to put in it.
using UnityEngine;using Tsukimi;
public delegate int Op(int a);public class DlField : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }
private Op F;}Forms that pass through silently
Section titled “Forms that pass through silently”Doc comments
Section titled “Doc comments”You can write doc comments, but they don’t appear anywhere.
using UnityEngine;using Tsukimi;
public class DcComment : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }
/// <summary>description</summary> private int Helper() { return 1; }}Nullable annotation
Section titled “Nullable annotation”A type with ? attached affects C#‘s checking, but the runtime behaviour doesn’t change.
using UnityEngine;using Tsukimi;
public class NrAnnot : TsukimiBehaviour{ void Start() { #nullable enable string? s = null; Debug.Log(s == null); // => true }}Null-analysis attribute
Section titled “Null-analysis attribute”[NotNull] also affects only C#‘s analysis.
using UnityEngine;using Tsukimi;
public class NrAttr : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }
[System.Diagnostics.CodeAnalysis.NotNull] private string S = "a";}Not-null-when-true specification
Section titled “Not-null-when-true specification”[NotNullWhen(true)] is conveyed to C#‘s analysis, but the runtime behaviour doesn’t change.
using UnityEngine;using Tsukimi;
public class NrNotNullWhen : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }
private bool Try([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string s) { s = "a"; return true; }}Maybe-null return specification
Section titled “Maybe-null return specification”[return: MaybeNull] also affects only C#‘s analysis.
using UnityEngine;using Tsukimi;
public class NrMaybeNull : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }
[return: System.Diagnostics.CodeAnalysis.MaybeNull] private string Get() { return null; }}Allow-null specification
Section titled “Allow-null specification”[AllowNull] also affects only C#‘s analysis.
using UnityEngine;using Tsukimi;
public class NrAllowNull : TsukimiBehaviour{ void Start() { Debug.Log(1); // => 1 }
[System.Diagnostics.CodeAnalysis.AllowNull] private string S = "a";}Disabling the check
Section titled “Disabling the check”From the line where you write #nullable disable onward, null-safety checking is turned off.
#nullable enableusing UnityEngine;using Tsukimi;
public class NrDisable : TsukimiBehaviour{ void Start() { #nullable disable string s = null; Debug.Log(s == null); // => true }}Runtime types
Section titled “Runtime types”Boundaries aren’t only on the language side. They’re also on the side of the types you touch. What this environment exposes
is a subset of Unity’s and VRChat’s types. Writing a type that isn’t there into a variable gets rejected with TUKI0102.
Even if a type exists, not all of the calls in it are necessarily present. Calls that aren’t exposed
are rejected with TUKI0101.
This document doesn’t keep a list of what’s in which type. Unity’s types are in Unity’s Script Reference, and VRChat’s types are in VRChat’s creator documentation. What this document covers is the boundary on the language side, which comes before that.