@ -24,9 +24,8 @@ from .baseobjects import (
MutableInterpreterObject ,
InterpreterObjectTypeVar ,
ObjectHolder ,
RangeHolder ,
IterableObject ,
TYPE_elementary ,
TYPE_var ,
TYPE_kwargs ,
@ -42,7 +41,7 @@ from .exceptions import (
BreakRequest
)
from . decorators import FeatureNew , noKwargs
from . decorators import FeatureNew
from . disabler import Disabler , is_disabled
from . helpers import default_resolve_key , flatten , resolve_second_level_holders
from . operator import MesonOperator
@ -51,7 +50,6 @@ from ._unholder import _unholder
import os , copy , re , pathlib
import typing as T
import textwrap
from functools import wraps
if T . TYPE_CHECKING :
# T.cast is not handled by flake8 to detect quoted annotation use
@ -64,6 +62,8 @@ HolderMapType = T.Dict[
T . Type [ int ] ,
T . Type [ bool ] ,
T . Type [ str ] ,
T . Type [ list ] ,
T . Type [ dict ] ,
] ,
# For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar]
T . Callable [ [ InterpreterObjectTypeVar , ' Interpreter ' ] , ObjectHolder [ InterpreterObjectTypeVar ] ]
@ -74,21 +74,7 @@ FunctionType = T.Dict[
T . Callable [ [ mparser . BaseNode , T . List [ TYPE_var ] , T . Dict [ str , TYPE_var ] ] , TYPE_var ]
]
__FN = T . TypeVar ( ' __FN ' , bound = T . Callable [ [ ' InterpreterBase ' , T . Any ] , T . Union [ TYPE_var , InterpreterObject ] ] )
def _holderify_result ( types : T . Union [ None , T . Type , T . Tuple [ T . Type , . . . ] ] = None ) - > T . Callable [ [ __FN ] , __FN ] :
def inner ( f : __FN ) - > __FN :
@wraps ( f )
def wrapper ( self : ' InterpreterBase ' , node : mparser . BaseNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
res = f ( self , node )
if types is not None and not isinstance ( res , types ) :
raise mesonlib . MesonBugException ( f ' Expected { types } but got object ` { res } ` of type { type ( res ) . __name__ } ' )
return self . _holderify ( res )
return T . cast ( __FN , wrapper )
return inner
class InterpreterBase :
elementary_types = ( list , )
def __init__ ( self , source_root : str , subdir : str , subproject : str ) :
self . source_root = source_root
self . funcs : FunctionType = { }
@ -99,8 +85,7 @@ class InterpreterBase:
self . subdir = subdir
self . root_subdir = subdir
self . subproject = subproject
# TODO: This should actually be more strict: T.Union[TYPE_elementary, InterpreterObject]
self . variables : T . Dict [ str , T . Union [ TYPE_var , InterpreterObject ] ] = { }
self . variables : T . Dict [ str , InterpreterObject ] = { }
self . argument_depth = 0
self . current_lineno = - 1
# Current node set during a function call. This can be used as location
@ -190,7 +175,7 @@ class InterpreterBase:
raise e
i + = 1 # In THE FUTURE jump over blocks and stuff.
def evaluate_statement ( self , cur : mparser . BaseNode ) - > T . Optional [ T . Union [ TYPE_var , InterpreterObject ] ] :
def evaluate_statement ( self , cur : mparser . BaseNode ) - > T . Optional [ InterpreterObject ] :
self . current_node = cur
if isinstance ( cur , mparser . FunctionNode ) :
return self . function_call ( cur )
@ -238,20 +223,18 @@ class InterpreterBase:
raise ContinueRequest ( )
elif isinstance ( cur , mparser . BreakNode ) :
raise BreakRequest ( )
elif isinstance ( cur , self . elementary_types ) :
return cur
else :
raise InvalidCode ( " Unknown statement. " )
return None
def evaluate_arraystatement ( self , cur : mparser . ArrayNode ) - > T . List [ T . Union [ TYPE_var , InterpreterObject ] ] :
def evaluate_arraystatement ( self , cur : mparser . ArrayNode ) - > InterpreterObject :
( arguments , kwargs ) = self . reduce_arguments ( cur . args )
if len ( kwargs ) > 0 :
raise InvalidCode ( ' Keyword arguments are invalid in array construction. ' )
return arguments
return self . _holderify ( [ _unholder ( x ) for x in arguments ] )
@FeatureNew ( ' dict ' , ' 0.47.0 ' )
def evaluate_dictstatement ( self , cur : mparser . DictNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
def evaluate_dictstatement ( self , cur : mparser . DictNode ) - > InterpreterObject :
def resolve_key ( key : mparser . BaseNode ) - > str :
if not isinstance ( key , mparser . StringNode ) :
FeatureNew . single_use ( ' Dictionary entry using non literal key ' , ' 0.53.0 ' , self . subproject )
@ -261,17 +244,13 @@ class InterpreterBase:
return str_key
arguments , kwargs = self . reduce_arguments ( cur . args , key_resolver = resolve_key , duplicate_key_error = ' Duplicate dictionary key: {} ' )
assert not arguments
return kwargs
return self . _holderify ( { k : _unholder ( v ) for k , v in kwargs . items ( ) } )
@_holderify_result ( ( bool , Disabler ) )
def evaluate_notstatement ( self , cur : mparser . NotNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
def evaluate_notstatement ( self , cur : mparser . NotNode ) - > InterpreterObject :
v = self . evaluate_statement ( cur . value )
if isinstance ( v , Disabler ) :
return v
# TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects
if not isinstance ( v , InterpreterObject ) :
raise mesonlib . MesonBugException ( f ' Argument to not ( { v } ) is not an InterpreterObject but { type ( v ) . __name__ } . ' )
return v . operator_call ( MesonOperator . NOT , None )
return self . _holderify ( v . operator_call ( MesonOperator . NOT , None ) )
def evaluate_if ( self , node : mparser . IfClauseNode ) - > T . Optional [ Disabler ] :
assert isinstance ( node , mparser . IfClauseNode )
@ -284,10 +263,10 @@ class InterpreterBase:
return result
if not isinstance ( result , InterpreterObject ) :
raise mesonlib . MesonBugException ( f ' Argument to not ( { result } ) is not an InterpreterObject but { type ( result ) . __name__ } . ' )
result = result . operator_call ( MesonOperator . BOOL , None )
if not isinstance ( result , bool ) :
res = result . operator_call ( MesonOperator . BOOL , None )
if not isinstance ( res , bool ) :
raise InvalidCode ( f ' If clause { result !r} does not evaluate to true or false. ' )
if result :
if res :
prev_meson_version = mesonlib . project_meson_versions [ self . subproject ]
if self . tmp_meson_version :
mesonlib . project_meson_versions [ self . subproject ] = self . tmp_meson_version
@ -300,20 +279,7 @@ class InterpreterBase:
self . evaluate_codeblock ( node . elseblock )
return None
def validate_comparison_types ( self , val1 : T . Any , val2 : T . Any ) - > bool :
if type ( val1 ) != type ( val2 ) :
return False
return True
def _evaluate_in ( self , val1 : T . Any , val2 : T . Any ) - > bool :
if not isinstance ( val1 , ( str , int , float , mesonlib . HoldableObject ) ) :
raise InvalidArguments ( ' lvalue of " in " operator must be a string, integer, float, or object ' )
if not isinstance ( val2 , ( list , dict ) ) :
raise InvalidArguments ( ' rvalue of " in " operator must be an array or a dict ' )
return val1 in val2
@_holderify_result ( ( bool , Disabler ) )
def evaluate_comparison ( self , node : mparser . ComparisonNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
def evaluate_comparison ( self , node : mparser . ComparisonNode ) - > InterpreterObject :
val1 = self . evaluate_statement ( node . left )
if isinstance ( val1 , Disabler ) :
return val1
@ -334,105 +300,42 @@ class InterpreterBase:
} [ node . ctype ]
# Check if the arguments should be reversed for simplicity (this essentially converts `in` to `contains`)
if operator in ( MesonOperator . IN , MesonOperator . NOT_IN ) and isinstance ( val2 , InterpreterObject ) :
return val2 . operator_call ( operator , _unholder ( val1 ) )
# Normal evaluation, with the same semantics
elif operator not in ( MesonOperator . IN , MesonOperator . NOT_IN ) and isinstance ( val1 , InterpreterObject ) :
return val1 . operator_call ( operator , _unholder ( val2 ) )
# OLD CODE, based on the builtin types -- remove once we have switched
# over to all ObjectHolders.
# Do not compare the ObjectHolders but the actual held objects
val1 = _unholder ( val1 )
val2 = _unholder ( val2 )
if node . ctype == ' in ' :
return self . _evaluate_in ( val1 , val2 )
elif node . ctype == ' notin ' :
return not self . _evaluate_in ( val1 , val2 )
valid = self . validate_comparison_types ( val1 , val2 )
# Ordering comparisons of different types isn't allowed since PR #1810
# (0.41.0). Since PR #2884 we also warn about equality comparisons of
# different types, which is now an error.
if not valid and ( node . ctype == ' == ' or node . ctype == ' != ' ) :
raise InvalidArguments ( textwrap . dedent (
f '''
Trying to compare values of different types ( { type ( val1 ) . __name__ } , { type ( val2 ) . __name__ } ) using { node . ctype } .
This was deprecated and undefined behavior previously and is as of 0.60 .0 a hard error .
'''
) )
if node . ctype == ' == ' :
return val1 == val2
elif node . ctype == ' != ' :
return val1 != val2
elif not valid :
raise InterpreterException (
' Values of different types ( {} , {} ) cannot be compared using {} . ' . format ( type ( val1 ) . __name__ ,
type ( val2 ) . __name__ ,
node . ctype ) )
elif not isinstance ( val1 , self . elementary_types ) :
raise InterpreterException ( ' {} can only be compared for equality. ' . format ( getattr ( node . left , ' value ' , ' <ERROR> ' ) ) )
elif not isinstance ( val2 , self . elementary_types ) :
raise InterpreterException ( ' {} can only be compared for equality. ' . format ( getattr ( node . right , ' value ' , ' <ERROR> ' ) ) )
# Use type: ignore because mypy will complain that we are comparing two Unions,
# but we actually guarantee earlier that both types are the same
elif node . ctype == ' < ' :
return val1 < val2
elif node . ctype == ' <= ' :
return val1 < = val2
elif node . ctype == ' > ' :
return val1 > val2
elif node . ctype == ' >= ' :
return val1 > = val2
else :
raise InvalidCode ( ' You broke my compare eval. ' )
if operator in ( MesonOperator . IN , MesonOperator . NOT_IN ) :
val1 , val2 = val2 , val1
@_holderify_result ( ( bool , Disabler ) )
def evaluate_andstatement ( self , cur : mparser . AndNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
return self . _holderify ( val1 . operator_call ( operator , _unholder ( val2 ) ) )
def evaluate_andstatement ( self , cur : mparser . AndNode ) - > InterpreterObject :
l = self . evaluate_statement ( cur . left )
if isinstance ( l , Disabler ) :
return l
if not isinstance ( l , InterpreterObject ) :
raise mesonlib . MesonBugException ( f ' Firtst argument to and ( { l } ) is not an InterpreterObject but { type ( l ) . __name__ } . ' )
l_bool = l . operator_call ( MesonOperator . BOOL , None )
if not l_bool :
return l_bool
return self . _holderify ( l_bool )
r = self . evaluate_statement ( cur . right )
if isinstance ( r , Disabler ) :
return r
if not isinstance ( r , InterpreterObject ) :
raise mesonlib . MesonBugException ( f ' Second argument to and ( { r } ) is not an InterpreterObject but { type ( r ) . __name__ } . ' )
return r . operator_call ( MesonOperator . BOOL , None )
return self . _holderify ( r . operator_call ( MesonOperator . BOOL , None ) )
@_holderify_result ( ( bool , Disabler ) )
def evaluate_orstatement ( self , cur : mparser . OrNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
def evaluate_orstatement ( self , cur : mparser . OrNode ) - > InterpreterObject :
l = self . evaluate_statement ( cur . left )
if isinstance ( l , Disabler ) :
return l
if not isinstance ( l , InterpreterObject ) :
raise mesonlib . MesonBugException ( f ' Firtst argument to or ( { l } ) is not an InterpreterObject but { type ( l ) . __name__ } . ' )
l_bool = l . operator_call ( MesonOperator . BOOL , None )
if l_bool :
return l_bool
return self . _holderify ( l_bool )
r = self . evaluate_statement ( cur . right )
if isinstance ( r , Disabler ) :
return r
if not isinstance ( r , InterpreterObject ) :
raise mesonlib . MesonBugException ( f ' Second argument to ot ( { r } ) is not an InterpreterObject but { type ( r ) . __name__ } . ' )
return r . operator_call ( MesonOperator . BOOL , None )
return self . _holderify ( r . operator_call ( MesonOperator . BOOL , None ) )
@_holderify_result ( )
def evaluate_uminusstatement ( self , cur : mparser . UMinusNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
def evaluate_uminusstatement ( self , cur : mparser . UMinusNode ) - > InterpreterObject :
v = self . evaluate_statement ( cur . value )
if isinstance ( v , Disabler ) :
return v
# TYPING TODO: Remove this check once `evaluate_statement` only returns InterpreterObjects
if not isinstance ( v , InterpreterObject ) :
raise InterpreterException ( f ' Argument to negation ( { v } ) is not an InterpreterObject but { type ( v ) . __name__ } . ' )
return v . operator_call ( MesonOperator . UMINUS , None )
return self . _holderify ( v . operator_call ( MesonOperator . UMINUS , None ) )
def evaluate_arithmeticstatement ( self , cur : mparser . ArithmeticNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
def evaluate_arithmeticstatement ( self , cur : mparser . ArithmeticNode ) - > InterpreterObject :
l = self . evaluate_statement ( cur . left )
if isinstance ( l , Disabler ) :
return l
@ -440,53 +343,21 @@ class InterpreterBase:
if isinstance ( r , Disabler ) :
return r
# New code based on InterpreterObjects
if isinstance ( l , InterpreterObject ) :
mapping : T . Dict [ str , MesonOperator ] = {
' add ' : MesonOperator . PLUS ,
' sub ' : MesonOperator . MINUS ,
' mul ' : MesonOperator . TIMES ,
' div ' : MesonOperator . DIV ,
' mod ' : MesonOperator . MOD ,
}
res = l . operator_call ( mapping [ cur . operation ] , _unholder ( r ) )
return self . _holderify ( res )
# OLD CODE, based on the builtin types -- remove once we have switched
# over to all ObjectHolders.
if cur . operation == ' add ' :
if isinstance ( l , dict ) and isinstance ( r , dict ) :
return { * * l , * * r }
try :
# MyPy error due to handling two Unions (we are catching all exceptions anyway)
return l + r # type: ignore
except Exception as e :
raise InvalidCode ( ' Invalid use of addition: ' + str ( e ) )
elif cur . operation == ' sub ' :
if not isinstance ( l , int ) or not isinstance ( r , int ) :
raise InvalidCode ( ' Subtraction works only with integers. ' )
raise mesonlib . MesonBugException ( ' The integer was not held by an ObjectHolder! ' )
elif cur . operation == ' mul ' :
if not isinstance ( l , int ) or not isinstance ( r , int ) :
raise InvalidCode ( ' Multiplication works only with integers. ' )
raise mesonlib . MesonBugException ( ' The integer was not held by an ObjectHolder! ' )
elif cur . operation == ' div ' :
raise mesonlib . MesonBugException ( ' The integer or string was not held by an ObjectHolder! ' )
elif cur . operation == ' mod ' :
if not isinstance ( l , int ) or not isinstance ( r , int ) :
raise InvalidCode ( ' Modulo works only with integers. ' )
raise mesonlib . MesonBugException ( ' The integer was not held by an ObjectHolder! ' )
else :
raise InvalidCode ( ' You broke me. ' )
def evaluate_ternary ( self , node : mparser . TernaryNode ) - > T . Union [ TYPE_var , InterpreterObject ] :
mapping : T . Dict [ str , MesonOperator ] = {
' add ' : MesonOperator . PLUS ,
' sub ' : MesonOperator . MINUS ,
' mul ' : MesonOperator . TIMES ,
' div ' : MesonOperator . DIV ,
' mod ' : MesonOperator . MOD ,
}
res = l . operator_call ( mapping [ cur . operation ] , _unholder ( r ) )
return self . _holderify ( res )
def evaluate_ternary ( self , node : mparser . TernaryNode ) - > T . Optional [ InterpreterObject ] :
assert isinstance ( node , mparser . TernaryNode )
result = self . evaluate_statement ( node . condition )
if isinstance ( result , Disabler ) :
return result
if not isinstance ( result , InterpreterObject ) :
raise mesonlib . MesonBugException ( f ' Ternary condition ( { result } ) is not an InterpreterObject but { type ( result ) . __name__ } . ' )
result_bool = result . operator_call ( MesonOperator . BOOL , None )
if result_bool :
return self . evaluate_statement ( node . trueblock )
@ -494,8 +365,7 @@ class InterpreterBase:
return self . evaluate_statement ( node . falseblock )
@FeatureNew ( ' format strings ' , ' 0.58.0 ' )
@_holderify_result ( str )
def evaluate_fstring ( self , node : mparser . FormatStringNode ) - > str :
def evaluate_fstring ( self , node : mparser . FormatStringNode ) - > InterpreterObject :
assert isinstance ( node , mparser . FormatStringNode )
def replace ( match : T . Match [ str ] ) - > str :
@ -510,38 +380,37 @@ class InterpreterBase:
except KeyError :
raise InvalidCode ( f ' Identifier " { var } " does not name a variable. ' )
return re . sub ( r ' @([_a-zA-Z][_0-9a-zA-Z]*)@ ' , replace , node . value )
res = re . sub ( r ' @([_a-zA-Z][_0-9a-zA-Z]*)@ ' , replace , node . value )
return self . _holderify ( res )
def evaluate_foreach ( self , node : mparser . ForeachClauseNode ) - > None :
assert isinstance ( node , mparser . ForeachClauseNode )
items = self . evaluate_statement ( node . items )
if isinstance ( items , ( list , RangeHolder ) ) :
if len ( node . varnames ) != 1 :
raise InvalidArguments ( ' Foreach on array does not unpack ' )
varname = node . varnames [ 0 ]
for item in items :
self . set_variable ( varname , self . _holderify ( item , permissive = True ) )
try :
self . evaluate_codeblock ( node . block )
except ContinueRequest :
continue
except BreakRequest :
break
elif isinstance ( items , dict ) :
if len ( node . varnames ) != 2 :
raise InvalidArguments ( ' Foreach on dict unpacks key and value ' )
for key , value in sorted ( items . items ( ) ) :
self . set_variable ( node . varnames [ 0 ] , self . _holderify ( key ) )
self . set_variable ( node . varnames [ 1 ] , self . _holderify ( value , permissive = True ) )
try :
self . evaluate_codeblock ( node . block )
except ContinueRequest :
continue
except BreakRequest :
break
else :
raise InvalidArguments ( ' Items of foreach loop must be an array or a dict ' )
if not isinstance ( items , IterableObject ) :
raise InvalidArguments ( ' Items of foreach loop do not support iterating ' )
tsize = items . iter_tuple_size ( )
if len ( node . varnames ) != ( tsize or 1 ) :
raise InvalidArguments ( f ' Foreach expects exactly { tsize or 1 } variables for iterating over objects of type { items . display_name ( ) } ' )
for i in items . iter_self ( ) :
if tsize is None :
if isinstance ( i , tuple ) :
raise mesonlib . MesonBugException ( f ' Iteration of { items } returned a tuple even though iter_tuple_size() is None ' )
self . set_variable ( node . varnames [ 0 ] , self . _holderify ( i ) )
else :
if not isinstance ( i , tuple ) :
raise mesonlib . MesonBugException ( f ' Iteration of { items } did not return a tuple even though iter_tuple_size() is { tsize } ' )
if len ( i ) != tsize :
raise mesonlib . MesonBugException ( f ' Iteration of { items } did not return a tuple even though iter_tuple_size() is { tsize } ' )
for j in range ( tsize ) :
self . set_variable ( node . varnames [ j ] , self . _holderify ( i [ j ] ) )
try :
self . evaluate_codeblock ( node . block )
except ContinueRequest :
continue
except BreakRequest :
break
def evaluate_plusassign ( self , node : mparser . PlusAssignmentNode ) - > None :
assert isinstance ( node , mparser . PlusAssignmentNode )
@ -551,69 +420,21 @@ class InterpreterBase:
# Remember that all variables are immutable. We must always create a
# full new variable and then assign it.
old_variable = self . get_variable ( varname )
# TYPING TODO: This should only be InterpreterObject in the future
new_value : T . Union [ None , TYPE_var , InterpreterObject ] = None
if isinstance ( old_variable , str ) :
if not isinstance ( addition , str ) :
raise InvalidArguments ( ' The += operator requires a string on the right hand side if the variable on the left is a string ' )
new_value = old_variable + addition
elif isinstance ( old_variable , list ) :
if isinstance ( addition , list ) :
new_value = old_variable + addition
else :
new_value = old_variable + [ addition ]
elif isinstance ( old_variable , dict ) :
if not isinstance ( addition , dict ) :
raise InvalidArguments ( ' The += operator requires a dict on the right hand side if the variable on the left is a dict ' )
new_value = { * * old_variable , * * addition }
elif isinstance ( old_variable , InterpreterObject ) :
# TODO: don't make _unholder permissive
new_value = self . _holderify ( old_variable . operator_call ( MesonOperator . PLUS , _unholder ( addition ) ) )
# Add other data types here.
else :
raise InvalidArguments ( ' The += operator currently only works with arrays, dicts, strings or ints ' )
new_value = self . _holderify ( old_variable . operator_call ( MesonOperator . PLUS , _unholder ( addition ) ) )
self . set_variable ( varname , new_value )
def evaluate_indexing ( self , node : mparser . IndexNode ) - > T . Union [ TYPE_elementary , InterpreterObject ] :
def evaluate_indexing ( self , node : mparser . IndexNode ) - > InterpreterObject :
assert isinstance ( node , mparser . IndexNode )
iobject = self . evaluate_statement ( node . iobject )
if isinstance ( iobject , Disabler ) :
return iobject
index = _unholder ( self . evaluate_statement ( node . index ) )
if isinstance ( iobject , InterpreterObject ) :
return self . _holderify ( iobject . operator_call ( MesonOperator . INDEX , index ) )
if not hasattr ( iobject , ' __getitem__ ' ) :
raise InterpreterException (
' Tried to index an object that doesn \' t support indexing. ' )
if isinstance ( iobject , dict ) :
if not isinstance ( index , str ) :
raise InterpreterException ( ' Key is not a string ' )
try :
# The cast is required because we don't have recursive types...
return T . cast ( T . Union [ TYPE_elementary , InterpreterObject ] , iobject [ index ] )
except KeyError :
raise InterpreterException ( ' Key %s is not in dict ' % index )
else :
if not isinstance ( index , int ) :
raise InterpreterException ( ' Index value is not an integer. ' )
try :
# Ignore the MyPy error, since we don't know all indexable types here
# and we handle non indexable types with an exception
# TODO maybe find a better solution
res = iobject [ index ] # type: ignore
# Only holderify if we are dealing with `InterpreterObject`, since raw
# lists already store ObjectHolders
if isinstance ( iobject , InterpreterObject ) :
return self . _holderify ( res )
else :
return res
except IndexError :
# We are already checking for the existence of __getitem__, so this should be save
raise InterpreterException ( ' Index %d out of bounds of array of size %d . ' % ( index , len ( iobject ) ) ) # type: ignore
if iobject is None :
raise InterpreterException ( ' Tried to evaluate indexing on None ' )
return self . _holderify ( iobject . operator_call ( MesonOperator . INDEX , index ) )
@_holderify_result ( )
def function_call ( self , node : mparser . FunctionNode ) - > T . Optional [ T . Union [ TYPE_var , InterpreterObject ] ] :
def function_call ( self , node : mparser . FunctionNode ) - > T . Optional [ InterpreterObject ] :
func_name = node . func_name
( h_posargs , h_kwargs ) = self . reduce_arguments ( node . args )
( posargs , kwargs ) = self . _unholder_args ( h_posargs , h_kwargs )
@ -626,14 +447,15 @@ class InterpreterBase:
func_args = flatten ( posargs )
if not getattr ( func , ' no-second-level-holder-flattening ' , False ) :
func_args , kwargs = resolve_second_level_holders ( func_args , kwargs )
return func ( node , func_args , kwargs )
res = func ( node , func_args , kwargs )
return self . _holderify ( res ) if res is not None else None
else :
self . unknown_function_called ( func_name )
return None
def method_call ( self , node : mparser . MethodNode ) - > T . Optional [ T . Union [ TYPE_var , InterpreterObject ] ] :
def method_call ( self , node : mparser . MethodNode ) - > T . Optional [ InterpreterObject ] :
invokable = node . source_object
obj : T . Union [ TYPE_var , InterpreterObject ]
obj : T . Optional [ InterpreterObject ]
if isinstance ( invokable , mparser . IdNode ) :
object_name = invokable . value
obj = self . get_variable ( object_name )
@ -644,16 +466,6 @@ class InterpreterBase:
( args , kwargs ) = self . _unholder_args ( h_args , h_kwargs )
if is_disabled ( args , kwargs ) :
return Disabler ( )
if isinstance ( obj , str ) :
raise mesonlib . MesonBugException ( ' Strings are now wrapped in object holders! ' )
if isinstance ( obj , bool ) :
raise mesonlib . MesonBugException ( ' Booleans are now wrapped in object holders! ' )
if isinstance ( obj , int ) :
raise mesonlib . MesonBugException ( ' Integers are now wrapped in object holders! ' )
if isinstance ( obj , list ) :
return self . array_method_call ( obj , method_name , args , kwargs )
if isinstance ( obj , dict ) :
return self . dict_method_call ( obj , method_name , args , kwargs )
if not isinstance ( obj , InterpreterObject ) :
raise InvalidArguments ( ' Variable " %s " is not callable. ' % object_name )
# TODO: InterpreterBase **really** shouldn't be in charge of checking this
@ -663,17 +475,11 @@ class InterpreterBase:
elif not isinstance ( obj , Disabler ) :
raise InvalidArguments ( f ' Invalid operation " extract_objects " on variable " { object_name } " of type { type ( obj ) . __name__ } ' )
obj . current_node = node
return self . _holderify ( obj . method_call ( method_name , args , kwargs ) )
res = obj . method_call ( method_name , args , kwargs )
return self . _holderify ( res ) if res is not None else None
def _holderify ( self , res : T . Union [ TYPE_var , InterpreterObject , None ] , * , permissive : bool = False ) - > T . Union [ TYPE_elementary , InterpreterObject ] :
# TODO: remove `permissive` once all primitives are ObjectHolders
if res is None :
return None
elif isinstance ( res , list ) :
return [ self . _holderify ( x , permissive = permissive ) for x in res ]
elif isinstance ( res , dict ) :
return { k : self . _holderify ( v , permissive = permissive ) for k , v in res . items ( ) }
elif isinstance ( res , HoldableTypes ) :
def _holderify ( self , res : T . Union [ TYPE_var , InterpreterObject ] ) - > InterpreterObject :
if isinstance ( res , HoldableTypes ) :
# Always check for an exact match first.
cls = self . holder_map . get ( type ( res ) , None )
if cls is not None :
@ -686,136 +492,42 @@ class InterpreterBase:
return cls ( res , T . cast ( ' Interpreter ' , self ) )
raise mesonlib . MesonBugException ( f ' Object { res } of type { type ( res ) . __name__ } is neither in self.holder_map nor self.bound_holder_map. ' )
elif isinstance ( res , ObjectHolder ) :
if permissive :
return res
raise mesonlib . MesonBugException ( f ' Returned object { res } of type { type ( res ) . __name__ } is an object holder. ' )
elif isinstance ( res , MesonInterpreterObject ) :
return res
raise mesonlib . MesonBugException ( f ' Unknown returned object { res } of type { type ( res ) . __name__ } in the parameters. ' )
def _unholder_args ( self ,
args : T . List [ T . Union [ TYPE_var , InterpreterObject ] ] ,
kwargs : T . Dict [ str , T . Union [ TYPE_var , InterpreterObject ] ] ) - > T . Tuple [ T . List [ TYPE_var ] , TYPE_kwargs ] :
args : T . List [ InterpreterObject ] ,
kwargs : T . Dict [ str , InterpreterObject ] ) - > T . Tuple [ T . List [ TYPE_var ] , TYPE_kwargs ] :
return [ _unholder ( x ) for x in args ] , { k : _unholder ( v ) for k , v in kwargs . items ( ) }
@staticmethod
def _get_one_string_posarg ( posargs : T . List [ TYPE_var ] , method_name : str ) - > str :
if len ( posargs ) > 1 :
raise InterpreterException ( f ' { method_name } () must have zero or one arguments ' )
elif len ( posargs ) == 1 :
s = posargs [ 0 ]
if not isinstance ( s , str ) :
raise InterpreterException ( f ' { method_name } () argument must be a string ' )
return s
return None
def unknown_function_called ( self , func_name : str ) - > None :
raise InvalidCode ( ' Unknown function " %s " . ' % func_name )
@noKwargs
def array_method_call ( self ,
obj : T . List [ T . Union [ TYPE_elementary , InterpreterObject ] ] ,
method_name : str ,
posargs : T . List [ TYPE_var ] ,
kwargs : TYPE_kwargs ) - > T . Union [ TYPE_var , InterpreterObject ] :
if method_name == ' contains ' :
def check_contains ( el : T . List [ TYPE_var ] ) - > bool :
if len ( posargs ) != 1 :
raise InterpreterException ( ' Contains method takes exactly one argument. ' )
item = posargs [ 0 ]
for element in el :
if isinstance ( element , list ) :
found = check_contains ( element )
if found :
return True
if element == item :
return True
return False
return self . _holderify ( check_contains ( [ _unholder ( x ) for x in obj ] ) )
elif method_name == ' length ' :
return self . _holderify ( len ( obj ) )
elif method_name == ' get ' :
index = posargs [ 0 ]
fallback = None
if len ( posargs ) == 2 :
fallback = self . _holderify ( posargs [ 1 ] )
elif len ( posargs ) > 2 :
m = ' Array method \' get() \' only takes two arguments: the ' \
' index and an optional fallback value if the index is ' \
' out of range. '
raise InvalidArguments ( m )
if not isinstance ( index , int ) :
raise InvalidArguments ( ' Array index must be a number. ' )
if index < - len ( obj ) or index > = len ( obj ) :
if fallback is None :
m = ' Array index {!r} is out of bounds for array of size {!r} . '
raise InvalidArguments ( m . format ( index , len ( obj ) ) )
if isinstance ( fallback , mparser . BaseNode ) :
return self . evaluate_statement ( fallback )
return fallback
return obj [ index ]
raise InterpreterException ( f ' Arrays do not have a method called { method_name !r} . ' )
@noKwargs
def dict_method_call ( self ,
obj : T . Dict [ str , T . Union [ TYPE_elementary , InterpreterObject ] ] ,
method_name : str ,
posargs : T . List [ TYPE_var ] ,
kwargs : TYPE_kwargs ) - > T . Union [ TYPE_var , InterpreterObject ] :
if method_name in ( ' has_key ' , ' get ' ) :
if method_name == ' has_key ' :
if len ( posargs ) != 1 :
raise InterpreterException ( ' has_key() takes exactly one argument. ' )
else :
if len ( posargs ) not in ( 1 , 2 ) :
raise InterpreterException ( ' get() takes one or two arguments. ' )
key = posargs [ 0 ]
if not isinstance ( key , ( str ) ) :
raise InvalidArguments ( ' Dictionary key must be a string. ' )
has_key = key in obj
if method_name == ' has_key ' :
return self . _holderify ( has_key )
if has_key :
return obj [ key ]
if len ( posargs ) == 2 :
fallback = self . _holderify ( posargs [ 1 ] )
if isinstance ( fallback , mparser . BaseNode ) :
return self . evaluate_statement ( fallback )
return fallback
raise InterpreterException ( f ' Key { key !r} is not in the dictionary. ' )
if method_name == ' keys ' :
if len ( posargs ) != 0 :
raise InterpreterException ( ' keys() takes no arguments. ' )
return sorted ( obj . keys ( ) )
raise InterpreterException ( ' Dictionaries do not have a method called " %s " . ' % method_name )
def reduce_arguments (
self ,
args : mparser . ArgumentNode ,
key_resolver : T . Callable [ [ mparser . BaseNode ] , str ] = default_resolve_key ,
duplicate_key_error : T . Optional [ str ] = None ,
) - > T . Tuple [
T . List [ T . Union [ TYPE_var , InterpreterObject ] ] ,
T . Dict [ str , T . Union [ TYPE_var , InterpreterObject ] ]
T . List [ InterpreterObject ] ,
T . Dict [ str , InterpreterObject ]
] :
assert isinstance ( args , mparser . ArgumentNode )
if args . incorrect_order ( ) :
raise InvalidArguments ( ' All keyword arguments must be after positional arguments. ' )
self . argument_depth + = 1
reduced_pos : T . List [ T . Union [ TYPE_var , InterpreterObject ] ] = [ self . evaluate_statement ( arg ) for arg in args . arguments ]
reduced_kw : T . Dict [ str , T . Union [ TYPE_var , InterpreterObject ] ] = { }
reduced_pos = [ self . evaluate_statement ( arg ) for arg in args . arguments ]
if any ( x is None for x in reduced_pos ) :
raise InvalidArguments ( f ' At least one value in the arguments is void. ' )
reduced_kw : T . Dict [ str , InterpreterObject ] = { }
for key , val in args . kwargs . items ( ) :
reduced_key = key_resolver ( key )
assert isinstance ( val , mparser . BaseNode )
reduced_val = self . evaluate_statement ( val )
if reduced_val is None :
raise InvalidArguments ( f ' Value of key { reduced_key } is void. ' )
if duplicate_key_error and reduced_key in reduced_kw :
raise InvalidArguments ( duplicate_key_error . format ( reduced_key ) )
reduced_kw [ reduced_key ] = reduced_val
@ -823,10 +535,10 @@ class InterpreterBase:
final_kw = self . expand_default_kwargs ( reduced_kw )
return reduced_pos , final_kw
def expand_default_kwargs ( self , kwargs : T . Dict [ str , T . Union [ TYPE_var , InterpreterObject ] ] ) - > T . Dict [ str , T . Union [ TYPE_var , InterpreterObject ] ] :
def expand_default_kwargs ( self , kwargs : T . Dict [ str , T . Optional [ InterpreterObject ] ] ) - > T . Dict [ str , T . Optional [ InterpreterObject ] ] :
if ' kwargs ' not in kwargs :
return kwargs
to_expand = kwargs . pop ( ' kwargs ' )
to_expand = _unholder ( kwargs . pop ( ' kwargs ' ) )
if not isinstance ( to_expand , dict ) :
raise InterpreterException ( ' Value of " kwargs " must be dictionary. ' )
if ' kwargs ' in to_expand :
@ -834,20 +546,20 @@ class InterpreterBase:
for k , v in to_expand . items ( ) :
if k in kwargs :
raise InterpreterException ( f ' Entry " { k } " defined both as a keyword argument and in a " kwarg " entry. ' )
kwargs [ k ] = v
kwargs [ k ] = self . _holderify ( v )
return kwargs
def assignment ( self , node : mparser . AssignmentNode ) - > None :
assert isinstance ( node , mparser . AssignmentNode )
if self . argument_depth != 0 :
raise InvalidArguments ( ''' Tried to assign values inside an argument list.
To specify a keyword argument , use : instead of = . ''' )
raise InvalidArguments ( textwrap . dedent ( ''' \
Tried to assign values inside an argument list .
To specify a keyword argument , use : instead of = .
''' ))
var_name = node . var_name
if not isinstance ( var_name , str ) :
raise InvalidArguments ( ' Tried to assign value to a non-variable. ' )
value = self . evaluate_statement ( node . value )
if not self . is_assignable ( value ) :
raise InvalidCode ( f ' Tried to assign the invalid value " { value } " of type { type ( value ) . __name__ } to variable. ' )
# For mutable objects we need to make a copy on assignment
if isinstance ( value , MutableInterpreterObject ) :
value = copy . deepcopy ( value )
@ -860,36 +572,23 @@ To specify a keyword argument, use : instead of =.''')
if holderify :
variable = self . _holderify ( variable )
else :
# Ensure that we are never storing a HoldableObject
def check ( x : T . Union [ TYPE_var , InterpreterObject ] ) - > None :
if isinstance ( x , mesonlib . HoldableObject ) :
raise mesonlib . MesonBugException ( f ' set_variable in InterpreterBase called with a HoldableObject { x } of type { type ( x ) . __name__ } ' )
elif isinstance ( x , list ) :
for y in x :
check ( y )
elif isinstance ( x , dict ) :
for v in x . values ( ) :
check ( v )
check ( variable )
# Ensure that we are always storing ObjectHolders
if not isinstance ( variable , InterpreterObject ) :
raise mesonlib . MesonBugException ( f ' set_variable in InterpreterBase called with a non InterpreterObject { variable } of type { type ( variable ) . __name__ } ' )
if not isinstance ( varname , str ) :
raise InvalidCode ( ' First argument to set_variable must be a string. ' )
if not self . is_assignable ( variable ) :
raise InvalidCode ( f ' Assigned value " { variable } " of type { type ( variable ) . __name__ } is not an assignable type. ' )
if re . match ( ' [_a-zA-Z][_0-9a-zA-Z]*$ ' , varname ) is None :
raise InvalidCode ( ' Invalid variable name: ' + varname )
if varname in self . builtin :
raise InvalidCode ( ' Tried to overwrite internal variable " %s " ' % varname )
self . variables [ varname ] = variable
def get_variable ( self , varname : str ) - > T . Union [ TYPE_var , InterpreterObject ] :
def get_variable ( self , varname : str ) - > InterpreterObject :
if varname in self . builtin :
return self . builtin [ varname ]
if varname in self . variables :
return self . variables [ varname ]
raise InvalidCode ( ' Unknown variable " %s " . ' % varname )
def is_assignable ( self , value : T . Any ) - > bool :
return isinstance ( value , ( InterpreterObject , list , dict ) )
def validate_extraction ( self , buildtarget : mesonlib . HoldableObject ) - > None :
raise InterpreterException ( ' validate_extraction is not implemented in this context (please file a bug) ' )