/*
 * $Id$
 *
 * SARL is an general-purpose agent programming language.
 * More details on http://www.sarl.io
 *
 * Copyright (C) 2014-2026 SARL.io, the original authors and main authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sarl.lang.typesystem;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtend.core.xtend.XtendConstructor;
import org.eclipse.xtend.core.xtend.XtendFunction;
import org.eclipse.xtend.core.xtend.XtendParameter;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.common.types.JvmConstructor;
import org.eclipse.xtext.common.types.JvmFormalParameter;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
import org.eclipse.xtext.common.types.JvmMember;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.util.AnnotationLookup;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.util.PolymorphicDispatcher;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.xbase.XAbstractFeatureCall;
import org.eclipse.xtext.xbase.XAbstractWhileExpression;
import org.eclipse.xtext.xbase.XAssignment;
import org.eclipse.xtext.xbase.XBasicForLoopExpression;
import org.eclipse.xtext.xbase.XBinaryOperation;
import org.eclipse.xtext.xbase.XBlockExpression;
import org.eclipse.xtext.xbase.XCastedExpression;
import org.eclipse.xtext.xbase.XCollectionLiteral;
import org.eclipse.xtext.xbase.XConstructorCall;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.XFeatureCall;
import org.eclipse.xtext.xbase.XForLoopExpression;
import org.eclipse.xtext.xbase.XIfExpression;
import org.eclipse.xtext.xbase.XMemberFeatureCall;
import org.eclipse.xtext.xbase.XPostfixOperation;
import org.eclipse.xtext.xbase.XReturnExpression;
import org.eclipse.xtext.xbase.XSwitchExpression;
import org.eclipse.xtext.xbase.XSynchronizedExpression;
import org.eclipse.xtext.xbase.XThrowExpression;
import org.eclipse.xtext.xbase.XTryCatchFinallyExpression;
import org.eclipse.xtext.xbase.XUnaryOperation;
import org.eclipse.xtext.xbase.XVariableDeclaration;
import org.eclipse.xtext.xbase.lib.Functions.Function2;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.scoping.featurecalls.OperatorMapping;

import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import io.sarl.lang.jvmmodel.SarlJvmModelAssociations;
import io.sarl.lang.sarl.SarlAssertExpression;
import io.sarl.lang.sarl.SarlBreakExpression;
import io.sarl.lang.sarl.SarlContinueExpression;
import io.sarl.lang.sarl.actionprototype.IActionPrototypeProvider;
import io.sarl.lang.sarl.actionprototype.InferredPrototype;
import io.sarl.lang.sarl.actionprototype.QualifiedActionName;
import io.sarl.lang.util.Utils;

/**
 * Helper on operations.
 *
 * <p>This implementation extends the Xtend expression helper by assuming that any function
 * with a name starting with "get", "is", "has" is a pure function.
 * It also assumes that "equals", "hashCode", "clone" and "toString" are also pure functions.
 *
 * @author $Author: sgalland$
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 * @since 0.6
 */
@Singleton
public class SARLOperationHelper implements IOperationHelper {

	@Inject
	private IPureOperationNameValidator nameValidator;

	@Inject
	private AnnotationLookup annotations;

	@Inject
	private IActionPrototypeProvider actionPrototypes;

	@Inject
	private SarlJvmModelAssociations associations;

	@Inject
	private OperatorMapping operatorMapping;

	private final PolymorphicDispatcher<Boolean> hasSideEffectsDispatcher;

	/** Constructor.
	 */
	public SARLOperationHelper() {
		this.hasSideEffectsDispatcher = new PolymorphicDispatcher<>(
				"_hasSideEffects", 2, 2, //$NON-NLS-1$
				Collections.singletonList(this)) {
			@Override
			protected Boolean handleNoSuchMethod(Object... params) {
				return Boolean.FALSE;
			}
		};
	}

	/** Replies if the given function is purable according to its modifiers and prototype.
	 *
	 * <p>Basically, an operation cannot be pure if:<ul>
	 * <li>native,</li>
	 * <li>the return type is void.</li>
	 * </ul>
	 *
	 * <p>This function is called usually before testing the pattern of the function's name.
	 *
	 * @param operation the operation to test.
	 * @return {@code true} if the operation is not pure according to the prototype.
	 * @since 0.10
	 */
	protected boolean isPureStateForbidden(XtendFunction operation) {
		if (operation == null
				|| operation.isNative()) {
			return true;
		}
		//  Return type with void means that the function has a side effect
		var returnType = operation.getReturnType();
		if (returnType != null) {
			// The type is specified in SARL
			return Void.TYPE.getName().equals(returnType.getIdentifier());
		}
		final var jvmOperation = this.associations.getDirectlyInferredOperation(operation);
		if (jvmOperation != null) {
			returnType = jvmOperation.getReturnType();
			if (returnType != null) {
				// The type is specified in SARL
				return Void.TYPE.getName().equals(returnType.getIdentifier());
			}
			return false;
		}
		return true;
	}

	/** Replies if it is impossible to determine the pure state of the given function .
	 *
	 * @param operation the operation to test.
	 * @return {@code true} if the pure state of the operation cannot be determined.
	 * @since 0.10
	 */
	@SuppressWarnings("static-method")
	protected boolean isPureStateAmbiguous(XtendFunction operation) {
		return operation.isAbstract() || operation.getExpression() == null;
	}

	@Override
	public boolean isPurableOperation(XtendFunction operation) {
		return isPurableOperation(operation, null);
	}

	/** Replies if the given is purable in the given context.
	 *
	 * @param operation the operation to test.
	 * @param context the context.
	 * @return {@code true} if the operation could be marked as pure.
	 */
	boolean isPurableOperation(XtendFunction operation, ISideEffectContext context) {
		if (isPureStateForbidden(operation)) {
			return false;
		}
		if (this.nameValidator.isNamePatternForNotPureOperation(operation)) {
			return false;
		}
		if (this.nameValidator.isNamePatternForPureOperation(operation)) {
			return true;
		}
		if (isPureStateAmbiguous(operation)) {
			return false;
		}
		// Test the body
		if (context == null) {
			return !hasSideEffects(
					getInferredPrototype(operation),
					operation.getExpression());
		}
		final Boolean result = internalHasSideEffects(operation.getExpression(), context);
		return result == null || !result.booleanValue();
	}

	private InferredPrototype createInferredPrototype(QualifiedActionName executableKey, List<XtendParameter> parameters) {
		final var isVarArgs = Utils.isVarArg(parameters);
		return this.actionPrototypes.createPrototypeFromSarlModel(
				// TODO more general context?
				this.actionPrototypes.createContext(),
				executableKey, isVarArgs, parameters);
	}

	private InferredPrototype createInferredPrototype(QualifiedActionName executableKey, boolean isVarArgs, List<JvmFormalParameter> parameters) {
		return this.actionPrototypes.createPrototypeFromJvmModel(
				// TODO more general context?
				this.actionPrototypes.createContext(),
				executableKey, isVarArgs, parameters);
	}

	/** Replies the inferred prototype of the given operation.
	 *
	 * @param operation the operation.
	 * @return the inferred prototype.
	 * @since 0.12
	 */
	public InferredPrototype getInferredPrototype(XtendFunction operation) {
		final var container = this.associations.getInferredType(operation.getDeclaringType());
		final var actionKey = this.actionPrototypes.createQualifiedActionName(
				container, operation.getName());
		// Compute the different action prototypes associated to the action to create.
		return createInferredPrototype(actionKey, operation.getParameters());
	}

	/** Replies the inferred prototype of the given constructor.
	 *
	 * @param constructor the constructor.
	 * @return the inferred prototype.
	 * @since 0.12
	 */
	public InferredPrototype getInferredPrototype(XtendConstructor constructor) {
		final var container = this.associations.getInferredType(constructor.getDeclaringType());
		final var constructorKey = this.actionPrototypes.createConstructorQualifiedName(container);
		// Compute the different action prototypes associated to the action to create.
		return createInferredPrototype(constructorKey, constructor.getParameters());
	}

	/** Replies the inferred prototype of the given operation.
	 *
	 * @param operation the operation.
	 * @return the inferred prototype.
	 * @since 0.12
	 */
	public InferredPrototype getInferredPrototype(JvmOperation operation) {
		final var fct = this.associations.getXtendFunction(operation);
		if (fct != null) {
			return getInferredPrototype(fct);
		}
		final var actionKey = this.actionPrototypes.createQualifiedActionName(
				operation.getDeclaringType(), operation.getSimpleName());
		// Compute the different action prototypes associated to the action to create.
		return createInferredPrototype(actionKey, operation.isVarArgs(), operation.getParameters());
	}

	/** Replies the inferred prototype of the given constructor.
	 *
	 * @param constructor the constructor.
	 * @return the inferred prototype.
	 * @since 0.12
	 */
	public InferredPrototype getInferredPrototype(JvmConstructor constructor) {
		final var cons = this.associations.getXtendConstructor(constructor);
		if (cons != null) {
			return getInferredPrototype(cons);
		}
		final var actionKey = this.actionPrototypes.createConstructorQualifiedName(constructor.getDeclaringType());
		// Compute the different action prototypes associated to the action to create.
		return createInferredPrototype(actionKey, constructor.isVarArgs(), constructor.getParameters());
	}

	@Override
	public Iterable<XExpression> getSideEffectExpressions(InferredPrototype calledOperation, XExpression expr) {
		final var ctx = new SideEffectContext(calledOperation, false);
		hasSideEffects(expr, ctx);
		return ctx.getSideEffectExpressions();
	}

	private Boolean internalHasSideEffects(XExpression expr, ISideEffectContext ctx) {
		final var result = hasSideEffects(expr, ctx);
		if (result != null && result.booleanValue()) {
			return Boolean.TRUE;
		}
		return Boolean.FALSE;
	}

	@Override
	public boolean hasSideEffects(InferredPrototype calledOperation, XExpression expr) {
		final var ctx = new SideEffectContext(calledOperation);
		return internalHasSideEffects(expr, ctx).booleanValue();
	}

	/** Determine if the given expression has a side effect.
	 *
	 * @param calledOperation the called operation, not yet in the call stack.
	 * @param expr the expression.
	 * @param context the context.
	 * @return {@code true} if the expression has a side effect.
	 */
	protected Boolean hasSideEffects(InferredPrototype calledOperation, XExpression expr, ISideEffectContext context) {
		final var ctx = new SideEffectContext(calledOperation, context);
		return internalHasSideEffects(expr, ctx);
	}

	/** Determine if the given expression has a side effect.
	 *
	 * @param expr the expression.
	 * @param context the context.
	 * @return {@code true} if the expression has a side effect.
	 */
	protected Boolean hasSideEffects(XExpression expr, ISideEffectContext context) {
		if (expr != null && !expr.eIsProxy()) {
			return this.hasSideEffectsDispatcher.invoke(expr, context);
		}
		return Boolean.FALSE;
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XSynchronizedExpression expression, ISideEffectContext context) {
		return hasSideEffects(expression.getExpression(), context);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XCastedExpression expression, ISideEffectContext context) {
		return hasSideEffects(expression.getTarget(), context);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XAbstractWhileExpression expression, ISideEffectContext context) {
		context.open();
		try {
			if (context.isStoppingAtFirstSideEffect()) {
				if (hasSideEffects(expression.getPredicate(), context).booleanValue()) {
					return Boolean.TRUE;
				}
				if (hasSideEffects(expression.getBody(), context.branch()).booleanValue()) {
					return Boolean.TRUE;
				}
				return Boolean.FALSE;
			}
			//
			final var r0 = hasSideEffects(expression.getPredicate(), context).booleanValue();
			final var r1 = hasSideEffects(expression.getBody(), context.branch()).booleanValue();
			return Boolean.valueOf(r0 || r1);
		} finally {
			context.close();
		}
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XForLoopExpression expression, ISideEffectContext context) {
		if (context.isStoppingAtFirstSideEffect()) {
			context.open();
			try {
				if (hasSideEffects(expression.getForExpression(), context).booleanValue()) {
					return Boolean.TRUE;
				}
			} finally {
				context.close();
			}
			return hasSideEffects(expression.getEachExpression(), context.branch());
		}
		//
		context.open();
		var r0 = false;
		try {
			r0 = hasSideEffects(expression.getForExpression(), context).booleanValue();
		} finally {
			context.close();
		}
		return Boolean.valueOf(hasSideEffects(expression.getEachExpression(), context.branch()).booleanValue() || r0);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XIfExpression expression, ISideEffectContext context) {
		if (context.isStoppingAtFirstSideEffect()) {
			if (hasSideEffects(expression.getIf(), context).booleanValue()) {
				return Boolean.TRUE;
			}
			final var buffer1 = context.createVariableAssignmentBufferForBranch();
			if (hasSideEffects(expression.getThen(), context.branch(buffer1)).booleanValue()) {
				return Boolean.TRUE;
			}
			final var buffer2 = context.createVariableAssignmentBufferForBranch();
			if (hasSideEffects(expression.getElse(), context.branch(buffer2)).booleanValue()) {
				return Boolean.TRUE;
			}
			context.mergeBranchVariableAssignments(Arrays.asList(buffer1, buffer2));
			return Boolean.FALSE;
		}
		//
		final var r0 = hasSideEffects(expression.getIf(), context).booleanValue();
		final var buffer1 = context.createVariableAssignmentBufferForBranch();
		final var r1 = hasSideEffects(expression.getThen(), context.branch(buffer1)).booleanValue();
		final var buffer2 = context.createVariableAssignmentBufferForBranch();
		final var r2 = hasSideEffects(expression.getElse(), context.branch(buffer2)).booleanValue();
		context.mergeBranchVariableAssignments(Arrays.asList(buffer1, buffer2));
		return Boolean.valueOf(r0 || r1 || r2);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XReturnExpression expression, ISideEffectContext context) {
		return hasSideEffects(expression.getExpression(), context);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	@SuppressWarnings("static-method")
	protected Boolean _hasSideEffects(XThrowExpression expression, ISideEffectContext context) {
		context.registerSideEffect(expression);
		return Boolean.TRUE;
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XTryCatchFinallyExpression expression, ISideEffectContext context) {
		if (context.isStoppingAtFirstSideEffect()) {
			final var buffers = new ArrayList<Map<String, List<XExpression>>>();
			var buffer = context.createVariableAssignmentBufferForBranch();
			if (hasSideEffects(expression.getExpression(), context.branch(buffer)).booleanValue()) {
				return Boolean.TRUE;
			}
			buffers.add(buffer);
			for (final var clause : expression.getCatchClauses()) {
				context.open();
				try {
					buffer = context.createVariableAssignmentBufferForBranch();
					if (hasSideEffects(clause.getExpression(), context.branch(buffer)).booleanValue()) {
						return Boolean.TRUE;
					}
					buffers.add(buffer);
				} finally {
					context.close();
				}
			}
			context.mergeBranchVariableAssignments(buffers);
			if (hasSideEffects(expression.getFinallyExpression(), context).booleanValue()) {
				return Boolean.TRUE;
			}
			return Boolean.FALSE;
		}
		//
		final var buffers = new ArrayList<Map<String, List<XExpression>>>();
		var buffer = context.createVariableAssignmentBufferForBranch();
		final var r0 = hasSideEffects(expression.getExpression(), context.branch(buffer)).booleanValue();
		buffers.add(buffer);
		var r1 = false;
		for (final var clause : expression.getCatchClauses()) {
			context.open();
			try {
				buffer = context.createVariableAssignmentBufferForBranch();
				r1 = hasSideEffects(clause.getExpression(), context.branch(buffer)).booleanValue() || r1;
				buffers.add(buffer);
			} finally {
				context.close();
			}
		}
		context.mergeBranchVariableAssignments(buffers);
		final var r2 = hasSideEffects(expression.getFinallyExpression(), context).booleanValue();
		return Boolean.valueOf(r0 || r1 || r2);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XVariableDeclaration expression, ISideEffectContext context) {
		if (context.isStoppingAtFirstSideEffect()) {
			if (hasSideEffects(expression.getRight(), context).booleanValue()) {
				return Boolean.TRUE;
			}
			context.declareVariable(expression.getIdentifier(), expression.getRight());
			return Boolean.FALSE;
		}
		//
		final var r0 = hasSideEffects(expression.getRight(), context);
		context.declareVariable(expression.getIdentifier(), expression.getRight());
		return r0;
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XBasicForLoopExpression expression, ISideEffectContext context) {
		context.open();
		try {
			if (context.isStoppingAtFirstSideEffect()) {
				for (final var ex : expression.getInitExpressions()) {
					if (hasSideEffects(ex, context).booleanValue()) {
						return Boolean.TRUE;
					}
				}
				if (hasSideEffects(expression.getEachExpression(), context).booleanValue()) {
					return Boolean.TRUE;
				}
				for (final var ex : expression.getUpdateExpressions()) {
					if (hasSideEffects(ex, context.branch()).booleanValue()) {
						return Boolean.TRUE;
					}
				}
				if (hasSideEffects(expression.getExpression(), context.branch()).booleanValue()) {
					return Boolean.TRUE;
				}
				return Boolean.FALSE;
			}
			//
			var r0 = false;
			for (final var ex : expression.getInitExpressions()) {
				r0 = hasSideEffects(ex, context).booleanValue() || r0;
			}
			final var r1 = hasSideEffects(expression.getEachExpression(), context).booleanValue();
			var r2 = false;
			for (final var ex : expression.getUpdateExpressions()) {
				r2 = hasSideEffects(ex, context.branch()).booleanValue() || r2;
			}
			final var r3 = hasSideEffects(expression.getExpression(), context.branch()).booleanValue();
			return Boolean.valueOf(r0 || r1 || r2 || r3);
		} finally {
			context.close();
		}
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XSwitchExpression expression, ISideEffectContext context) {
		context.open();
		try {
			if (context.isStoppingAtFirstSideEffect()) {
				if (hasSideEffects(expression.getSwitch(), context).booleanValue()) {
					return Boolean.TRUE;
				}
				final var buffers = new ArrayList<Map<String, List<XExpression>>>();
				for (final var ex : expression.getCases()) {
					context.open();
					try {
						if (hasSideEffects(ex.getCase(), context).booleanValue()) {
							return Boolean.TRUE;
						}
						final var buffer = context.createVariableAssignmentBufferForBranch();
						if (hasSideEffects(ex.getThen(), context.branch(buffer)).booleanValue()) {
							return Boolean.TRUE;
						}
						buffers.add(buffer);
					} finally {
						context.close();
					}
				}
				final var buffer = context.createVariableAssignmentBufferForBranch();
				if (hasSideEffects(expression.getDefault(), context.branch(buffer)).booleanValue()) {
					return Boolean.TRUE;
				}
				buffers.add(buffer);
				context.mergeBranchVariableAssignments(buffers);
				return Boolean.FALSE;
			}
			//
			final var r0 = hasSideEffects(expression.getSwitch(), context).booleanValue();
			final var buffers = new ArrayList<Map<String, List<XExpression>>>();
			var r1 = false;
			for (final var ex : expression.getCases()) {
				context.open();
				try {
					r1 = hasSideEffects(ex.getCase(), context).booleanValue() || r1;
					final var buffer = context.createVariableAssignmentBufferForBranch();
					r1 = hasSideEffects(ex.getThen(), context.branch(buffer)).booleanValue() || r1;
					buffers.add(buffer);
				} finally {
					context.close();
				}
			}
			final var buffer = context.createVariableAssignmentBufferForBranch();
			final var r2 = hasSideEffects(expression.getDefault(), context.branch(buffer)).booleanValue();
			buffers.add(buffer);
			context.mergeBranchVariableAssignments(buffers);
			return Boolean.valueOf(r0 || r1 || r2);
		} finally {
			context.close();
		}
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XCollectionLiteral expression, ISideEffectContext context) {
		context.open();
		try {
			if (context.isStoppingAtFirstSideEffect()) {
				for (final var ex : expression.getElements()) {
					if (hasSideEffects(ex, context).booleanValue()) {
						return Boolean.TRUE;
					}
				}
				return Boolean.FALSE;
			}
			//
			var r0 = false;
			for (final var ex : expression.getElements()) {
				r0 = hasSideEffects(ex, context).booleanValue() || r0;
			}
			return Boolean.valueOf(r0);
		} finally {
			context.close();
		}
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XConstructorCall expression, ISideEffectContext context) {
		if (context.isStoppingAtFirstSideEffect()) {
			for (final var ex : expression.getArguments()) {
				if (hasSideEffects(ex, context).booleanValue()) {
					return Boolean.TRUE;
				}
			}
			return Boolean.FALSE;
		}
		//
		var r0 = false;
		for (final var ex : expression.getArguments()) {
			r0 = hasSideEffects(ex, context).booleanValue() || r0;
		}
		return Boolean.valueOf(r0);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XBinaryOperation expression, ISideEffectContext context) {
		if (context.isStoppingAtFirstSideEffect()) {
			if (isReassignmentOperator(expression)) {
				if (!isLocalExpression(expression.getLeftOperand(), context, false)) {
					context.registerSideEffect(expression);
					return Boolean.TRUE;
				}
			} else {
				if (expression.isTypeLiteral() || expression.isPackageFragment()) {
					return Boolean.FALSE;
				}
				if (hasSideEffects(expression.getLeftOperand(), context).booleanValue()) {
					return Boolean.TRUE;
				}
			}
			if (hasSideEffects(expression.getRightOperand(), context).booleanValue()) {
				return Boolean.TRUE;
			}
			return Boolean.FALSE;
		}
		//
		var r0 = false;
		if (isReassignmentOperator(expression)) {
			if (!isLocalExpression(expression.getLeftOperand(), context, false)) {
				context.registerSideEffect(expression);
				r0 = true;
			}
		} else {
			r0 = !(expression.isTypeLiteral() || expression.isPackageFragment());
			r0 = hasSideEffects(expression.getLeftOperand(), context).booleanValue() || r0;
		}
		final var r1 = hasSideEffects(expression.getRightOperand(), context).booleanValue();
		return Boolean.valueOf(r0 || r1);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XUnaryOperation expression, ISideEffectContext context) {
		if (context.isStoppingAtFirstSideEffect()) {
			if (expression.isTypeLiteral() || expression.isPackageFragment()) {
				return Boolean.FALSE;
			}
			if (hasSideEffects(expression.getOperand(), context).booleanValue()) {
				return Boolean.TRUE;
			}
			return Boolean.FALSE;
		}
		//
		final var r0 = !(expression.isTypeLiteral() || expression.isPackageFragment());
		final var r1 = hasSideEffects(expression.getOperand(), context).booleanValue();
		return Boolean.valueOf(r0 || r1);
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	@SuppressWarnings("static-method")
	protected Boolean _hasSideEffects(XPostfixOperation expression, ISideEffectContext context) {
		context.registerSideEffect(expression);
		return Boolean.TRUE;
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XFeatureCall expression, ISideEffectContext context) {
		return Boolean.valueOf(internalHasFeatureCallSideEffects(expression, context));
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XMemberFeatureCall expression, ISideEffectContext context) {
		return Boolean.valueOf(internalHasFeatureCallSideEffects(expression, context));
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XAssignment expression, ISideEffectContext context) {
		if (context.isStoppingAtFirstSideEffect()) {
			final var feature = expression.getFeature();
			if (feature instanceof XVariableDeclaration) {
				final var se = hasSideEffects(expression.getValue(), context);
				context.assignVariable(feature.getIdentifier(), expression.getValue());
				return se;
			}
			return Boolean.TRUE;
		}
		//
		final JvmIdentifiableElement feature = expression.getFeature();
		if (feature instanceof XVariableDeclaration) {
			final var r0 = hasSideEffects(expression.getActualReceiver(), context).booleanValue();
			final var r1 = hasSideEffects(expression.getValue(), context).booleanValue();
			context.assignVariable(feature.getIdentifier(), expression.getValue());
			return Boolean.valueOf(r0 || r1);
		}
		return Boolean.TRUE;
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	protected Boolean _hasSideEffects(XBlockExpression expression, ISideEffectContext context) {
		final var exprs = expression.getExpressions();
		if (exprs != null && !exprs.isEmpty()) {
			context.open();
			try {
				if (context.isStoppingAtFirstSideEffect()) {
					for (final var ex : exprs) {
						if (hasSideEffects(ex, context).booleanValue()) {
							return Boolean.TRUE;
						}
					}
					return Boolean.FALSE;
				}
				//
				var r0 = false;
				for (final var ex : exprs) {
					r0 = hasSideEffects(ex, context).booleanValue() || r0;
				}
				return Boolean.valueOf(r0);
			} finally {
				context.close();
			}
		}
		return Boolean.FALSE;
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	@SuppressWarnings("static-method")
	protected Boolean _hasSideEffects(SarlAssertExpression expression, ISideEffectContext context) {
		// Assume => expression.isStatic === true
		return Boolean.FALSE;
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 */
	@SuppressWarnings("static-method")
	protected Boolean _hasSideEffects(SarlBreakExpression expression, ISideEffectContext context) {
		return Boolean.FALSE;
	}

	/** Test if the given expression has side effects.
	 *
	 * @param expression the expression.
	 * @param context the list of context expressions.
	 * @return {@code true} if the expression has side effects.
	 * @since 0.7
	 */
	@SuppressWarnings("static-method")
	protected Boolean _hasSideEffects(SarlContinueExpression expression, ISideEffectContext context) {
		return Boolean.FALSE;
	}

	/** Replies if the given operator is a reassignment operator.
	 * A reassignment operator changes its left operand.
	 *
	 * @param operator the operator.
	 * @return {@code true} if the operator changes its left operand.
	 */
	protected boolean isReassignmentOperator(XBinaryOperation operator) {
		if (operator.isReassignFirstArgument()) {
			return true;
		}
		final var operatorName = this.operatorMapping.getOperator(
				QualifiedName.create(operator.getFeature().getSimpleName()));
		final var compboundOperatorName = this.operatorMapping.getSimpleOperator(operatorName);
		return compboundOperatorName != null;
	}

	private boolean internalHasFeatureCallSideEffects(XAbstractFeatureCall expression, ISideEffectContext context) {
		if (expression.eIsProxy()) {
			return false;
		}
		if (expression.isTypeLiteral() || expression.isPackageFragment()) {
			return false;
		}
		var feature = expression.getFeature();
		if (feature == null) {
			// Assuming that if the link is not done, the feature is outside the operation's scope.
			return false;
		}
		if (feature.eIsProxy()) {
