Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions src/Compiler/TypedTree/TcGlobals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,8 @@ type TcGlobals(
member val unchecked_unary_not_vref = ValRefForIntrinsic v_unchecked_unary_not_info
member val unchecked_subtraction_vref = ValRefForIntrinsic v_unchecked_subtraction_info
member val unchecked_multiply_vref = ValRefForIntrinsic v_unchecked_multiply_info
member val unchecked_division_vref = ValRefForIntrinsic v_unchecked_division_info
member val unchecked_modulus_vref = ValRefForIntrinsic v_unchecked_modulus_info
member val unchecked_defaultof_vref = ValRefForIntrinsic v_unchecked_defaultof_info
member val refcell_deref_vref = ValRefForIntrinsic v_refcell_deref_info
member val refcell_assign_vref = ValRefForIntrinsic v_refcell_assign_info
Expand Down
126 changes: 102 additions & 24 deletions src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3742,6 +3742,14 @@ let (|SpecificUnopExpr|_|) g vrefReqd expr =
| UnopExpr g (vref, arg1) when valRefEq g vref vrefReqd -> Some arg1
| _ -> None

let (|SignedIntegerConstExpr|_|) expr =
match expr with
| Expr.Const (Const.Int32 _, _, _)
| Expr.Const (Const.SByte _, _, _)
| Expr.Const (Const.Int16 _, _, _)
| Expr.Const (Const.Int64 _, _, _) -> Some ()
| _ -> None

let (|SpecificBinopExpr|_|) g vrefReqd expr =
match expr with
| BinopExpr g (vref, arg1, arg2) when valRefEq g vref vrefReqd -> Some (arg1, arg2)
Expand Down Expand Up @@ -9647,12 +9655,44 @@ let IsSimpleSyntacticConstantExpr g inputExpr =
checkExpr vrefs e

checkExpr Set.empty inputExpr
let EvalArithBinOp (opInt8, opInt16, opInt32, opInt64, opUInt8, opUInt16, opUInt32, opUInt64) (arg1: Expr) (arg2: Expr) =
// At compile-time we check arithmetic

let EvalArithShiftOp (opInt8, opInt16, opInt32, opInt64, opUInt8, opUInt16, opUInt32, opUInt64) (arg1: Expr) (arg2: Expr) =
// At compile-time we check arithmetic
let m = unionRanges arg1.Range arg2.Range
try
match arg1, arg2 with
try
match arg1, arg2 with
| Expr.Const (Const.Int32 x1, _, ty), Expr.Const (Const.Int32 shift, _, _) -> Expr.Const (Const.Int32 (opInt32 x1 shift), m, ty)
| Expr.Const (Const.SByte x1, _, ty), Expr.Const (Const.Int32 shift, _, _) -> Expr.Const (Const.SByte (opInt8 x1 shift), m, ty)
| Expr.Const (Const.Int16 x1, _, ty), Expr.Const (Const.Int32 shift, _, _) -> Expr.Const (Const.Int16 (opInt16 x1 shift), m, ty)
| Expr.Const (Const.Int64 x1, _, ty), Expr.Const (Const.Int32 shift, _, _) -> Expr.Const (Const.Int64 (opInt64 x1 shift), m, ty)
| Expr.Const (Const.Byte x1, _, ty), Expr.Const (Const.Int32 shift, _, _) -> Expr.Const (Const.Byte (opUInt8 x1 shift), m, ty)
| Expr.Const (Const.UInt16 x1, _, ty), Expr.Const (Const.Int32 shift, _, _) -> Expr.Const (Const.UInt16 (opUInt16 x1 shift), m, ty)
| Expr.Const (Const.UInt32 x1, _, ty), Expr.Const (Const.Int32 shift, _, _) -> Expr.Const (Const.UInt32 (opUInt32 x1 shift), m, ty)
| Expr.Const (Const.UInt64 x1, _, ty), Expr.Const (Const.Int32 shift, _, _) -> Expr.Const (Const.UInt64 (opUInt64 x1 shift), m, ty)
| _ -> error (Error ( FSComp.SR.tastNotAConstantExpression(), m))
with :? System.OverflowException -> error (Error ( FSComp.SR.tastConstantExpressionOverflow(), m))

let EvalArithUnOp (opInt8, opInt16, opInt32, opInt64, opUInt8, opUInt16, opUInt32, opUInt64) (arg1: Expr) =
// At compile-time we check arithmetic
let m = arg1.Range
try
match arg1 with
| Expr.Const (Const.Int32 x1, _, ty) -> Expr.Const (Const.Int32 (opInt32 x1), m, ty)
| Expr.Const (Const.SByte x1, _, ty) -> Expr.Const (Const.SByte (opInt8 x1), m, ty)
| Expr.Const (Const.Int16 x1, _, ty) -> Expr.Const (Const.Int16 (opInt16 x1), m, ty)
| Expr.Const (Const.Int64 x1, _, ty) -> Expr.Const (Const.Int64 (opInt64 x1), m, ty)
| Expr.Const (Const.Byte x1, _, ty) -> Expr.Const (Const.Byte (opUInt8 x1), m, ty)
| Expr.Const (Const.UInt16 x1, _, ty) -> Expr.Const (Const.UInt16 (opUInt16 x1), m, ty)
| Expr.Const (Const.UInt32 x1, _, ty) -> Expr.Const (Const.UInt32 (opUInt32 x1), m, ty)
| Expr.Const (Const.UInt64 x1, _, ty) -> Expr.Const (Const.UInt64 (opUInt64 x1), m, ty)
| _ -> error (Error ( FSComp.SR.tastNotAConstantExpression(), m))
with :? System.OverflowException -> error (Error ( FSComp.SR.tastConstantExpressionOverflow(), m))

let EvalArithBinOp (opInt8, opInt16, opInt32, opInt64, opUInt8, opUInt16, opUInt32, opUInt64) (arg1: Expr) (arg2: Expr) =
// At compile-time we check arithmetic
let m = unionRanges arg1.Range arg2.Range
try
match arg1, arg2 with
| Expr.Const (Const.Int32 x1, _, ty), Expr.Const (Const.Int32 x2, _, _) -> Expr.Const (Const.Int32 (opInt32 x1 x2), m, ty)
| Expr.Const (Const.SByte x1, _, ty), Expr.Const (Const.SByte x2, _, _) -> Expr.Const (Const.SByte (opInt8 x1 x2), m, ty)
| Expr.Const (Const.Int16 x1, _, ty), Expr.Const (Const.Int16 x2, _, _) -> Expr.Const (Const.Int16 (opInt16 x1 x2), m, ty)
Expand Down Expand Up @@ -9700,29 +9740,67 @@ let rec EvalAttribArgExpr g x =
// Detect bitwise or of attribute flags
| AttribBitwiseOrExpr g (arg1, arg2) ->
EvalArithBinOp ((|||), (|||), (|||), (|||), (|||), (|||), (|||), (|||)) (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificBinopExpr g g.unchecked_addition_vref (arg1, arg2) ->
// At compile-time we check arithmetic
let v1, v2 = EvalAttribArgExpr g arg1, EvalAttribArgExpr g arg2
match v1, v2 with
| Expr.Const (Const.String x1, m, ty), Expr.Const (Const.String x2, _, _) -> Expr.Const (Const.String (x1 + x2), m, ty)
| _ ->
#if ALLOW_ARITHMETIC_OPS_IN_LITERAL_EXPRESSIONS_AND_ATTRIBUTE_ARGS
EvalArithBinOp (Checked.(+), Checked.(+), Checked.(+), Checked.(+), Checked.(+), Checked.(+), Checked.(+), Checked.(+)) g v1 v2
#else
errorR (Error ( FSComp.SR.tastNotAConstantExpression(), x.Range))
x
#endif
#if ALLOW_ARITHMETIC_OPS_IN_LITERAL_EXPRESSIONS_AND_ATTRIBUTE_ARGS
| SpecificBinopExpr g g.unchecked_subtraction_vref (arg1, arg2) ->
EvalArithBinOp (Checked.(-), Checked.(-), Checked.(-), Checked.(-), Checked.(-), Checked.(-), Checked.(-), Checked.(-)) g (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificBinopExpr g g.unchecked_multiply_vref (arg1, arg2) ->
EvalArithBinOp (Checked.(*), Checked.(*), Checked.(*), Checked.(*), Checked.(*), Checked.(*), Checked.(*), Checked.(*)) g (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
#endif
| SpecificBinopExpr g g.unchecked_addition_vref (arg1, arg2) ->
// At compile-time we check arithmetic
let v1, v2 = EvalAttribArgExpr g arg1, EvalAttribArgExpr g arg2
match v1, v2 with
| Expr.Const (Const.String x1, m, ty), Expr.Const (Const.String x2, _, _) -> Expr.Const (Const.String (x1 + x2), m, ty)
| _ ->
EvalArithBinOp (Checked.(+), Checked.(+), Checked.(+), Checked.(+), Checked.(+), Checked.(+), Checked.(+), Checked.(+)) v1 v2
| SpecificBinopExpr g g.unchecked_subtraction_vref (arg1, arg2) ->
EvalArithBinOp (Checked.(-), Checked.(-), Checked.(-), Checked.(-), Checked.(-), Checked.(-), Checked.(-), Checked.(-)) (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificBinopExpr g g.unchecked_multiply_vref (arg1, arg2) ->
EvalArithBinOp (Checked.(*), Checked.(*), Checked.(*), Checked.(*), Checked.(*), Checked.(*), Checked.(*), Checked.(*)) (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificBinopExpr g g.unchecked_division_vref (arg1, arg2) ->
EvalArithBinOp ((/), (/), (/), (/), (/), (/), (/), (/)) (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificBinopExpr g g.unchecked_modulus_vref (arg1, arg2) ->
EvalArithBinOp ((%), (%), (%), (%), (%), (%), (%), (%)) (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificBinopExpr g g.bitwise_shift_left_vref (arg1, arg2) ->
EvalArithShiftOp ((<<<), (<<<), (<<<), (<<<), (<<<), (<<<), (<<<), (<<<)) (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificBinopExpr g g.bitwise_shift_right_vref (arg1, arg2) ->
EvalArithShiftOp ((>>>), (>>>), (>>>), (>>>), (>>>), (>>>), (>>>), (>>>)) (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificBinopExpr g g.bitwise_and_vref (arg1, arg2) ->
EvalArithBinOp ((&&&), (&&&), (&&&), (&&&), (&&&), (&&&), (&&&), (&&&)) (EvalAttribArgExpr g arg1) (EvalAttribArgExpr g arg2)
| SpecificUnopExpr g g.unchecked_unary_minus_vref arg1 ->
let v1 = EvalAttribArgExpr g arg1

match v1 with
| SignedIntegerConstExpr ->
let ignore (_x: 'a) = Unchecked.defaultof<'a>
EvalArithUnOp (Checked.(~-), Checked.(~-), Checked.(~-), Checked.(~-), ignore, ignore, ignore, ignore) v1
| _ ->
errorR (Error ( FSComp.SR.tastNotAConstantExpression(), v1.Range))
x
| SpecificUnopExpr g g.unchecked_unary_plus_vref arg1 ->
EvalArithUnOp ((~+), (~+), (~+), (~+), (~+), (~+), (~+), (~+)) (EvalAttribArgExpr g arg1)
| SpecificUnopExpr g g.unchecked_unary_not_vref arg1 ->
match EvalAttribArgExpr g arg1 with
| Expr.Const (Const.Bool value, m, ty) ->
Expr.Const (Const.Bool (not value), m, ty)
| expr ->
errorR (Error ( FSComp.SR.tastNotAConstantExpression(), expr.Range))
x
// Detect logical operations on booleans, which are represented as a match expression
| Expr.Match (decision = TDSwitch (input = input; cases = [ TCase (DecisionTreeTest.Const (Const.Bool test), TDSuccess ([], targetNum)) ]); targets = [| TTarget (_, t0, _); TTarget (_, t1, _) |]) ->
match EvalAttribArgExpr g (stripDebugPoints input) with
| Expr.Const (Const.Bool value, _, _) ->
let pass, fail =
if targetNum = 0 then
t0, t1
else
t1, t0

if value = test then
EvalAttribArgExpr g (stripDebugPoints pass)
else
EvalAttribArgExpr g (stripDebugPoints fail)
| _ ->
errorR (Error ( FSComp.SR.tastNotAConstantExpression(), x.Range))
x
| _ ->
errorR (Error ( FSComp.SR.tastNotAConstantExpression(), x.Range))
x


and EvaledAttribExprEquality g e1 e2 =
match e1, e2 with
| Expr.Const (c1, _, _), Expr.Const (c2, _, _) -> c1 = c2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,6 @@ module AttributeUsage =
(Error 685, Line 20, Col 5, Line 20, Col 10, "The generic function 'Foo' must be given explicit type argument(s)")
]

// # SOURCE=E_WithBitwiseAnd01.fsx SCFLAGS="--test:ErrorRanges -a" # E_WithBitwiseAnd01.fsx
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"E_WithBitwiseAnd01.fsx"|])>]
let ``E_WithBitwiseAnd01_fsx`` compilation =
compilation
|> verifyCompile
|> shouldFail
|> withDiagnostics [
(Error 267, Line 7, Col 25, Line 7, Col 91, "This is not a valid constant expression or custom attribute value")
(Warning 839, Line 12, Col 3, Line 12, Col 6, "Unexpected condition in imported assembly: failed to decode AttributeUsage attribute")
]

// SOURCE=E_WithBitwiseOr01.fsx SCFLAGS="--test:ErrorRanges -a" # E_WithBitwiseOr01.fsx
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"E_WithBitwiseOr01.fsx"|])>]
let ``E_WithBitwiseOr01_fsx`` compilation =
Expand Down

This file was deleted.

172 changes: 172 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/EmittedIL/Literals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,175 @@ let main _ =
|> verifyIL ["""
.field public static literal int32 x = int32(0x00000007)
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.LiteralAttribute::.ctor() = ( 01 00 00 00 )"""]


[<Fact>]
let ``Arithmetics in integer literals is evaluated at compile-time``() =
FSharp """
module LiteralArithmetics

let [<Literal>] bytesInMegabyte = 1024L * 1024L

let [<Literal>] bytesInKilobyte = bytesInMegabyte >>> 10

let [<Literal>] bytesInKilobyte2 = bytesInMegabyte / 1024L

let [<Literal>] secondsInDayPlusThree = 3 + (60 * 60 * 24)

let [<Literal>] bitwise = 1us &&& (3us ||| 4us)
"""
|> compile
|> shouldSucceed
|> verifyIL [
""".field public static literal int64 bytesInMegabyte = int64(0x100000)"""
""".field public static literal int64 bytesInKilobyte = int64(0x400)"""
""".field public static literal int64 bytesInKilobyte2 = int64(0x400)"""
""".field public static literal int32 secondsInDayPlusThree = int32(0x00015183)"""
""".field public static literal uint16 bitwise = uint16(0x0001)"""
]

[<Fact>]
let ``Logical operations on booleans are evaluated at compile-time``() =
FSharp """
module LiteralArithmetics

let [<Literal>] flag = true

let [<Literal>] flippedFlag = not flag

let [<Literal>] simple1 = flippedFlag || false

let [<Literal>] simple2 = true && not true

let [<Literal>] complex1 = false || (flag && not flippedFlag)

let [<Literal>] complex2 = false || (flag && flippedFlag)

let [<Literal>] complex3 = true || (flag && not flippedFlag)
"""
|> compile
|> shouldSucceed
|> verifyIL [
""".field public static literal bool flag = bool(true)"""
""".field public static literal bool flippedFlag = bool(false)"""
""".field public static literal bool simple1 = bool(false)"""
""".field public static literal bool simple2 = bool(false)"""
""".field public static literal bool complex1 = bool(true)"""
""".field public static literal bool complex2 = bool(false)"""
""".field public static literal bool complex3 = bool(true)"""
]

[<Fact>]
let ``Arithmetics can be used for constructing enum literals``() =
FSharp """
module LiteralArithmetics

type E =
| A = 1
| B = 2

let [<Literal>] x = enum<E> (1 + 1)
"""
|> compile
|> shouldSucceed
|> verifyIL [
""".field public static literal valuetype LiteralArithmetics/E x = int32(0x00000002)"""
]

[<Fact>]
let ``Arithmetics can be used for constructing literals in attributes``() =
FSharp """
module LiteralArithmetics

open System.Runtime.CompilerServices

// 256 = AggressiveInlining
[<MethodImpl(enum -(-1 <<< 8))>]
let x () =
3
"""
|> compile
|> shouldSucceed
|> verifyIL [
""".method public static int32 x() cil managed aggressiveinlining"""
]

[<Fact>]
let ``Compilation fails when addition in literal overflows``() =
FSharp """
module LiteralArithmetics

let [<Literal>] x = System.Int32.MaxValue + 1
"""
|> compile
|> shouldFail
|> withResult {
Error = Error 3177
Range = { StartLine = 4
StartColumn = 21
EndLine = 4
EndColumn = 46 }
Message = "This literal expression or attribute argument results in an arithmetic overflow."
}

[<Fact>]
let ``Compilation fails when using decimal arithmetics in literal``() =
FSharp """
module LiteralArithmetics

let [<Literal>] x = 1m + 1m
"""
|> compile
|> shouldFail
|> withResults [
{ Error = Error 267
Range = { StartLine = 4
StartColumn = 21
EndLine = 4
EndColumn = 23 }
Message = "This is not a valid constant expression or custom attribute value" }
{ Error = Error 267
Range = { StartLine = 4
StartColumn = 26
EndLine = 4
EndColumn = 28 }
Message = "This is not a valid constant expression or custom attribute value" }
{ Error = Error 267
Range = { StartLine = 4
StartColumn = 21
EndLine = 4
EndColumn = 28 }
Message = "This is not a valid constant expression or custom attribute value" }
]

[<Fact>]
let ``Compilation fails when using arithmetics with a non-literal in literal``() =
FSharp """
module LiteralArithmetics

let [<Literal>] x = 1 + System.DateTime.Now.Hour
"""
|> compile
|> shouldFail
|> withResults [
#if !NETCOREAPP
{ Error = Warning 52
Range = { StartLine = 4
StartColumn = 25
EndLine = 4
EndColumn = 49 }
Message = "The value has been copied to ensure the original is not mutated by this operation or because the copy is implicit when returning a struct from a member and another member is then accessed" }
#endif
{ Error = Error 267
Range = { StartLine = 4
StartColumn = 25
EndLine = 4
EndColumn = 49 }
Message = "This is not a valid constant expression or custom attribute value" }
{ Error = Error 267
Range = { StartLine = 4
StartColumn = 21
EndLine = 4
EndColumn = 49 }
Message = "This is not a valid constant expression or custom attribute value" }
]