Wiki

root/cobra/trunk/Source/BinaryOpExpr.cobra

Revision 2662, 42.2 KB (checked in by Charles.Esterbrook, 8 days ago)

Fix a compile-time bug for foo in bar where bar is a nilable array such as String[]?.

  • Property svn:eol-style set to native
Line 
1"""
2Class hierarchy:
3
4BinaryOpExpr
5    AbstractAssignExpr
6        AssignExpr
7        CoalesceAssignExpr
8        InverseCoalesceAssignExpr
9    NumericPromoExpr
10        AugAssignMathExpr
11        AugAssignBitwiseExpr
12        BinaryBitwiseExpr
13        BinaryMathExpr
14    BinaryBoolExpr
15    CompareExpr
16    DotExpr
17    InExpr
18    InheritsExpr
19    AbstractToExpr
20        ToExpr
21        ToQExpr
22    CoalesceExpr
23    InverseCoalesceExpr
24"""
25
26
27class BinaryOpExpr
28    is abstract, partial
29    inherits Expr
30
31    shared
32        def make(opToken as IToken, op as String, left as Expr, right as Expr) as BinaryOpExpr
33            """
34            Instantiates the correct class for the given operator and returns it.
35            """
36            branch op
37                on 'TO'
38                    return ToExpr(opToken, op, left, right)
39                on 'TOQ'
40                    return ToQExpr(opToken, op, left, right)
41                on 'QUESTION'
42                    return CoalesceExpr(opToken, op, left, right)
43                on 'QUESTION_EQUALS'
44                    return CoalesceAssignExpr(opToken, op, left, right)
45                on 'BANG'
46                    return InverseCoalesceExpr(opToken, op, left, right)
47                on 'BANG_EQUALS'
48                    return InverseCoalesceAssignExpr(opToken, op, left, right)
49                on 'ASSIGN'
50                    return AssignExpr(opToken, op, left, right)
51                on 'DOT'
52                    return DotExpr(opToken, op, left, right)
53                on 'INHERITS' or 'IMPLEMENTS'
54                    return InheritsExpr(opToken, op, left, right)
55                on 'IN' or 'NOTIN'
56                    return InExpr(opToken, op, left, right)
57                on 'IS' or 'ISNOT' or 'EQ' or 'NE' or 'LT' or 'GT' or 'LE' or 'GE'
58                    if left inherits CompareExpr
59                        if not left.isParened
60                            left.addComparison(op, right)
61                            return left
62                    return CompareExpr(opToken, op, left, right)
63                on 'AND' or 'OR' or 'IMPLIES'
64                    return BinaryBoolExpr(opToken, op, left, right)
65                on 'PLUS' or 'MINUS' or 'STAR' or 'STARSTAR' or 'SLASH' or 'SLASHSLASH' or 'PERCENT'
66                    return BinaryMathExpr(opToken, op, left, right)
67                on 'PLUS_EQUALS' or 'MINUS_EQUALS' or 'STAR_EQUALS' or 'STARSTAR_EQUALS' or 'SLASH_EQUALS' or 'SLASHSLASH_EQUALS' or 'PERCENT_EQUALS'
68                    return AugAssignMathExpr(opToken, op, left, right)
69                on 'AMPERSAND' or 'VERTICAL_BAR' or 'CARET' or 'DOUBLE_LT' or 'DOUBLE_GT'
70                    return BinaryBitwiseExpr(opToken, op, left, right)
71                on 'AMPERSAND_EQUALS' or 'VERTICAL_BAR_EQUALS' or 'CARET_EQUALS' or 'DOUBLE_LT_EQUALS' or 'DOUBLE_GT_EQUALS'
72                    return AugAssignBitwiseExpr(opToken, op, left, right)
73                else
74                    throw FallThroughException([opToken, op, left, right])
75
76    var _op as String
77    var _left as Expr
78    var _right as Expr
79
80    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
81        base.init(opToken)
82        _op = op
83        _left = left
84        _left.superNode = this
85        _right = right
86        _right.superNode = this
87
88    get allExprs as Expr*
89        for expr in base.allExprs, yield expr
90        for expr in .left.allExprs, yield expr
91        for expr in .right.allExprs, yield expr
92
93    get op from var
94
95    get left from var
96
97    get right from var
98
99    get willChangeVar as bool is override
100        if _left.willChangeVar, return true
101        if _right.willChangeVar, return true
102        return false
103
104    def _innerClone
105        base._innerClone
106        _left  = _left.clone  to Expr
107        _right = _right.clone to Expr
108
109    def bindImp as dynamic
110        base.bindImp
111        assert .didBindImp
112        # TODO: maybe checks should only be invoked if left and right were bound without error
113        .checkAfterBindImp
114        .checkForVoids
115        return .bindImpResult
116
117    def _bindImp
118        base._bindImp
119        assert _left is not this
120        assert _right is not this
121        # The try..catch below enables errors to be detected on both sides of the operator
122        try
123            _left.bindImp
124        catch ne1 as NodeException
125            gotError = true
126        if _left.type is nil
127            # assert _left.hasError  to-do
128            _left.type = .compiler.passThroughType
129               
130        try
131            _right.bindImp
132        catch ne2 as NodeException
133            gotError = true
134        if _right.type is nil
135            # assert _left.hasError  to-do
136            _right.type = .compiler.passThroughType
137
138        if gotError
139            _type = .compiler.passThroughType
140            # to-do CC: past an exception catching block, the type of the var (eg ne1 and ne2)
141            # must be considered nilable
142            throw NodeMultiException(ne1, ne2)
143        assert _left.didBindImp
144        assert _right.didBindImp
145
146    def checkAfterBindImp
147        """
148        Invoked after `bindImp` in order to do more error checking.
149        So this is a hook for subclasses to do error checking after
150        left and right have been successfully bound.
151        """
152        require .didBindImp
153        pass
154
155    def checkForVoids
156        left = _left
157        right = _right
158        voidType = .compiler.voidType
159        # check for void on either side of the expression
160        sugg = 'Use a different method or change that method to return something.'
161        if left.type is voidType
162            if left inherits BinaryOpExpr and (left to BinaryOpExpr).op=='DOT' and (left to BinaryOpExpr).right inherits CallExpr  # CC: axe casts
163                append = ' because "[((left to BinaryOpExpr).right to CallExpr).name]" does not return anything.'  # CC: axe BinaryOp cast
164            else
165                append = '.'
166            .throwError('There is no type for the left hand side of "[.token.text]"[append] [sugg]')
167        if right.type is voidType and not right.isCalling
168            if right inherits BinaryOpExpr and (right to BinaryOpExpr).op=='DOT' and (right to BinaryOpExpr).right inherits CallExpr  # CC: axe cast
169                append = ' because "[((right to BinaryOpExpr).right to CallExpr).name]" does not return anything.'  # CC: axe BinaryOp cast
170            else
171                append = '.'
172            .throwError('There is no type for the right hand side of "[.token.text]"[append] [sugg]')
173
174    def addSubFields
175        base.addSubFields
176        .addField('op', _op)
177        .addField('left', _left)
178        .addField('right', _right)
179
180    def toCobraSource as String is override
181        return '([_left.toCobraSource] [.token.text] [_right.toCobraSource])'
182
183
184class AbstractAssignExpr
185    is partial
186    inherits BinaryOpExpr
187    """
188    The abstract base class for AssignExpr, AugAssignExpr, CoalesceAssignExpr and InverseCoalesceAssignExpr.
189    """
190
191    var _trackLocal as bool  # for the detailed stack trace
192    var _trackName as String?
193
194    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
195        base.init(opToken, op, left, right)
196
197    get canBeStatement as bool is override
198        return true
199
200    get willChangeVar as bool is override
201        return true
202
203    def checkAfterBindImp
204        base.checkAfterBindImp
205        # can't do the following in a `_bindImp` override since type inference is implemented in
206        # a subclass (_left may not have its _definition until after that executes)
207        if .compiler.willTrackLocals
208            if _left inherits NameExpr
209                _trackLocal = _left.definition inherits AbstractLocalVar
210                if _trackLocal, _trackName = _left.namedDefinition.name
211        if .curCodeMember and not .curCodeMember inherits Initializer and _left.definition inherits BoxVar and _left.definition.isReadOnly
212            .throwError('The readonly field "[_left.definition.name]" can only be assigned to in an initializer or the field\'s declaration.')
213                       
214
215
216class AssignExpr inherits AbstractAssignExpr is partial
217
218    var _backUpIfInheritsStack as List<of IType>?
219
220    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
221        require op == 'ASSIGN'
222        base.init(opToken, op, left, right)
223        assert op == 'ASSIGN'
224
225    def _bindImp
226        try
227            base._bindImp
228        catch NodeMultiException   # TODO: try non multi error instead
229            # for a type inference situation, create a var of type passthrough for the left hand side
230            # to prevent superfluous errors in subsequent statements
231            left = _left
232            if left inherits IdentifierExpr
233                if left.definition is nil
234                    definition = LocalVar(left.token, .compiler.passThroughType).bindAll to LocalVar  # CC: axe cast when supporting 'as this'
235                    .compiler.codeMemberStack.peek.addLocal(definition)
236                    left.setDefinition(definition)
237                    left.type = definition.type
238            if _type is nil
239                _type = .compiler.passThroughType
240            throw
241        if _left inherits DotExpr and _left.definition inherits Property and _left.definition.setPart is nil
242            .throwError('Cannot assign to property "[_left.definition.name]" because it is read only.')
243        left = _left
244        right = _right
245        assert right.type
246        if left inherits IdentifierExpr
247            if left.namedDefinition is nil
248                # type inference
249                assert right.type
250                if right.type is .compiler.passThroughType  # TODO: ? change to unspecifiedType
251                    if right.hasError
252                        # for a type inference situation, create a var of type passthrough for the left hand side
253                        # to prevent superfluous errors in subsequent statements
254                        definition = LocalVar(left.token, .compiler.passThroughType).bindAll to LocalVar  # CC: axe cast when supporting 'as this'
255                        .compiler.codeMemberStack.peek.addLocal(definition)
256                        left.setDefinition(definition)
257                        left.type = definition.type
258                        _type = left.type
259                        return
260                    else
261                        .throwError('Cannot infer type for "[left.name]" because the type of the right hand expression is unknown.')
262                if left.name.startsWith('_')
263                    .throwError('No class variable named "[left.name]" exists and local variable declarations cannot start with an underscore (_).')
264                inferredType = if(right.type inherits NilType, .compiler.nilableDynamicType, right.type)  # `x = nil` for a new local var `x` is inferred as `dynamic?`
265                definition = LocalVar(left.token, inferredType).bindAll to LocalVar  # CC: axe cast when supporting 'as this'
266                .compiler.codeMemberStack.peek.addLocal(definition)
267                left.setDefinition(definition)
268                left.type = definition.type
269            if left.definition is nil
270                left.throwUnknownIdError
271            if left.definition inherits LocalVar and (left.definition to LocalVar).name.startsWithNonLowerLetter
272                (left.definition to LocalVar).isUsed = true  # suppress subsequent warning about unused variable
273                .throwError(ErrorMessages.localVariablesMustStartLowercase)
274        if not right.canBeAssignedTo(left.type to !)
275            okay = false
276            if left inherits IdentifierExpr
277                if (defi = left.namedDefinition) inherits IVar
278                    if defi.attemptAssignmentOf(right.type to !)
279                        _backUpIfInheritsStack = List<of IType>(defi.ifInheritsStack)  # will need this later for code gen
280                        okay = true
281            if not okay
282                .throwError('Incompatible types. Cannot assign value of type [right.type.name] on the right to [left.type.name] on the left.')
283        _type = left.type
284        right.contextType = _type
285
286    def checkAfterBindImp
287        base.checkAfterBindImp
288        if not .hasError and _left.definition inherits Param
289            if _right.definition inherits BoxVar
290                # the exception to this warning is when the statement is guarded by an if which references the _left.definition, as in: if param is nil; param = _param
291                # TODO: this has a test case that generates no warnings. so the cobra test cases should probably barf on unexpected warnings
292                isException = false
293                if .parent inherits BlockStmt and (.parent to BlockStmt).parent inherits IfStmt
294                    ifStmt = (.parent to BlockStmt).parent to IfStmt
295                    if (bop = ifStmt.cond) inherits BinaryOpExpr
296                        if bop.left.definition is _left.definition, isException = true
297                        else if bop.right.definition is _right.definition, isException = true
298                if not isException
299                    leftName = _left.definition.name
300                    rightName = _right.definition.name
301                    .compiler.warning(this, 'Setting a parameter ("[leftName]") to a class variable ("[rightName]") is often a mistake. You may want to reverse the assignment.')
302
303
304class NumericPromoExpr inherits BinaryOpExpr is partial
305
306    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
307        base.init(opToken, op, left, right)
308
309    get isConcated from var as bool
310
311    def _bindImp
312        base._bindImp
313        if _left.type.isDynamic or _right.type.isDynamic
314            _type = .compiler.dynamicType
315            return
316        tint = .compiler.anyIntType
317        tdecimal = .compiler.decimalType
318        tfloat = .compiler.anyFloatType
319        tpassthrough = .compiler.passThroughType
320        tstring = .compiler.stringType
321        cannotMix = false
322        leftType = _left.type.nonNil
323        rightType = _right.type.nonNil
324
325        tgcd = leftType.greatestCommonDenominatorWith(rightType)
326
327        if leftType.isDescendantOf(tint)
328            if rightType.isDescendantOf(tint)
329                # special case: true division
330                # if both sides are ints, the type becomes decimal
331                if _op == 'SLASH'
332                    _type = .compiler.numberType
333                else
334                    _type = tgcd
335            else
336                cannotMix = _ifRightTypeThen(tdecimal, tdecimal, tfloat, tgcd)
337        else if leftType.isDescendantOf(tdecimal)
338            cannotMix = _ifRightTypeThen(tdecimal, tdecimal, tint, tdecimal)
339        else if leftType.isDescendantOf(tfloat)
340            cannotMix = _ifRightTypeThen(tfloat, tgcd, tint, tgcd)
341        else if leftType.isDescendantOf(tstring)
342            cannotMix = _ifRightTypeThen(tstring, tstring)
343        else if leftType inherits Box
344            if leftType.isGeneric and leftType.isDescendantOf(.compiler.collectionOfType.constructedTypeFor([leftType.genericParams[0]]))
345                if leftType.genericParams.count > 1
346                    .throwError('Cannot concatenate [leftType.name] on the left side of "+" because it has more than one generic parameter.')
347                if rightType inherits Box
348                    if rightType.isGeneric and rightType.isDescendantOf(.compiler.enumerableOfType.constructedTypeFor([rightType.genericParams[0]]))
349                        if rightType.genericParams.count > 1
350                            .throwError('Cannot concatenate [rightType.name] on the left side of "+" because it has more than one generic parameter.')
351                        leftInner = leftType.genericParams[0]
352                        rightInner = rightType.genericParams[0]
353                        newInner = leftInner.greatestCommonDenominatorWith(rightInner)
354                        _type = leftType.genericDef.constructedTypeFor([newInner])
355                        _isConcated = true
356                    else if rightType.isDescendantOf(.compiler.enumerableType)
357                        # casting will be done at run-time and either succeed or throw an exception
358                        _type = leftType
359                        _isConcated = true
360                    else
361                        cannotMix = true
362                else
363                    cannotMix = true           
364        if _type is nil
365            if _left.isKindOf(tpassthrough) and not _right.isKindOf(tpassthrough)
366                _type = rightType
367            else if _right.isKindOf(tpassthrough) and not _left.isKindOf(tpassthrough)
368                _type = leftType
369            else if _left.isKindOf(tpassthrough) or _right.isKindOf(tpassthrough)
370                _type = tpassthrough
371            else if leftType == rightType and leftType inherits EnumDecl
372                _type = leftType
373            else if cannotMix
374                .throwError('Cannot mix types [_left.type.name] and [_right.type.name] for arithmetic.')
375            if _type is nil
376                sugg = 'Try finding a method that performs this function.'
377                if leftType == rightType
378                    .throwError('Cannot apply [_op] to [leftType.name]. [sugg]')
379                else
380                    .throwError('Cannot apply [_op] to [leftType.name] and [rightType.name]. [sugg]')
381
382    def _ifRightTypeThen(ifThenPairs as vari IType) as bool
383        rightType = _right.type.nonNil
384        i = 0
385        while i < ifThenPairs.length
386            matchType, resultType = ifThenPairs[i], ifThenPairs[i+1]
387            i += 2
388            if rightType.isDescendantOf(matchType)
389                _type = resultType
390                return false
391        return true     
392                       
393   
394
395class AugAssignMathExpr inherits NumericPromoExpr is partial
396    """
397    TODO: How should:
398        someInt /= someInt
399    be handled given that
400        someInt / someInt
401    gives a decimal?
402    """
403
404    # TODO: does not inherit AbstractAssignExpr.
405    # TODO: Does not participate in -detailed-stack-trace
406
407    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
408        base.init(opToken, op, left, right)
409        assert op in ['PLUS_EQUALS', 'MINUS_EQUALS', 'STAR_EQUALS', 'STARSTAR_EQUALS', 'SLASH_EQUALS', 'SLASHSLASH_EQUALS', 'PERCENT_EQUALS']
410
411    get canBeStatement as bool is override
412        return true
413
414    get willChangeVar as bool is override
415        return true
416
417    def _bindImp
418        base._bindImp
419        # TODO: does NumericPromoExpr cover everything we need?
420        # C# and other languages use += and -= on delegates and events, but Cobra does not.
421        if .left.type inherits MethodSig
422            # TODO: error
423            pass
424        else if .left.type.nonNil.isDescendantOf(.compiler.delegateType)
425            leftSource = .left.toCobraSource
426            rightSource = .right.toCobraSource
427            if not .right inherits RefExpr, rightSource = 'ref ' + rightSource
428            branch .op
429                on 'PLUS_EQUALS'
430                    msg = 'Cannot use "+=". '
431                    if .left.definition inherits BoxEvent, msg += 'Use "listen [leftSource], [rightSource]" for events.'
432                    else, msg += 'Use "[leftSource] = Delegate.combine([leftSource], [rightSource])" for delegates.'
433                on 'MINUS_EQUALS'
434                    msg = 'Cannot use "-=". '
435                    if .left.definition inherits BoxEvent, msg += 'Use "ignore [leftSource], [rightSource]" for events.'
436                    else, msg += 'Use "[leftSource] = Delegate.remove([rightSource])" for delegates.'
437                else
438                    msg = 'Cannot use the operator "[.token.text]" on events or delegates.'
439            .throwError(msg)
440
441class AugAssignBitwiseExpr
442    is partial
443    inherits NumericPromoExpr
444    """
445    """
446
447    # TODO: does not inherit AbstractAssignExpr. Does not participate in super stack trace
448
449    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
450        base.init(opToken, op, left, right)
451        assert op in ['AMPERSAND_EQUALS', 'VERTICAL_BAR_EQUALS', 'CARET_EQUALS', 'DOUBLE_LT_EQUALS', 'DOUBLE_GT_EQUALS']
452
453    get canBeStatement as bool is override
454        return true
455
456    get willChangeVar as bool is override
457        return true
458
459    def _bindImp
460        base._bindImp
461        # TODO: does NumericPromoExpr cover everything we need?
462
463
464class BinaryBoolExpr
465    is partial
466    inherits BinaryOpExpr
467
468    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
469        base.init(opToken, op, left, right)
470        assert op in ['AND', 'OR', 'IMPLIES']
471
472    def _bindImp
473        base._bindImp
474        _type = .compiler.boolType
475        if _left.type is not .compiler.boolType
476            _left = TruthExpr(_left).bindImp to TruthExpr # CC: axe cast after "as this"
477        if _right.type is not .compiler.boolType
478            _right = TruthExpr(_right).bindImp to TruthExpr # CC: axe cast after "as this"
479
480
481class BinaryBitwiseExpr
482    is partial
483    inherits NumericPromoExpr
484
485    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
486        base.init(opToken, op, left, right)
487        assert op in ['AMPERSAND', 'VERTICAL_BAR', 'CARET', 'DOUBLE_LT', 'DOUBLE_GT']
488       
489    def _bindImp
490        base._bindImp
491
492
493class BinaryMathExpr
494    is partial
495    inherits NumericPromoExpr
496
497    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
498        base.init(opToken, op, left, right)
499        assert op in ['PLUS', 'MINUS', 'STAR', 'STARSTAR', 'SLASH', 'SLASHSLASH', 'PERCENT']
500
501    def _bindImp
502        base._bindImp
503        if .isConcated and .type, return
504        lt = _left.type.nonNil
505        rt = _right.type.nonNil
506        if lt is rt
507            assert _type == lt or (_type is .compiler.numberType and _op=='SLASH')
508        else if lt.isDynamic or rt.isDynamic
509            _type = .compiler.dynamicType
510        else if lt inherits PassThroughType
511            _type = rt
512        else if rt inherits PassThroughType
513            _type = lt
514        else
515            # TODO: this will need to be more sophisticated in the future with different sized ints and floats
516            if lt.isDescendantOf(.compiler.anyIntType)
517                _type = rt
518            else if rt.isDescendantOf(.compiler.anyIntType)
519                _type = lt
520            else if lt.isDescendantOf(.compiler.decimalType)
521                if rt.isDescendantOf(.compiler.anyFloatType)
522                    .throwError('Cannot promote between decimal and float for operator "[_op]". Decimal is more accurate and float has more range.')
523                else
524                    # int case already taken care of, so...
525                    .throwError('Cannot promote between decimal and "[rt.name]".')
526            else if lt.isDescendantOf(.compiler.anyFloatType)
527                if rt.isDescendantOf(.compiler.decimalType)
528                    .throwError('Cannot promote between float and decimal for operator "[_op]". Decimal is more accurate and float has more range.')
529                else
530                    # int case already taken care of, so...
531                    .throwError('Cannot promote between decimal and "[rt.name]".')
532            else
533                throw FallThroughException({'this': this, '_left': _left, 'op': _op, 'lt': lt, 'rt': rt})
534
535
536class CompareExpr
537    is partial
538    inherits BinaryOpExpr
539
540    var _items as List<of Expr>
541    var _operations as List<of String>
542
543    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
544        require op in ['IS', 'ISNOT', 'EQ', 'NE', 'LT', 'GT', 'LE', 'GE']
545        base.init(opToken, op, left, right)
546        _items = [left, right]
547        _operations = [op]
548        if op == 'ISNOT' and .token.text == 'is'
549            .token.text = 'is not'
550
551    def addComparison(op as String, right as Expr)
552        _operations.add(op)
553        _items.add(right)
554
555    def _bindImp
556        base._bindImp
557        _type = .compiler.boolType
558        if _items.count > 2
559            # this transformation is done so that the back ends handle chained comparisons differently than normal comparisons
560            cce = ChainedCompareExpr(.token, _items, _operations)
561            cce.bindImp
562            _transformTo(cce)
563            return
564        if _left.type is nil or _right.type is nil
565            assert .hasError
566            return
567        leftType = _left.type to !
568        rightType = _right.type to !
569        innerMsg as String?
570        # cannot invoke .isAssignableTo directly on the types because if one is nilable and the other is not, you can get false, even though that doesn't apply for "is". So use isEquatableTo and isComparableTo
571        if .op in ['EQ', 'NE', 'IS', 'ISNOT']
572            if not leftType.isEquatableTo(rightType)
573                innerMsg = if(.op.startsWith('IS'), 'can never be identical', 'cannot be equated')
574            else if .op in ['IS', 'ISNOT']
575                # Essentially "is" and "is not" are for reference types. Give a warning when used for value types.
576                if not leftType.isDynamicOrPassThrough and not rightType.isDynamicOrPassThrough
577                    opName = if(.op=='IS', 'is', 'is not')
578                    altName = if(.op=='IS', '==', '<>')
579                    if leftType inherits NilableType and rightType inherits NilType
580                        pass
581                    else if leftType inherits NilType and rightType inherits NilableType
582                        pass
583                    else if not leftType.isReference and not rightType.isReference
584                        .compiler.warning(this, 'Both the left and right sides of "[opName]" are value types ("[leftType.name]" and "[rightType.name]"), but "[opName]" applies to reference types. Use "[altName]" instead.')
585                    # interestingly, I don't think the following ever happen in practice...
586                    else if not leftType.isReference and not rightType.isNilableAndDescendantOf(leftType)
587                        .compiler.warning(this, 'The left side of "[opName]" is a value type ("[leftType.name]") while the right side is an incompatible reference type ("[rightType.name]").')
588                    else if not rightType.isReference and not leftType.isNilableAndDescendantOf(rightType)
589                        .compiler.warning(this, 'The right side of "[opName]" is a value type ("[rightType.name]") while the left side is an incompatible reference type ("[leftType.name]").')                     
590                    else if .left.isObjectLiteral or .right.isObjectLiteral
591                        .compiler.warning(this, 'Do not use the identity operator "[opName]" with an object literal. Use an equality operator such as "==" or "<>".')
592        else
593            if not leftType.isComparableTo(rightType)
594                innerMsg = 'cannot be compared'
595        if innerMsg
596            typeNames = if(_left.type.name==_right.type.name, 'type ("[_left.type.name]")', 'types ("[_left.type.name]" and "[_right.type.name]")')
597            msg = 'The left and right sides of the "[.token.text]" expression [innerMsg] because of their [typeNames].'
598            if .op == 'IS' and _right.type.isDescendantOf(.compiler.typeType) # C#
599                msg += ' Maybe you should try "inherits" instead of "is".'
600            .throwError(msg)
601
602    def _isObjectLiteral(expr as Expr) as bool
603        if expr inherits StringLit, return true
604        if expr inherits CompositeLiteral, return true
605        return false
606
607interface IDotRightExpr
608    inherits IExpr
609    """
610    IDotRightExpr is an expression that can appear on the right hand side of a DotExpr.
611    Implemented by MemberExpr and CallExpr.
612    """
613    get name as String
614    get memberDefinition as IMember?
615    get args as List<of Expr>
616
617
618class DotExpr inherits BinaryOpExpr is partial
619    implements IPotentialTypeExpr
620    """
621    When creating a DotExpr, set .isImplicit to true if the original expression did not actually
622    contain a dot (.), but only implied it. For example: `_foo()`
623   
624    When DotExpr's are chained as in "a.b.c.d", the .right will always be an
625    IDotRightExpr (MemberExpr or CallExpr) and the .left will generally by the DotExpr
626    (except for the first one).
627   
628    a.b.c.d ==>
629   
630              .
631             / \
632            .   d
633           / \
634          .   c
635         / \
636        a   b
637    """
638
639    var _dotRightExpr as IDotRightExpr
640
641    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
642        require
643            op == 'DOT'
644            right inherits IDotRightExpr 
645        body
646            base.init(opToken, op, left, right)
647            _dotRightExpr = right to IDotRightExpr
648
649    def addMinFields
650        base.addMinFields
651        .addField('left.toCobraSource', .safeLeftToCobraSource)
652        .addField('right.toCobraSource', .safeRightToCobraSource)
653
654    def safeLeftToCobraSource as String
655        try
656            return .left.toCobraSource
657        catch exc as Exception
658            return exc.toString
659   
660    def safeRightToCobraSource as String
661        try
662            return .right.toCobraSource
663        catch exc as Exception
664            return exc.toString
665
666    get canBeStatement as bool is override
667        return true
668
669    get definition is override
670        """
671        The definition of a DotExpr is the definition of its .right which will be
672        a CallExpr or MemberExpr (via the interface IDotRightExpr).
673        """
674        return .right.definition
675
676    get dotRight as IDotRightExpr
677        """
678        Returns .right but with the more narrow type of IDotRightExpr.
679        """
680        return _dotRightExpr
681
682    def _bindImp
683        base._bindImp
684        if _left inherits BaseLit
685            .compiler.curCodeMember.usesBase  # use of base implies override if the containing member is not already marked new
686        else if _left inherits ThisLit
687            if not _left.isImplicit
688                .compiler.warning(this, 'An explicit "this" literal is unnecessary for accessing members of the current object. You can remove "this".')
689            else if not .isImplicit and .dotRight.name.startsWith('_')
690                .compiler.warning(this, 'An explicit dot (".") is unnecessary for accessing underscored members of the current object. You can remove ".".')
691        if not _type
692            assert _right.type
693            _type = _right.type
694        if _left.type.isDynamic
695            _type = _right.type
696            if not _type.isDynamic
697                # interesting example: d.getType
698                # where `d` is dynamic. The resulting type, however, is the .typeType because `getType` is recognized as a method of Object
699                _contextType = _type
700        else if not _left.hasError and not _right.hasError
701            if _left inherits IdentifierExpr  # TODO: seems flawed. The error below should take place if left is a DotExpr
702                if _left.namedDefinition inherits Box # CC: combine with above if statement
703                    if _dotRightExpr.memberDefinition is not nil and not _dotRightExpr.memberDefinition.isShared  # when _dotRightExpr.memberDefinition is nil, it's a nested type
704                        if _dotRightExpr.name == 'init'
705                            # hack to support Application.init in ../HowTo/390-GTK.cobra
706                            # a better hack would be to de the correct binding to the shared init method
707                            pass
708                        else
709                            .throwError('Cannot access instance member "[_dotRightExpr.name]" on type "[_left.name]". Make the member "shared" or create an instance of the type.')
710            if _left inherits ThisOrBaseLit
711                if .curCodeMember and .curCodeMember.isShared and not _dotRightExpr.memberDefinition.isShared  # .curCodeMember is nil when bind imp'ing on var init exprs like "var _x = someExpr"
712                    .throwError('Cannot access instance member "[_dotRightExpr.name]" from the current shared member. Make the member "shared" or create an instance of the type.')
713            # Check for qualified type such as Foo.Bar
714            if .definition implements IType and .type.isDescendantOf(.compiler.typeType)
715                # A.B where A and B are namespaces or types
716                _transformTo(TypeExpr(.token, .definition to IType).bindImp)
717            else if (right = .right) inherits CallExpr
718                if right.memberDefinition implements IType
719                    # A.B.C() where C is a type such as a class or struct
720                    te = TypeExpr(.token, right.memberDefinition to IType)
721                    te.bindImp
722                    pc = PostCallExpr(right.token, te, right.args)
723                    pc.bindImp
724                    _transformTo(pc)
725            else
726                assert not _dotRightExpr.memberDefinition inherits MemberOverload or .compiler.refExprLevel > 0
727        # _checkLeftHandNilable -- not ready  for this yet
728   
729    def _checkLeftHandNilable
730        if _left inherits IdentifierExpr
731            defi = _left.namedDefinition           
732            if defi inherits AbstractLocalVar
733                if _left.type inherits NilableType and not _left.type.isDynamic
734                    .compiler.warning(this, 'The receiver "[_left.toCobraSource]" may be nil which will cause an exception. You can guard against this with an "if" statement, an assignment, a coalesce or by changing the type.')
735
736    def _help(hg as HelpGenerator) is override
737        type = nil
738        try
739            type = .left.receiverType ? .left.type
740        catch
741            pass
742        if type
743            terms = '[type.name] [.dotRight.name]'
744            hg.searchTerms.add(terms)
745            ns = type.parentNameSpace
746            if ns
747                if not ns.isRoot, hg.searchTerms.add(ns.fullName + ' ' + terms)
748            else if type inherits Box
749                pb = type.parentBox
750                if pb, hg.searchTerms.add(pb.qualifiedName + ' ' + terms)
751            _helpType(hg, type)
752        else
753            hg.searchTerms.add(.dotRight.name)
754        base._help(hg)
755   
756    def afterStatementBindImp
757        base.afterStatementBindImp
758        if not .hasError
759            # TODO: inheritance
760            # TODO: test on DLLs
761            member = _dotRightExpr.memberDefinition
762            if member inherits MemberOverload
763                member = member.members[0]
764            if member inherits BoxMember
765                post while member
766                    found = false
767                    for attrib in member.attributes
768                        if attrib.name in ['MustUseResult', 'MustUseResultAttribute']
769                            found = true
770                            break
771                    if found
772                        break
773                    member = member.matchingBaseMember
774            if found
775                .compiler.warning(this, 'The result of "[member.name]" should be used in an expression such as assignment or passing arguments (as indicated by its MustUseResult attribute).')
776
777    get potentialType as IType?
778        # overridden to return the type this dotted expr represents in those cases when it does represent a type
779        assert .didBindImp
780        if .definition inherits IType
781            return .definition
782        else
783            return nil
784
785    def toCobraSource as String is override
786        if _left inherits ThisLit
787            return '.[_right.toCobraSource]'
788        else
789            return '[_left.toCobraSource].[_right.toCobraSource]'
790
791
792class InExpr inherits BinaryOpExpr is partial
793    """
794    Syntax:
795        <left> in <right>
796        <left> not in <right>
797    """
798
799    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
800        base.init(opToken, op, left, right)
801        assert op in ['IN', 'NOTIN']
802
803    get containsExpr from var as Expr?
804
805    get needsNilCheck from var as bool
806   
807    def _bindImp
808        base._bindImp
809        _type = .compiler.boolType
810        if not .hasError
811            if .right.type.innerType is nil
812                .recordError('The right-hand expression for "in" is of type "[.right.type.name]" which contains nothing.')
813            else if .left.type.nonNil inherits CharType and .right.type is .compiler.stringType
814                # special case because there is no String.contains that takes a char
815                # by not setting _contains, the code gen will use CobraImp.In
816                pass
817            else if not _canContain(.right.type to !, .left.type to !)
818                .recordError('The left expression ([.left.toCobraSource]) cannot be "in" the right-hand expression because the left type is "[.left.type.name]" and the right-hand expression contains type "[.right.type.innerType.name]".')
819            else if _right.type.nonNil inherits ArrayType
820                pass
821            else if not _right.type.isDynamic
822                # try to use right.contains(left)
823                # this is more direct and offers speed up on some classes like Set
824                contains = DotExpr(.token, 'DOT', _right, CallExpr(.token, 'contains', List<of Expr>([_left]), true, isImplicit=true))
825                try
826                    contains.bindImp
827                catch SourceException
828                    pass
829                success
830                    # TODO: Not sure what's going on here. Cobra thinks these have "contains" and C# says no way
831                    # error: "System.Collections.Generic.Dictionary<string,int>.KeyCollection" does not contain a definition for "Contains" (C#)
832                    # error: "System.Collections.Generic.Dictionary<string,int>.ValueCollection" does not contain a definition for "Contains" (C#)
833                    if contains.type is .compiler.boolType and not _right.type.name.startsWith('KeyCollection<') and not _right.type.name.startsWith('ValueCollection<')
834                        _containsExpr = contains
835                # optimize `key in dict.keys` to `dict.containsKey(key)` which eliminates a linear search
836                if _containsExpr is nil
837                    if _right inherits DotExpr
838                        if _right.right inherits MemberExpr and _
839                            (_right.right to MemberExpr).name == 'keys' and _
840                            _right.left.type inherits Box and _
841                            (_right.left.type to Box).isIndirectConstructionOf(.compiler.idictionaryOfType)
842                            # then
843                            contains = DotExpr(.token, 'DOT', _right.left, CallExpr(.token, 'containsKey', List<of Expr>([_left]), true, isImplicit=true))
844                            contains.bindImp
845                            _containsExpr = contains
846
847    def _canContain(containerType as IType, whatType as IType) as bool
848        it = containerType.innerType
849        if it and _isAssignableTo(whatType, it to !), return true
850        contains = containerType.memberForName('contains')
851        # TODO: handle method overloads
852        if contains inherits Method
853            if contains.params.count == 1 and _isAssignableTo(whatType, contains.params[0].type)
854                return true
855        return false
856
857    def _isAssignableTo(a as IType, b as IType) as bool
858        if a.isAssignableTo(b), return true
859        if a inherits NilableType
860            if a.theWrappedType.isAssignableTo(b)
861                _needsNilCheck = true
862                return true
863        return false
864
865
866class InheritsExpr inherits BinaryOpExpr is partial
867
868    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
869        base.init(opToken, op, left, right)
870        assert op in ['INHERITS', 'IMPLEMENTS']
871       
872    def _bindImp
873        base._bindImp
874        _type = .compiler.boolType
875        if _right inherits IPotentialTypeExpr
876            rightType = _right.potentialType
877            if rightType
878                leftType = _left.type
879                if leftType
880                    okay = true
881                    if leftType.isDynamic
882                        pass
883                    else if leftType.isDescendantOf(rightType)
884                        .compiler.warning(this, 'The expression is always of type "[.right.toCobraSource]".')
885                    else if rightType.isDescendantOf(leftType)
886                        # ex: someShape inherits Circle
887                        pass
888                    else if leftType inherits NilableType and not rightType inherits NilableType and rightType.isStrictDescendantOf(leftType.nonNil)
889                        # ex: someNilableShape inherits Circle
890                        pass
891                    else if leftType inherits NilableType and not rightType inherits NilableType and leftType.nonNil.isDescendantOf(rightType)
892                        # ex: someNilableCircle inherits Shape
893                        .compiler.warning(this, 'The expression will always be of type "[.right.toCobraSource]" when not nil. You can just check for not nil by removing "inherits [.right.toCobraSource]".')
894                    else if rightType inherits Interface
895                        if leftType inherits Class
896                            pass
897                        else if leftType inherits Struct
898                            found = false
899                            for leftInterface in leftType.baseInterfaces
900                                if leftInterface.isDescendantOf(rightType)
901                                    .compiler.warning(this, 'The expression is always of type "[.right.toCobraSource]".')
902                                    found = true
903                                    break
904                            if not found
905                                .compiler.warning(this, 'The expression (of type "[leftType.name]") is never of type "[.right.toCobraSource]".')
906                        else if leftType inherits Interface
907                            # inheriting interface IA gives no information about what other interfaces objects of type IA inherit at run-time
908                            pass
909                    else
910                        okay = false
911                    if not okay
912                        .compiler.warning(this, 'The expression (of type "[leftType.name]") is never of type "[.right.toCobraSource]".')
913        defi = .right.definition
914        if defi inherits Class
915            if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against classes.')
916        else if defi inherits Struct
917            if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against structs.')
918        else if defi inherits Interface
919            # got annoying:
920            # if .op <> 'IMPLEMENTS', .throwError('Use the "implements" operator when testing objects against interfaces.')
921            pass
922        else if defi inherits GenericParam
923            pass
924        else if defi inherits IType  # int, decimal, etc.
925            if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against structs.')
926        else if defi
927            .throwError('Cannot test "[.op.toLower]" against a [defi.englishName].')
928
929
930class AbstractToExpr
931    is abstract
932    inherits BinaryOpExpr
933
934    var _potentialTypeExpr as IPotentialTypeExpr
935
936    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
937        base.init(opToken, op, left, right)
938        assert right inherits IPotentialTypeExpr
939        _potentialTypeExpr = right to IPotentialTypeExpr
940
941    def _bindImp
942        base._bindImp
943        if not _right.isKindOf(.compiler.typeType) and not _right.type inherits Box
944            .throwError('The "to" operator does not apply to "[_right.toCobraSource]". Use a type.')
945        _type = _potentialTypeExpr.potentialType
946        if _type is nil
947            .throwError('The expression "[_potentialTypeExpr.toCobraSource]" does not identify a type.')
948        # TODO: not quite ready for the following
949        #       at the very least, need to enhance CobraType and descendants to have a superType of .objectType
950        # test case is 208-to-errors.cobra with substring 'can never be cast to a'
951        #else if _left.type and not _type.isDescendantOf(_left.type to !)
952        #   .throwError('The given expression, of type "[_left.type.name]", can never be cast to a "[_potentialTypeExpr.toCobraSource]".')
953
954
955class ToExpr
956    is partial
957    inherits AbstractToExpr
958
959    var _rightTypeExpr as IPotentialTypeExpr
960
961    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
962        base.init(opToken, op, left, right)
963        assert right inherits IPotentialTypeExpr
964        _rightTypeExpr = right to IPotentialTypeExpr
965
966    def _bindImp
967        base._bindImp
968        if _left inherits NilLiteral and not (_type inherits NilableType or _type inherits PassThroughType)
969            .throwError('Cannot cast nil to a non-nil type.')
970        rightType = _rightTypeExpr.potentialType
971        if rightType
972            if _left.type inherits NilableType and not rightType inherits NilableType
973                if _left.type.nonNil == rightType
974                    .compiler.warning(this, 'The given expression is already a "[_rightTypeExpr.toCobraSource]", but nilable. You can just use "to !".')
975            else if not _left.type inherits NilableType and rightType inherits NilableType
976                if rightType.nonNil == _left.type
977                    .compiler.warning(this, 'The given expression is already a "[_left.type.name]", but not nilable. You can just use "to ?".')
978            else if _left.type == rightType
979                .compiler.warning(this, 'The given expression is already a "[_rightTypeExpr.toCobraSource]" so the typecast is redundant. You can remove it.')
980            else if rightType inherits AbstractNumberType and _left.type.isDescendantOf(.compiler.stringType)
981                typeName = rightType.name
982                .recordError('Cannot cast a string to a numeric type. Consider using "[typeName].parse" or "[typeName].tryParse". Use "@help [typeName]" for details.')
983        else
984            .throwError('Cannot locate a type for the type cast "to [_rightTypeExpr.toCobraSource]".')
985
986
987class ToQExpr inherits AbstractToExpr is partial
988
989    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
990        base.init(opToken, op, left, right)
991
992    def _bindImp
993        base._bindImp
994        if not _type inherits NilableType
995            _type = .typeProvider.nilableType(_type to !)
996        # warn about redundancy
997        if not .left.hasError and not .right.hasError
998            rightTypeExpr = _right to IPotentialTypeExpr
999            rightType = rightTypeExpr.potentialType
1000            if rightType
1001                if _left.type inherits NilableType and not rightType inherits NilableType
1002                    if _left.type.nonNil == rightType
1003                        .compiler.warning(this, 'The given expression is already a "[rightTypeExpr.toCobraSource]", but nilable. You can use "to !" to remove the nilability or remove the redundant cast.')
1004                else if not _left.type inherits NilableType and rightType inherits NilableType
1005                    pass # TODO: check rhs type is castable to lhs regardless of nilability
1006                else if _left.type == rightType
1007                    .compiler.warning(this, 'The given expression is already a "[rightTypeExpr.toCobraSource]" so the typecast is redundant. You can remove it.')
1008            else
1009                .throwError('Cannot locate a type for the type cast "to? [rightTypeExpr.toCobraSource]".')
1010
1011
1012class CoalesceExpr
1013    is partial
1014    inherits BinaryOpExpr
1015    """
1016    x ? y ==> if(x is nil, y, x)
1017    but without the potential double evaluation of x, which could be a complex expression.
1018    """
1019
1020    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
1021        base.init(opToken, op, left, right)
1022
1023    def _bindImp
1024        base._bindImp
1025        ptt = .compiler.passThroughType
1026        if _left.type is ptt or _right.type is ptt
1027            # x ? y   ...where either is typed as passthrough
1028            _type = ptt
1029        else if not _left.type inherits NilableType 
1030            .throwError('The left hand type of "?" is not nilable (it\'s "[_left.type.name]") so the expression will always evaluate to the left hand side.')
1031        else if _left.type inherits NilableType and not _right.type inherits NilableType
1032            # x ? y   ...where x can be nil, but y cannot
1033            _type = _left.type.nonNil  # TODO: should be greatest common denominator between the two
1034        else
1035            # the catch all case
1036            _type = _left.type  # TODO: should be greatest common denominator between the two
1037
1038
1039class InverseCoalesceExpr
1040    is partial
1041    inherits BinaryOpExpr
1042    """
1043    x ! y ==> if(x is not nil, y, x)
1044    but without the potential double evaluation of x, which could be a complex expression.
1045    """
1046
1047    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
1048        base.init(opToken, op, left, right)
1049
1050    def _bindImp
1051        base._bindImp
1052        left, right = .left, .right
1053        ptt = .compiler.passThroughType
1054        if left.type is ptt or right.type is ptt
1055            # x ! y   ...where either is typed as passthrough
1056            _type = ptt
1057        else if not left.type inherits NilableType
1058            .throwError('The left hand type of "!" is not nilable (it\'s "[left.type.name]") so the expression will always evaluate to the right hand side.')
1059# TODO?
1060#       else if _right.type inherits NilableType and not _right.type inherits NilableType
1061#           # x ? y   ...where x can be nil, but y cannot
1062#           _type = _left.type.nonNil  # TODO: should be greatest common denominator between the two
1063        else
1064            # the catch all case
1065            _type = left.type.greatestCommonDenominatorWith(right.type to !)
1066
1067
1068class CoalesceAssignExpr
1069    is partial
1070    inherits AbstractAssignExpr
1071    """
1072    x ?= y
1073    """
1074
1075    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
1076        base.init(opToken, op, left, right)
1077
1078    def _bindImp
1079        base._bindImp
1080        if not _left.type inherits NilableType 
1081            .throwError('The left hand type of "?=" is not nilable (it\'s "[_left.type.name]") so the expression will always evaluate to the left hand side.')
1082        # TODO: error check that the right hand type is not nilable?
1083        _type = _right.type
1084
1085
1086class InverseCoalesceAssignExpr
1087    is partial
1088    inherits AbstractAssignExpr
1089    """
1090    x != y
1091    """
1092
1093    cue init(opToken as IToken, op as String, left as Expr, right as Expr)
1094        base.init(opToken, op, left, right)
1095
1096    def _bindImp
1097        base._bindImp
1098        left, right = .left, .right
1099        if not left.type inherits NilableType
1100            correct = 'bang-equals' in .compiler.options.setValue('correct-source')
1101            if correct and left.type.isComparableTo(right.type to !)
1102                .compiler.correctSource(.token, '<>')
1103                _transformTo(CompareExpr(.token.copy('NE', '<>'), 'NE', left, right))
1104                return
1105            else
1106                .throwError('The left hand type of "!=" is not nilable (it\'s "[left.type.name]") so the expression will always evaluate to the right hand side. If you meant "does not equal" then use "<>". "a != b" means "a = a ! b" where "!" is an operator concerning nil.')
1107        _type = left.type.greatestCommonDenominatorWith(right.type to !)
Note: See TracBrowser for help on using the browser.