От: | ie | http://ziez.blogspot.com/ | |
Дата: | 23.09.06 13:51 | ||
Оценка: |
14.12 The null coalescing operator
The ?? operator is called the null coalescing operator.
null-coalescing-expression:
conditional-or-expression
conditional-or-expression ?? null-coalescing-expression
A null coalescing expression of the form a ?? b requires a to be of a nullable type or reference type. If a is
non-null, the result of a ?? b is a; otherwise, the result is b. The operation evaluates b only if a is null.
The null coalescing operator is right-associative, meaning that operations are grouped from right to left.
[Example: An expression of the form a ?? b ?? c is evaluated as a ?? (b ?? c). In general terms, an
expression of the form E1 ?? E2 ?? ... ?? EN returns the first of the operands that is non-null, or null if all
operands are null. end example]
The type of the expression a ?? b depends on which implicit conversions are available between the types
of the operands. In order of preference, the type of a ?? b is A0, A, or B, where A is the type of a, B is the
type of b, and A0 is the type that results from removing the trailing ? modifier, if any, from A. Specifically,
a ?? b is processed as follows:
• If A is not a nullable type or a reference type, a compile-time error occurs.
• If A is a nullable type and an implicit conversion exists from b to A0, the result type is A0. At run-time, a
is first evaluated. If a is not null, a is unwrapped to type A0, and this becomes the result. Otherwise, b is
evaluated and converted to type A0, and this becomes the result.
• Otherwise, if an implicit conversion exists from b to A, the result type is A. At run-time, a is first
evaluated. If a is not null, a becomes the result. Otherwise, b is evaluated and converted to type A, and
this becomes the result.
• Otherwise, if an implicit conversion exists from A0 to B, the result type is B. At run-time, a is first
evaluated. If a is not null, a is unwrapped to type A0 (unless A and A0 are the same type) and converted
to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.
Otherwise, a and b are incompatible, and a compile-time error occurs.
using System.Console;
using Nemerle;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
namespace Iae.Macro
{
/** This module is used to simplify work with op_Implicit operator.
This module is stateless.
*/
module OpImplicitHelper
{
/**
* Used to print all implicit operators of some type
*/
public PrintAll(cl : MType.Class) : void
{
def members = cl.tycon.LookupMember("op_Implicit");
foreach (m is IMethod in members)
WriteLine(m);
}
/**
* Used to determine has a `from' type implicit conversation
* to a `to' type or hasn't.
*/
public HasImplicit(from : MType.Class, to : MType.Class) : bool
{
def members = to.tycon.LookupMember("op_Implicit")
+ from.tycon.LookupMember("op_Implicit");
ret :
{
foreach (m is IMethod in members)
{
def tyRet = m.ReturnType;
def tyPar = m.GetParameters().Head.ty;
when (tyRet.Equals(to) && tyPar.Equals(from))
ret (true);
}
false;
}
}
}
macro @printImplicit(expr)
{
def typer = Macros.ImplicitCTX ();
def tx = typer.TypeExpr (expr);
match (tx.Type.Hint)
{
| Some (Class (_ti, _args) as cl) =>
OpImplicitHelper.PrintAll(cl);
<[]>
| _ =>
Message.FatalError ("PrintImplicit macro can not be used with such expression.");
}
}
macro @test(expr)
{
<[ if ($expr != null) $expr.Value; else (CI () : int) ]>
}
macro @?? (exprA, exprB)
{
def typer = Macros.ImplicitCTX ();
def tx = (typer.TypeExpr(exprA).Type.Hint, typer.TypeExpr(exprB).Type.Hint);
def teA = typer.TypeExpr(exprA);
def teB = typer.TypeExpr(exprB);
def tyA = typer.TypeExpr(exprA).Type;
def tyB = typer.TypeExpr(exprB).Type;
def upCast =
(texpr, ty) =>
TExpr.TypeConversion (texpr.loc, ty, texpr, ty,
ConversionKind.UpCast());
def _implicit =
(texpr, ty) =>
TExpr.TypeConversion (texpr.loc, ty, texpr, ty,
ConversionKind.Implicit());
match (tx)
{
| (Some(Class(tiA, [tyA0]) as clA), Some(Class(_, _) as clB))
when tiA.IsValueType && tiA.FullName == "System.Nullable" =>
def clA0 = tyA0 :> MType.Class;
if (clA0.Equals(clB)
|| OpImplicitHelper.HasImplicit(clB, clA0))
{
/**
* BUG: 'Nullable[T]' can not be converted to 'T',
* but it should.
* Example:
* def inull : int? = 10
* // cannot convert System.Nullable[int] to int:
* def i : int = inull :> int
**/
def st = <[
if ($exprA != null)
$exprA.Value
else
$(upCast(teB, tyA0) : typed)
]>;
WriteLine($"nullable: B -> A0: $st");
st
}
else if (clA.Equals(clB)
|| OpImplicitHelper.HasImplicit(clB, clA))
{
/**
* BUG: 'Nullable[T1]' can not be converted to
* 'Nullable[T2]' when 'T1' has implicit
* conversation to 'T2', but it should.
* C# Example:
* int? i = 10;
* double? d = i;
**/
def st = <[
if ($exprA != null)
$exprA
else
$(upCast(teB, tyA) : typed)
]>;
WriteLine($"nullable: B -> A: $st");
st
}
else if (OpImplicitHelper.HasImplicit(clA0, clB))
{
/**
* BUG: 'T1' can not be converted to 'Nullable[T2]'
* when 'T1' has implicit conversation to 'T2',
* but it should.
* C# Example:
* int i = 10;
* double? d = 0.1;
* d = i;
**/
def st = <[
if ($exprA != null)
$(upCast(typer.TypeExpr(<[ $exprA.Value ]>), tyB) : typed)
else
$exprB
]>;
WriteLine($"nullable: A0 -> B: $st");
st
}
else
{
Message.FatalError ($"Operator `??' cannot be applied to "
"operands of type `$clA' and `$clB'");
}
| (Some(Class(tiA, _)), Some(Class(_, _)))
when tiA.IsValueType =>
Message.FatalError (exprA.Location,
$"`$tiA' is not a reference or nullable type "
"as required by the `??' operator");
| (Some(Class(_, _) as clA), Some(Class(_, _) as clB))
when clA.Equals(clB) || OpImplicitHelper.HasImplicit(clB, clA) =>
/**
* ?BUG: This match case and next one can be replaced with single
* one, but followed by code doesn't compile. Is this a design
* decision or a bug? (C# compile such code with no problems)
* Example:
* #pragma indent
* class A {}
* class B
* public static @:(_ : B) : A
* A()
* def a = A()
* def b = B()
* // expected B-, got A in computation branch:
* def c = if (a==null) b else a
**/
def st = <[
if ($exprA != null)
$exprA
else
$(upCast(teB, tyA) : typed)
]>;
WriteLine($"reference: B -> A: $st");
st
| (Some(Class(_, _) as clA), Some(Class(_, _) as clB))
when OpImplicitHelper.HasImplicit(clA, clB) =>
def st = <[
if ($exprA != null)
$(upCast(teA, tyB) : typed)
else
$exprB
]>;
WriteLine($"reference: A -> B: $st");
st
| _ =>
Message.FatalError ($"Operator `??' cannot be applied to "
"operands of type `$tyA' and `$tyB'");
}
}
}
#pragma indent
using System.Console;
using Iae.Macro;
class C1
public override ToString() : string
"C1"
public Test() : void
WriteLine("C1::Test");
class C2
public static @:(_ : C2) : C1
C1()
public override ToString() : string
"C2"
class CI
public static @:(_ : CI) : int
2
def getType['t] (_ : 't)
$"$(typeof('t))"
def c1 = null : C1
def c2 = C2()
def r1 = c1 ?? c2 // (***)compile-time message: "reference: B -> A: if (c1 != null) c1; else (c2 : C1)"
def c1 = C1()
def t1 = getType(r1)
WriteLine($"TEST REFERENCE B -> A (result): $r1")
WriteLine($"TEST REFERENCE B -> A (type): $t1")
// run-time messages:
// TEST REFERENCE B -> A (result): C2
// TEST REFERENCE B -> A (type): C1
def r2 = c2 ?? c1 // compile-time message: "reference: A -> B: if (c2 != null) (c2 : C1); else c1"
def t2 = getType(r2)
WriteLine($"TEST REFERENCE A -> B (result): $r2")
WriteLine($"TEST REFERENCE A -> B (type): $t2")
// run-time messages:
// TEST REFERENCE B -> A (result): C2
// TEST REFERENCE B -> A (type): C1
def r0 = if (c1 == null) (c2 : C1) else c1 // собственно, тоже самое, что и в (***)
def t0 = getType(r0)
WriteLine($"TEST REFERENCE B -> A (result): $r0")
WriteLine($"TEST REFERENCE B -> A (type): $t0")
// run-time messages: выводит то, что ожидается в предыдущих случаях
// TEST REFERENCE B -> A (result): C1
// TEST REFERENCE B -> A (type): C1
def inull_v : int? = 10
def inull_n : int? = null
def r3 = inull_v ?? 1 // compile-time message: "nullable: B -> A0: if (inull_v != null) inull_v.Value; else (1 : int)"
def t3 = getType(r3)
WriteLine($"TEST NULLABLE B -> A0 (result): $r3")
WriteLine($"TEST NULLABLE B -> A0 (type): $t3")
// run-time messages:
// TEST NULLABLE B -> A0 (result): 10
// TEST NULLABLE B -> A0 (type): System.Int32
def r4 = inull_n ?? 1 // compile-time message: "nullable: B -> A0: if (inull_n != null) inull_v.Value; else (1 : int)"
def t4 = getType(r4)
WriteLine($"TEST NULLABLE B -> A0 (result): $r4")
WriteLine($"TEST NULLABLE B -> A0 (type): $t4")
// run-time messages:
// TEST NULLABLE B -> A0 (result): 1
// TEST NULLABLE B -> A0 (type): System.Int32
def r5 = test(inull_n) // это макрос, см. выше
def t5 = getType(r5)
WriteLine($"TEST NULLABLE B -> A0 (result): $r5")
WriteLine($"TEST NULLABLE B -> A0 (type): $t5")
// run-time messages:
// TEST NULLABLE B -> A0 (result): 2
// TEST NULLABLE B -> A0 (type): System.Int32
def r5 = inull_n ?? CI() // compile-time message: "nullable: B -> A0: if (inull_n != null) inull_n.Value; else (CI () : int)"
// Compile-time exception:
//test.n:59:21:59:23: debug: Internal compiler error, please report a bug to bugs.nemerle.org. You can try modifying program near this location.
//[01;31merror[0m: internal compiler error: assertion failed in file ncc\generation\ILEmitter.n, line 801:
//_N_AutoModule::Main: failed to convert non-value type CI to a value type int
// .............
def r5 = if (inull_n != null) inull_n.Value; else (CI () : int)
def t5 = getType(r5)
WriteLine($"TEST NULLABLE B -> A0 (result): $r5")
WriteLine($"TEST NULLABLE B -> A0 (type): $t5")
// run-time messages:
// TEST NULLABLE B -> A0 (result): 2
// TEST NULLABLE B -> A0 (type): System.Int32