22// The .NET Foundation licenses this file to you under the Apache 2.0 License.
33// See the LICENSE file in the project root for more information.
44
5- using MSAst = System . Linq . Expressions ;
5+ #nullable enable
66
77using System ;
88using System . Collections . Generic ;
9-
10- using Microsoft . Scripting . Interpreter ;
9+ using System . Diagnostics ;
1110
1211using IronPython . Runtime ;
1312using IronPython . Runtime . Operations ;
1413
15- using AstUtils = Microsoft . Scripting . Ast . Utils ;
14+ using Microsoft . Scripting . Interpreter ;
15+
16+ using MSAst = System . Linq . Expressions ;
1617
1718namespace IronPython . Compiler . Ast {
18- using Ast = MSAst . Expression ;
19-
2019 public class DictionaryExpression : Expression , IInstructionProvider {
2120 private readonly SliceExpression [ ] _items ;
22- private static readonly MSAst . Expression EmptyDictExpression = Ast . Call ( AstMethods . MakeEmptyDict ) ;
21+ private readonly bool _hasNullKey ;
22+ private static readonly MSAst . Expression EmptyDictExpression = Expression . Call ( AstMethods . MakeEmptyDict ) ;
2323
2424 public DictionaryExpression ( params SliceExpression [ ] items ) {
25+ foreach ( var item in items ) {
26+ if ( item . SliceStart is null ) _hasNullKey = true ;
27+ if ( item . SliceStop is null ) throw PythonOps . ValueError ( "None disallowed in expression list" ) ;
28+ }
2529 _items = items ;
2630 }
2731
28- public IList < SliceExpression > Items => _items ;
32+ public IReadOnlyList < SliceExpression > Items => _items ;
2933
3034 public override MSAst . Expression Reduce ( ) {
35+ // empty dictionary
36+ if ( _items . Length == 0 ) {
37+ return EmptyDictExpression ;
38+ }
39+
40+ if ( _hasNullKey ) {
41+ // TODO: unpack constant dicts?
42+ return ReduceDictionaryWithUnpack ( Parent . LocalContext , _items . AsSpan ( ) ) ;
43+ }
44+
3145 // create keys & values into array and then call helper function
3246 // which creates the dictionary
33- if ( _items . Length != 0 ) {
34- return ReduceConstant ( ) ?? ReduceDictionaryWithItems ( ) ;
35- }
47+ return ReduceConstant ( ) ?? ReduceDictionaryWithItems ( _items . AsSpan ( ) ) ;
48+ }
3649
37- // empty dictionary
38- return EmptyDictExpression ;
50+ private static MSAst . Expression ReduceDictionaryWithUnpack ( MSAst . Expression context , ReadOnlySpan < SliceExpression > items ) {
51+ Debug . Assert ( items . Length > 0 ) ;
52+ var expressions = new List < MSAst . Expression > ( items . Length + 2 ) ;
53+ var varExpr = Expression . Variable ( typeof ( PythonDictionary ) , "$dict" ) ;
54+ bool isInit = false ;
55+ var cnt = 0 ;
56+ for ( var i = 0 ; i < items . Length ; i ++ ) {
57+ var item = items [ i ] ;
58+ if ( item . SliceStart is null ) {
59+ if ( cnt != 0 ) {
60+ var dict = ReduceDictionaryWithItems ( items . Slice ( i - cnt , cnt ) ) ;
61+ if ( ! isInit ) {
62+ expressions . Add ( Expression . Assign ( varExpr , dict ) ) ;
63+ isInit = true ;
64+ } else {
65+ expressions . Add ( Expression . Call ( AstMethods . DictUpdate , context , varExpr , dict ) ) ;
66+ }
67+ cnt = 0 ;
68+ }
69+ if ( ! isInit ) {
70+ expressions . Add ( Expression . Assign ( varExpr , EmptyDictExpression ) ) ;
71+ isInit = true ;
72+ }
73+ expressions . Add ( Expression . Call ( AstMethods . DictUpdate , context , varExpr , TransformOrConstantNull ( item . SliceStop , typeof ( object ) ) ) ) ;
74+ } else {
75+ cnt ++ ;
76+ }
77+ }
78+ if ( cnt != 0 ) {
79+ var dict = ReduceDictionaryWithItems ( items . Slice ( items . Length - cnt , cnt ) ) ;
80+ if ( isInit ) {
81+ expressions . Add ( Expression . Call ( AstMethods . DictUpdate , context , varExpr , dict ) ) ;
82+ } else {
83+ return dict ;
84+ }
85+ }
86+ expressions . Add ( varExpr ) ;
87+ return Expression . Block ( typeof ( PythonDictionary ) , new MSAst . ParameterExpression [ ] { varExpr } , expressions ) ;
3988 }
4089
41- private MSAst . Expression ReduceDictionaryWithItems ( ) {
42- MSAst . Expression [ ] parts = new MSAst . Expression [ _items . Length * 2 ] ;
43- Type t = null ;
90+ private static MSAst . Expression ReduceDictionaryWithItems ( ReadOnlySpan < SliceExpression > items ) {
91+ MSAst . Expression [ ] parts = new MSAst . Expression [ items . Length * 2 ] ;
92+ Type ? t = null ;
4493 bool heterogeneous = false ;
45- for ( int index = 0 ; index < _items . Length ; index ++ ) {
46- SliceExpression slice = _items [ index ] ;
94+ for ( int index = 0 ; index < items . Length ; index ++ ) {
95+ SliceExpression slice = items [ index ] ;
4796 // Eval order should be:
4897 // { 2 : 1, 4 : 3, 6 :5 }
4998 // This is backwards from parameter list eval, so create temporaries to swap ordering.
5099
51-
52100 parts [ index * 2 ] = TransformOrConstantNull ( slice . SliceStop , typeof ( object ) ) ;
53101 MSAst . Expression key = parts [ index * 2 + 1 ] = TransformOrConstantNull ( slice . SliceStart , typeof ( object ) ) ;
54102
@@ -68,19 +116,19 @@ private MSAst.Expression ReduceDictionaryWithItems() {
68116 }
69117 }
70118
71- return Ast . Call (
119+ return Expression . Call (
72120 heterogeneous ? AstMethods . MakeDictFromItems : AstMethods . MakeHomogeneousDictFromItems ,
73- Ast . NewArrayInit (
121+ Expression . NewArrayInit (
74122 typeof ( object ) ,
75123 parts
76124 )
77125 ) ;
78126 }
79127
80- private MSAst . Expression ReduceConstant ( ) {
128+ private MSAst . Expression ? ReduceConstant ( ) {
81129 for ( int index = 0 ; index < _items . Length ; index ++ ) {
82130 SliceExpression slice = _items [ index ] ;
83- if ( ! slice . SliceStop . IsConstant || ! slice . SliceStart . IsConstant ) {
131+ if ( slice . SliceStart is null || ! slice . SliceStart . IsConstant || ! slice . SliceStop ! . IsConstant ) {
84132 return null ;
85133 }
86134 }
@@ -89,11 +137,11 @@ private MSAst.Expression ReduceConstant() {
89137 for ( int index = 0 ; index < _items . Length ; index ++ ) {
90138 SliceExpression slice = _items [ index ] ;
91139
92- storage . AddNoLock ( slice . SliceStart . GetConstantValue ( ) , slice . SliceStop . GetConstantValue ( ) ) ;
140+ Debug . Assert ( slice . SliceStart is not null ) ;
141+ storage . AddNoLock ( slice . SliceStart ! . GetConstantValue ( ) , slice . SliceStop ! . GetConstantValue ( ) ) ;
93142 }
94143
95-
96- return Ast . Call ( AstMethods . MakeConstantDict , Ast . Constant ( new ConstantDictionaryStorage ( storage ) , typeof ( object ) ) ) ;
144+ return Expression . Call ( AstMethods . MakeConstantDict , Expression . Constant ( new ConstantDictionaryStorage ( storage ) , typeof ( object ) ) ) ;
97145 }
98146
99147 public override void Walk ( PythonWalker walker ) {
@@ -120,8 +168,8 @@ void IInstructionProvider.AddInstructions(LightCompiler compiler) {
120168
121169 #endregion
122170
123- private class EmptyDictInstruction : Instruction {
124- public static EmptyDictInstruction Instance = new EmptyDictInstruction ( ) ;
171+ private class EmptyDictInstruction : Instruction {
172+ public static readonly EmptyDictInstruction Instance = new EmptyDictInstruction ( ) ;
125173
126174 public override int Run ( InterpretedFrame frame ) {
127175 frame . Push ( PythonOps . MakeEmptyDict ( ) ) ;
0 commit comments