Skip to content
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
128 changes: 128 additions & 0 deletions backend/src/BuiltinExecution/Libs/Reflection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,134 @@ let fns : List<BuiltInFn> =
| _ -> incorrectArgs ()
sqlSpec = NotQueryable
previewable = Pure
deprecated = NotDeprecated }


{ name = fn "reflectType" 0
typeParams = []
parameters = [ Param.make "dv" (TVariable "a") "" ]
returnType =
TCustomType(
Ok(
FQTypeName.fqPackage
LibExecution.PackageIDs.Type.LanguageTools.RuntimeTypes.valueType
),
[]
)
description =
"Returns the ValueType of a value without the full Dval structure. More lightweight than reflect for type inspection."
fn =
function
| _, _, _, [ dv ] ->
dv
|> Dval.toValueType
|> LibExecution.RuntimeTypesToDarkTypes.ValueType.toDT
|> Ply
| _ -> incorrectArgs ()
sqlSpec = NotQueryable
previewable = Pure
deprecated = NotDeprecated }


{ name = fn "typeOf" 0
typeParams = []
parameters = [ Param.make "dv" (TVariable "a") "" ]
returnType = TString
description =
"Returns a human-readable type name as a string (e.g., 'Int64', 'List<String>', 'Dict<Bool>'). Lightweight alternative to full reflection."
fn =
function
| _, _, _, [ dv ] ->
let rec typeToString (vt : ValueType) : string =
match vt with
| ValueType.Unknown -> "Unknown"
| ValueType.Known kt ->
match kt with
| KTUnit -> "Unit"
| KTBool -> "Bool"
| KTInt8 -> "Int8"
| KTUInt8 -> "UInt8"
| KTInt16 -> "Int16"
| KTUInt16 -> "UInt16"
| KTInt32 -> "Int32"
| KTUInt32 -> "UInt32"
| KTInt64 -> "Int64"
| KTUInt64 -> "UInt64"
| KTInt128 -> "Int128"
| KTUInt128 -> "UInt128"
| KTFloat -> "Float"
| KTChar -> "Char"
| KTString -> "String"
| KTUuid -> "Uuid"
| KTDateTime -> "DateTime"
| KTList inner -> $"List<{typeToString inner}>"
| KTDict inner -> $"Dict<{typeToString inner}>"
| KTTuple(first, second, theRest) ->
let types =
[ first; second ] @ theRest |> List.map typeToString |> String.concat ", "
$"({types})"
| KTCustomType(typeName, typeArgs) ->
let name =
match typeName with
| FQTypeName.Package id -> $"Package({id})"
let args =
if List.isEmpty typeArgs then
""
else
"<" + (typeArgs |> List.map typeToString |> String.concat ", ") + ">"
name + args
| KTFn(args, ret) ->
let argStr =
args
|> NEList.toList
|> List.map typeToString
|> String.concat ", "
$"({argStr}) -> {typeToString ret}"
| KTDB inner -> $"DB<{typeToString inner}>"

dv |> Dval.toValueType |> typeToString |> DString |> Ply
| _ -> incorrectArgs ()
sqlSpec = NotQueryable
previewable = Pure
deprecated = NotDeprecated }


{ name = fn "typeName" 0
typeParams = []
parameters = [ Param.make "dv" (TVariable "a") "" ]
returnType =
TCustomType(
Ok(FQTypeName.stdlib "Option" 0),
[ TCustomType(
Ok(
FQTypeName.fqPackage
LibExecution.PackageIDs.Type.LanguageTools.RuntimeTypes.FQTypeName
.fqTypeName
),
[]
) ]
)
description =
"Returns the FQTypeName for custom types (records and enums). Returns None for primitives and built-in types."
fn =
function
| _, _, _, [ dv ] ->
let typeName =
match dv with
| DRecord(_, typeName, _, _) -> Some typeName
| DEnum(_, typeName, _, _, _) -> Some typeName
| _ -> None

match typeName with
| Some name ->
LibExecution.RuntimeTypesToDarkTypes.FQTypeName.toDT name
|> Dval.optionSome (VT.known LibExecution.RuntimeTypesToDarkTypes.FQTypeName.knownType)
| None ->
Dval.optionNone (VT.known LibExecution.RuntimeTypesToDarkTypes.FQTypeName.knownType)
|> Ply
| _ -> incorrectArgs ()
sqlSpec = NotQueryable
previewable = Pure
deprecated = NotDeprecated } ]

let builtins = LibExecution.Builtin.make [] fns
46 changes: 46 additions & 0 deletions backend/testfiles/execution/cli/reflection-test.dark
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// CLI test for reflection capabilities
// This demonstrates using reflection in CLI scripts

module ReflectionCliTest =
type Person = { name: String; age: Int64 }

type Status =
| Active
| Inactive of String

let testValue = Person { name = "Alice"; age = 30L }

let testEnum = Status.Active

// Test typeOf
let typeOfTest =
(Builtin.typeOf testValue |> Stdlib.String.startsWith "Package")
&& (Builtin.typeOf 42L == "Int64")
&& (Builtin.typeOf "hello" == "String")

// Test reflectType
let reflectTypeTest =
match Builtin.reflectType 42L with
| Known kt ->
(match kt with
| KTInt64 -> true
| _ -> false)
| _ -> false

// Test typeName
let typeNameTest =
match Builtin.typeName testValue with
| Some _fqtn -> true
| None -> false

// Test package functions
let packageFnTests =
Darklang.LanguageTools.Reflection.isPrimitive 42L
&& Darklang.LanguageTools.Reflection.isCustomType testValue
&& Darklang.LanguageTools.Reflection.isCollection [ 1L; 2L ]

// Return 0 for success if all tests pass
(if typeOfTest && reflectTypeTest && typeNameTest && packageFnTests then
0L
else
1L)
82 changes: 82 additions & 0 deletions backend/testfiles/execution/stdlib/reflection.dark
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Tests for reflection builtins

// Test Builtin.typeOf with primitives
Builtin.typeOf () = "Unit"
Builtin.typeOf true = "Bool"
Builtin.typeOf 42L = "Int64"
Builtin.typeOf 3.14 = "Float"
Builtin.typeOf "hello" = "String"
Builtin.typeOf 'c' = "Char"

// Test Builtin.typeOf with collections
Builtin.typeOf [ 1L; 2L; 3L ] = "List<Int64>"
Builtin.typeOf [ "a"; "b" ] = "List<String>"
Builtin.typeOf (Stdlib.Dict.empty ()) = "Dict<Unknown>"

// Test Builtin.typeOf with tuples
Builtin.typeOf (1L, "hello") = "(Int64, String)"
Builtin.typeOf (1L, "hello", true) = "(Int64, String, Bool)"

// Test Builtin.reflectType with primitives
(match Builtin.reflectType 42L with
| Known kt ->
(match kt with
| KTInt64 -> true
| _ -> false)
| _ -> false) = true

(match Builtin.reflectType "hello" with
| Known kt ->
(match kt with
| KTString -> true
| _ -> false)
| _ -> false) = true

(match Builtin.reflectType true with
| Known kt ->
(match kt with
| KTBool -> true
| _ -> false)
| _ -> false) = true

// Test Builtin.reflectType with list
(match Builtin.reflectType [ 1L; 2L; 3L ] with
| Known kt ->
(match kt with
| KTList _elementType -> true
| _ -> false)
| _ -> false) = true

// Test Builtin.typeName with primitives (should return None)
Builtin.typeName 42L = Stdlib.Option.Option.None

Builtin.typeName "hello" = Stdlib.Option.Option.None

Builtin.typeName [ 1L; 2L ] = Stdlib.Option.Option.None

// Test Builtin.typeName with custom types
module CustomTypeTests =
type Person = { name: String; age: Int64 }

type Color =
| Red
| Blue
| Green of String

let person = Person { name = "Alice"; age = 30L }

let color = Color.Red

// typeName should return Some for custom types
(match Builtin.typeName person with
| Some _fqTypeName -> true
| None -> false) = true

(match Builtin.typeName color with
| Some _fqTypeName -> true
| None -> false) = true

// typeOf should work with custom types
(Builtin.typeOf person |> Stdlib.String.startsWith "Package") = true

(Builtin.typeOf color |> Stdlib.String.startsWith "Package") = true
Loading