@@ -48,6 +48,12 @@ internal struct EvalState
4848 internal ImmutableList < PrerequisiteEvalRecord > . Builder PrereqEvals ;
4949 internal Dictionary < string , IMembership > BigSegmentsMembership ;
5050 internal BigSegmentsStatus ? BigSegmentsStatus ;
51+ // True once the current evaluation scope has read a definition that carries the override
52+ // marker. Evaluate sets it from the evaluated flag. MatchClause sets it for each segment
53+ // that is read. CheckPrerequisites starts a nested scope from the prerequisite's own
54+ // marker and merges the nested result into this scope when it returns, so the marking
55+ // propagates upward only.
56+ internal bool OverrideAffected ;
5157
5258 internal EvalState ( Context context )
5359 {
@@ -57,6 +63,7 @@ internal EvalState(Context context)
5763 PrereqEvals = null ;
5864 BigSegmentsMembership = null ;
5965 BigSegmentsStatus = null ;
66+ OverrideAffected = false ;
6067 }
6168 }
6269
@@ -91,39 +98,46 @@ public EvalResult Evaluate(in FeatureFlag flag, in Context context)
9198 {
9299 throw new Exception ( ErrorMessageForTesting ) ;
93100 }
101+
102+ // Reading the flag's own definition is the first read of this evaluation, so the marking
103+ // starts from the flag's override marker.
104+ var state = new EvalState ( context ) ;
105+ state . OverrideAffected = flag . IsOverride ;
106+
94107 if ( ! context . Valid )
95108 {
96109 Logger . Warn ( "Tried to evaluate flag with invalid context: {0} returning null" ,
97110 flag . Key ) ;
98111
99- return new EvalResult (
100- new EvaluationDetail < LdValue > ( LdValue . Null , null , EvaluationReason . ErrorReason ( EvaluationErrorKind . UserNotSpecified ) ) ,
112+ return new EvalResult ( ErrorResult ( EvaluationErrorKind . UserNotSpecified , state . OverrideAffected ) ,
101113 ImmutableList . Create < PrerequisiteEvalRecord > ( ) ) ;
102114 }
103115
104116 try
105117 {
106- var state = new EvalState ( context ) ;
107118 var details = EvaluateInternal ( ref state , flag ) ;
119+ var reason = details . Reason ;
108120 if ( state . BigSegmentsStatus . HasValue )
109121 {
110- details = new EvaluationDetail < LdValue > (
111- details . Value ,
112- details . VariationIndex ,
113- details . Reason . WithBigSegmentsStatus ( state . BigSegmentsStatus . Value )
114- ) ;
122+ reason = reason . WithBigSegmentsStatus ( state . BigSegmentsStatus . Value ) ;
115123 }
124+ // Error reasons are marked too. A malformed override definition yields the caller's
125+ // default value with an error reason, and an override still affected that result.
126+ reason = reason . WithOverrideAffected ( state . OverrideAffected ) ;
127+ details = new EvaluationDetail < LdValue > ( details . Value , details . VariationIndex , reason ) ;
116128 return new EvalResult ( details , state . PrereqEvals is null ?
117129 ImmutableList . Create < PrerequisiteEvalRecord > ( ) : state . PrereqEvals . ToImmutable ( ) ) ;
118130 }
119131 catch ( StopEvaluationException e )
120132 {
121133 Logger . Error ( @"Could not evaluate flag ""{0}"": {1}" , flag . Key , string . Format ( e . MessageFormat , e . MessageParams ) ) ;
122- return new EvalResult ( ErrorResult ( e . ErrorKind ) , ImmutableList . Create < PrerequisiteEvalRecord > ( ) ) ;
134+ return new EvalResult ( ErrorResult ( e . ErrorKind , state . OverrideAffected ) ,
135+ ImmutableList . Create < PrerequisiteEvalRecord > ( ) ) ;
123136 }
124137 catch ( Exception )
125138 {
126- return new EvalResult ( ErrorResult ( EvaluationErrorKind . Exception ) , ImmutableList . Create < PrerequisiteEvalRecord > ( ) ) ;
139+ return new EvalResult ( ErrorResult ( EvaluationErrorKind . Exception , state . OverrideAffected ) ,
140+ ImmutableList . Create < PrerequisiteEvalRecord > ( ) ) ;
127141 }
128142 }
129143
@@ -166,6 +180,10 @@ private EvaluationDetail<LdValue> EvaluateInternal(ref EvalState state, FeatureF
166180 private static EvaluationDetail < LdValue > ErrorResult ( EvaluationErrorKind kind ) =>
167181 new EvaluationDetail < LdValue > ( LdValue . Null , null , EvaluationReason . ErrorReason ( kind ) ) ;
168182
183+ private static EvaluationDetail < LdValue > ErrorResult ( EvaluationErrorKind kind , bool overrideAffected ) =>
184+ new EvaluationDetail < LdValue > ( LdValue . Null , null ,
185+ EvaluationReason . ErrorReason ( kind ) . WithOverrideAffected ( overrideAffected ) ) ;
186+
169187 private EvaluationDetail < LdValue > GetVariation ( FeatureFlag flag , int variation , in EvaluationReason reason )
170188 {
171189 if ( variation < 0 || variation >= flag . Variations . Count ( ) )
@@ -213,7 +231,23 @@ private EvaluationDetail<LdValue> GetOffValue(FeatureFlag flag, in EvaluationRea
213231 }
214232 else
215233 {
216- var prereqDetails = EvaluateInternal ( ref state , prereqFeatureFlag ) ;
234+ // The nested evaluation is a scope of its own. It starts from the prerequisite's
235+ // own marker so that its record reflects only the definitions its subtree read.
236+ // When it returns, or throws, its marking merges into this scope. The marking
237+ // propagates upward only.
238+ var parentOverrideAffected = state . OverrideAffected ;
239+ state . OverrideAffected = prereqFeatureFlag . IsOverride ;
240+ EvaluationDetail < LdValue > prereqDetails ;
241+ bool prereqOverrideAffected ;
242+ try
243+ {
244+ prereqDetails = EvaluateInternal ( ref state , prereqFeatureFlag ) ;
245+ }
246+ finally
247+ {
248+ prereqOverrideAffected = state . OverrideAffected ;
249+ state . OverrideAffected = parentOverrideAffected || prereqOverrideAffected ;
250+ }
217251 // Note that if the prerequisite flag is off, we don't consider it a match no matter
218252 // what its off variation was. But we still need to evaluate it in order to generate
219253 // an event.
@@ -225,7 +259,9 @@ private EvaluationDetail<LdValue> GetOffValue(FeatureFlag flag, in EvaluationRea
225259 {
226260 state . PrereqEvals = ImmutableList . CreateBuilder < PrerequisiteEvalRecord > ( ) ;
227261 }
228- state . PrereqEvals . Add ( new PrerequisiteEvalRecord ( prereqFeatureFlag , flag . Key , prereqDetails ) ) ;
262+ var prereqRecordDetails = new EvaluationDetail < LdValue > ( prereqDetails . Value , prereqDetails . VariationIndex ,
263+ prereqDetails . Reason . WithOverrideAffected ( prereqOverrideAffected ) ) ;
264+ state . PrereqEvals . Add ( new PrerequisiteEvalRecord ( prereqFeatureFlag , flag . Key , prereqRecordDetails ) ) ;
229265 }
230266 if ( ! prereqOk )
231267 {
0 commit comments