Skip to content

Commit 5df01a4

Browse files
ncaveMangelMaxime
andauthored
[JS/TS] Initial support for Nullable Reference Types (fable-compiler#4159)
Co-authored-by: Maxime Mangel <[email protected]>
1 parent cc41244 commit 5df01a4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+488
-369
lines changed

src/Fable.AST/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
* [All] Support for Nullable Reference Types (by @ncave and @MangelMaxime)
11+
1012
## 5.0.0-beta.1 - 2025-02-16
1113

1214
### Added

src/Fable.AST/Fable.fs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,18 +191,20 @@ type Type =
191191
| String
192192
| Regex
193193
| Number of kind: NumberKind * info: NumberInfo
194-
| Option of genericArg: Type * isStruct: bool
195-
| Tuple of genericArgs: Type list * isStruct: bool
196-
| Array of genericArg: Type * kind: ArrayKind
197-
| List of genericArg: Type
194+
| Option of genArg: Type * isStruct: bool
195+
| Tuple of genArg: Type list * isStruct: bool
196+
| Array of genArg: Type * kind: ArrayKind
197+
| List of genArg: Type
198198
| LambdaType of argType: Type * returnType: Type
199199
| DelegateType of argTypes: Type list * returnType: Type
200200
| GenericParam of name: string * isMeasure: bool * constraints: Constraint list
201-
| DeclaredType of ref: EntityRef * genericArgs: Type list
202-
| AnonymousRecordType of fieldNames: string[] * genericArgs: Type list * isStruct: bool
201+
| DeclaredType of ref: EntityRef * genArgs: Type list
202+
| AnonymousRecordType of fieldNames: string[] * genArgs: Type list * isStruct: bool
203+
| Nullable of genArg: Type * isStruct: bool
203204

204205
member this.Generics =
205206
match this with
207+
| Nullable(gen, _)
206208
| Option(gen, _)
207209
| Array(gen, _)
208210
| List gen -> [ gen ]
@@ -225,6 +227,7 @@ type Type =
225227

226228
member this.MapGenerics f =
227229
match this with
230+
| Nullable(gen, isStruct) -> Nullable(f gen, isStruct)
228231
| Option(gen, isStruct) -> Option(f gen, isStruct)
229232
| Array(gen, kind) -> Array(f gen, kind)
230233
| List gen -> List(f gen)

src/Fable.Cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
* [JS/TS] Fix #3533: Add directives prologues supports (by @MangelMaxime)
13+
* [JS/TS] Support for Nullable Reference Types (by @ncave and @MangelMaxime)
1314

1415
### Changed
1516

src/Fable.Cli/Main.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl
737737
let fableProj =
738738
Project.From(
739739
projCracked.ProjectFile,
740-
projCracked.ProjectOptions.SourceFiles,
740+
projCracked.ProjectOptions,
741741
[],
742742
assemblies,
743743
Log.log,

src/Fable.Compiler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
* [JS/TS] Fix #3533: Add directives prologues supports (by @MangelMaxime)
13+
* [JS/TS] Support for Nullable Reference Types (by @ncave and @MangelMaxime)
1314

1415
### Changed
1516

src/Fable.Compiler/Library.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ module CodeServices =
164164
let fableProj =
165165
Project.From(
166166
cliArgs.ProjectFile,
167-
crackerResponse.ProjectOptions.SourceFiles,
167+
crackerResponse.ProjectOptions,
168168
checkProjectResult.AssemblyContents.ImplementationFiles,
169169
assemblies,
170170
Log.log,
@@ -205,7 +205,7 @@ module CodeServices =
205205
let fableProj =
206206
Project.From(
207207
cliArgs.ProjectFile,
208-
crackerResponse.ProjectOptions.SourceFiles,
208+
crackerResponse.ProjectOptions,
209209
typeCheckProjectResult.ProjectCheckResults.AssemblyContents.ImplementationFiles,
210210
typeCheckProjectResult.Assemblies,
211211
Log.log,
@@ -298,7 +298,7 @@ module CodeServices =
298298
let fableProj =
299299
Project.From(
300300
cliArgs.ProjectFile,
301-
crackerResponse.ProjectOptions.SourceFiles,
301+
crackerResponse.ProjectOptions,
302302
checkProjectResult.AssemblyContents.ImplementationFiles,
303303
assemblies,
304304
Log.log,

src/Fable.Transforms/Dart/Fable2Dart.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ module Util =
194194
let tup = List.length genArgs |> getTupleTypeIdent com ctx
195195
Type.reference (tup, genArgs)
196196

197+
let transformNullableType com ctx genArg =
198+
transformType com ctx genArg |> Nullable
199+
197200
let transformOptionType com ctx genArg =
198201
let genArg = transformType com ctx genArg
199202

@@ -215,7 +218,6 @@ module Util =
215218
| Types.array, _ -> List Dynamic
216219
| "System.Tuple`1", _ -> transformTupleType com ctx genArgs
217220
| Types.valueType, _ -> Object
218-
| Types.nullable, [ genArg ]
219221
| "Fable.Core.Dart.DartNullable`1", [ genArg ] -> Nullable genArg
220222
| Types.regexGroup, _ -> Nullable String
221223
| Types.regexMatch, _ -> makeTypeRefFromName "Match" []
@@ -650,6 +652,7 @@ module Util =
650652
| BigInt
651653
| NativeInt
652654
| UNativeInt -> Dynamic // TODO
655+
| Fable.Nullable(genArg, _isStruct) -> transformNullableType com ctx genArg
653656
| Fable.Option(genArg, _isStruct) -> transformOptionType com ctx genArg
654657
| Fable.Array(TransformType com ctx genArg, _) -> List genArg
655658
| Fable.List(TransformType com ctx genArg) -> Type.reference (getFSharpListTypeIdent com ctx, [ genArg ])

src/Fable.Transforms/FSharp2Fable.Util.fs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,8 @@ module TypeHelpers =
15571557
| Types.string -> Fable.String
15581558
| Types.regex -> Fable.Regex
15591559
| Types.type_ -> Fable.MetaType
1560+
| Types.nullable ->
1561+
Fable.Nullable(makeTypeGenArgsWithConstraints withConstraints ctxTypeArgs genArgs |> List.head, true)
15601562
| Types.valueOption ->
15611563
Fable.Option(makeTypeGenArgsWithConstraints withConstraints ctxTypeArgs genArgs |> List.head, true)
15621564
| Types.option ->
@@ -1632,12 +1634,12 @@ module TypeHelpers =
16321634
else
16331635
Fable.Any // failwithf "Unexpected non-declared F# type: %A" t
16341636

1635-
// TODO:
1636-
// if not t.IsGenericParameter && t.HasNullAnnotation // || t.IsNullAmbivalent
1637-
// then
1638-
// makeRuntimeType [ typ ] Types.nullable // represent it as Nullable<T>
1639-
// else typ
1640-
typ
1637+
if
1638+
Compiler.CheckNulls && t.HasNullAnnotation // || t.IsNullAmbivalent
1639+
then
1640+
Fable.Nullable(typ, false)
1641+
else
1642+
typ
16411643

16421644
let makeType (ctxTypeArgs: Map<string, Fable.Type>) t =
16431645
makeTypeWithConstraints true ctxTypeArgs t
@@ -1672,15 +1674,21 @@ module TypeHelpers =
16721674
| FSharpXmlDoc.FromXmlText(xmlDoc) -> xmlDoc.GetXmlText() |> Some
16731675
| _ -> None
16741676

1675-
let tryGetInterfaceTypeFromMethod (meth: FSharpMemberOrFunctionOrValue) =
1676-
if meth.ImplementedAbstractSignatures.Count > 0 then
1677-
nonAbbreviatedType meth.ImplementedAbstractSignatures[0].DeclaringType |> Some
1677+
let tryGetInterfaceTypeFromMethod (memb: FSharpMemberOrFunctionOrValue) =
1678+
if
1679+
memb.IsOverrideOrExplicitInterfaceImplementation
1680+
&& memb.ImplementedAbstractSignatures.Count > 0
1681+
then
1682+
nonAbbreviatedType memb.ImplementedAbstractSignatures[0].DeclaringType |> Some
16781683
else
16791684
None
16801685

1681-
let tryGetInterfaceDefinitionFromMethod (meth: FSharpMemberOrFunctionOrValue) =
1682-
if meth.ImplementedAbstractSignatures.Count > 0 then
1683-
let t = nonAbbreviatedType meth.ImplementedAbstractSignatures[0].DeclaringType
1686+
let tryGetInterfaceDefinitionFromMethod (memb: FSharpMemberOrFunctionOrValue) =
1687+
if
1688+
memb.IsOverrideOrExplicitInterfaceImplementation
1689+
&& memb.ImplementedAbstractSignatures.Count > 0
1690+
then
1691+
let t = nonAbbreviatedType memb.ImplementedAbstractSignatures[0].DeclaringType
16841692

16851693
if t.HasTypeDefinition then
16861694
Some t.TypeDefinition
@@ -2380,6 +2388,7 @@ module Util =
23802388
entityName + "." + memberName + overloadHash
23812389

23822390
let getAbstractMemberInfo com (ent: FSharpEntity) (memb: FSharpMemberOrFunctionOrValue) =
2391+
let ent = tryGetInterfaceDefinitionFromMethod memb |> Option.defaultValue ent
23832392
let isMangled = isMangledAbstractEntity com ent
23842393
let isGetter = FsMemberFunctionOrValue.IsGetter(memb)
23852394
let isSetter = not isGetter && FsMemberFunctionOrValue.IsSetter(memb)

src/Fable.Transforms/Fable2Babel.fs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ module Reflection =
334334
| Fable.LambdaType(argType, returnType) -> genericTypeInfo "lambda" [ argType; returnType ]
335335
| Fable.DelegateType(argTypes, returnType) -> genericTypeInfo "delegate" [ yield! argTypes; yield returnType ]
336336
| Fable.Tuple(genArgs, _) -> genericTypeInfo "tuple" genArgs
337+
| Fable.Nullable(genArg, isStruct) -> transformTypeInfoFor purpose com ctx r genMap genArg
337338
| Fable.Option(genArg, _) -> genericTypeInfo "option" [ genArg ]
338339
| Fable.Array(genArg, _) -> genericTypeInfo "array" [ genArg ]
339340
| Fable.List genArg -> genericTypeInfo "list" [ genArg ]
@@ -495,6 +496,7 @@ module Reflection =
495496
| Fable.MetaType -> jsInstanceof (libValue com ctx "Reflection" "TypeInfo") expr
496497
| Fable.Option _ -> warnAndEvalToFalse "options" // TODO
497498
| Fable.GenericParam _ -> warnAndEvalToFalse "generic parameters"
499+
| Fable.Nullable(genArg, _isStruct) -> transformTypeTest com ctx range expr genArg
498500
| Fable.DeclaredType(ent, genArgs) ->
499501
match ent.FullName with
500502
| Types.idisposable ->
@@ -609,6 +611,7 @@ module Annotation =
609611
| Fable.Regex -> makeAliasTypeAnnotation com ctx "RegExp"
610612
| Fable.Number(BigInt, _) -> makeAliasTypeAnnotation com ctx "bigint"
611613
| Fable.Number(kind, _) -> makeNumericTypeAnnotation com ctx kind
614+
| Fable.Nullable(genArg, _) -> makeNullableTypeAnnotation com ctx genArg
612615
| Fable.Option(genArg, _) -> makeOptionTypeAnnotation com ctx genArg
613616
| Fable.Tuple(genArgs, _) -> makeTupleTypeAnnotation com ctx genArgs
614617
| Fable.Array(genArg, kind) -> makeArrayTypeAnnotation com ctx genArg kind
@@ -693,7 +696,7 @@ module Annotation =
693696
makeFableLibImportTypeAnnotation com ctx [] moduleName typeName
694697

695698
let makeNullableTypeAnnotation com ctx genArg =
696-
makeFableLibImportTypeAnnotation com ctx [ genArg ] "Option" "Nullable"
699+
makeFableLibImportTypeAnnotation com ctx [ genArg ] "Util" "Nullable"
697700

698701
let makeOptionTypeAnnotation com ctx genArg =
699702
makeFableLibImportTypeAnnotation com ctx [ genArg ] "Option" "Option"
@@ -881,8 +884,6 @@ module Annotation =
881884

882885
let makeEntityTypeAnnotation com ctx genArgs (ent: Fable.Entity) =
883886
match genArgs, ent with
884-
| [ genArg ], EntFullName Types.nullable -> makeNullableTypeAnnotation com ctx genArg
885-
886887
| _, Patterns.Try (tryNativeOrFableLibraryInterface com ctx genArgs) ta -> ta
887888

888889
| _, Patterns.Try (Lib.tryJsConstructorFor Annotation com ctx) entRef ->
@@ -1978,6 +1979,16 @@ module Util =
19781979
| Fable.Value(Fable.Null _, _), e
19791980
| e, Fable.Value(Fable.Null _, _) ->
19801981
com.TransformAsExpr(ctx, e) |> makeNullCheck range (op = BinaryEqual)
1982+
| ExprType(Fable.Nullable _), ExprType(Fable.Nullable _) ->
1983+
Replacements.Util.Helper.LibCall(
1984+
com,
1985+
"Util",
1986+
"nullableEquals",
1987+
Fable.Boolean,
1988+
[ e1; e2 ],
1989+
?loc = range
1990+
)
1991+
|> transformAsExpr com ctx
19811992
| ExprType(Fable.MetaType), _ ->
19821993
let e =
19831994
Replacements.Util.Helper.LibCall(

src/Fable.Transforms/Global/Compiler.fs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,20 @@ module CompilerExt =
124124
false
125125

126126
let private coreAssemblyNames = set Metadata.coreAssemblies
127-
let mutable private _lang = JavaScript
127+
128+
let mutable private _language = JavaScript
129+
let mutable private _checkNulls = false
128130

129131
type Compiler with
130132

131133
static member CoreAssemblyNames = coreAssemblyNames
132134

133-
static member Language = _lang
134-
/// Use this only once at the start of the program
135-
static member SetLanguageUnsafe lang = _lang <- lang
135+
static member Language = _language
136+
static member CheckNulls = _checkNulls
137+
138+
/// Set these only once at the start of the program
139+
static member SetLanguageUnsafe language = _language <- language
140+
static member SetCheckNullsUnsafe enabled = _checkNulls <- enabled
136141

137142
member com.GetEntity(entityRef: Fable.EntityRef) =
138143
match com.TryGetEntity(entityRef) with

0 commit comments

Comments
 (0)